OAuth 2.0是一个工业级别的开发授权协议,可以使互联网用户授权第三方网站或应用访问他们在特定网站上的信息(如个人资料、照片等),而不必向第三方网站或应用提供密码。

使用场景

某一技术交流网站S准备提供GitHub账号登录的方式,并可以访问用户在GitHub上的个人资料。传统的实现方式是需要用户提供GitHub账号、密码等私密信息给S网站,然后该网站使用这些信息请求GitHub服务器验证并获取所需的用户资料。这种方式虽然能达到目的,用户必须暴露GitHub账号信息给S网站,站在用户的角度上讲,这种做法有着极大的安全隐患,不仅增加了账号信息泄露的风险,还会使S网站对用户GitHub账户的权限无法得到限制。

于是聪明的互联网前辈们发明了OAuth 授权协议,使得上述问题迎刃而解。其大致思想是引入了一个授权服务器,经过用户授权后授权服务器向第三方应用发放一个访问令牌,这样就可以在不向第三方应用提供账号和密码的情况下,通过令牌在特定时间内访问用户存放在特定资源服务器上的资源。

角色

OAuth定义了四种角色

  • 资源所有者(resource owner)

一个可以对受保护资源授权的实体。一般指用户,如上述场景中的GitHub用户。

  • 资源服务器(resource server)

托管受保护资源的服务器,可以接受和响应使用访问令牌(access tokens)对保护资源发起的请求,可能与授权服务器是同一台服务器。在上述场景中为GitHub用户信息服务器。

  • 客户端/第三方应用(client)

代表资源所有者并使用其授权请求受保护资源的应用。如上述场景中的S网站。客户端只有向授权服务器注册了自身信息才能参与OAuth的流程。根据客户端是否有能力确保其凭证的保密性,OAuth定义了两种客户端类型。机密类型和公共类型:机密类型指客户端有能力确保其凭证(如密码)的保密性(比如客户端通过安全服务器来实现对其凭证的访问限制),或者能通过其他方式进行安全客户端认证;公共类型指客户端无法确保其凭证的保密性(比如客户端在资源所有者的设备上执行,如本机应用程序或基于Web浏览器的应用),并且无法通过其他方式进行安全客户端认证。

  • 授权服务器(authorization server)

成功地认证了资源所有者并收到授权信息后发布访问令牌的服务器,可能与资源服务器是同一台服务器。在上述场景中指的是GitHub授权服务器。

协议端点

端点(endpoint),即HTTP资源,可以为网页、API服务等。授权流程利用了两个授权服务器端点和一个客户端端点

  • 授权端点(Authorization endpoint)

一种用于客户端通过浏览器跳转的方式向资源所有者获取授权的授权服务器端点。如授权服务器授权引导页面,需要用户输入账号和密码并且确认授权。

  • 令牌端点(Token endpoint)

一种用于客户端用授权许可交换访问令牌,通常伴随着客户端认证的授权服务器端点。授权服务器通常以HTTP服务的形式暴露给客户端。

  • 重定向端点(Redirection endpoint)

一种用于授权服务器通过资源所有者浏览器返回包含授权凭证信息(如授权码)的客户端端点。如客户端用户中心页面。

客户端注册

开始OAuth流程之前,客户端的开发人员需要向授权服务器注册应用信息。授权服务器通常会提供应用注册页面,注册时一般会提供网站的基本信息如应用名称、logo、网站、重定向URI等。

重定向URI

授权成功后认证服务器会将用户浏览器重定向到重定向URI上。为了防止某些攻击,服务只会重定向到客户端注册时指定的URI上,并且重定向URI应该受到TLS的保护,即重定向URI应该支持HTTPS。

客户端标识符和客户端密码

客户端注册成功后,客户端开发者会得到一个客户端标识符和一个客户端密码,客户端标识符是所有与授权服务器交互的客户端的唯一标识,是公开的信息,客户端密码是客户端与授权服务器认证时所需,必须私密地保存,客户端密码在某些授权方式下是可以省略的。

协议流程

(A) 客户端向资源所有者请求授权。可以直接向资源所有者发出授权请求(如上图所示),或者更合适的方式是间接地通过授权服务器作为媒介发出请求。

(B)客户端收到授权许可,授权许可是一个代表了资源所有者的授权的凭证,授权过程可以用下面将要描述的四种授权方式之一或者扩展授权类型来表示。

(C)客户端与授权服务器进行认证并出示授权许可来请求访问令牌。

(D) 授权服务器认证客户端并且验证授权许可,如果都是有效的话,就会发放访问令牌给客户端。

(E) 客户端向资源服务器请求受保护的资源并且通过出示访问令牌进行认证。

(F) 资源服务器验证访问令牌,处理包含有效的访问令牌的请求并且返回受保护的资源。

授权方式

授权码

目前应用最广泛也是最安全的方式,一般Web服务器应用都会使用这种授权方式。

授权码授权方式下,授权服务器作为客户端和资源所有者交互的媒介,从而使客户端得到授权码。客户端不是直接向资源所有者请求权,而是将资源所有者通过浏览器(即规范中的user-agent,大多情况下指浏览器,还可能是服务、扩展等,本文用浏览器表示这个概念)导向授权服务器,接着授权服务器认证资源所有者并且获得授权,而后授权服务器又会将资源所有者导回至客户端,并且返回了授权码。因为资源所有者只会向授权服务器认证,所以其凭据信息不会共享给客户端。

(A) 客户端通过把资源所有者的浏览器(user-agent)导向授权端点来启动流程。客户端会(在请求中)附上如下参数:

  • response_type:授权类型,必须设置为“code”,指示授权服务器客户端使用授权码的方式进行授权
  • client_id:客户端标识符,在授权服务器注册应用后得到的唯一标识
  • redirect_uri:重定向URI,可选项。若设置了该参数则必须和客户端注册时提供的重定向URL的值一致,这样做的目的是为了防止某些攻击。一旦访问被允许或者拒绝,授权服务器会将浏览器转向(通过HTTP 302重定向)重定向URI
  • scope:对资源的访问范围,可选项
  • state:本地状态,推荐项。供后续步骤验证使用。若请求中提供了此参数则授权服务器返回授权码的响应中也必须包含该参数并且其值要和请求中的完全一致,此参数的目的是为了防止CSRF攻击

如当在下面客户端网站点击登录页上的使用GitHub登录时,浏览器会发起一个到GitHub的授权端点的请求:

https://github.com/login?response_type=code&client_id=4bceac0b4d39cf045157&scope=user&state=1001&redirect_uri=https://passport.csdn.net/account/login?oauth_provider=GitHubProvider

(B)授权服务器通过浏览器认证资源所有者并且确定资源所有者允许还是拒绝了客户端的访问请求。下图所示的是在授权端点上通过账号和密码的方式认证资源所有者。

 (C) 假设资源所有者允许了访问请求,授权服务器就会使用重定向URI(可以是本次请求包含的或者客户端注册时的URI)使浏览器跳转回客户端。成功状态下的重定向URI包含了以下信息:

  • code:授权码
  • state:本地状态,其值必须同(A)步骤中客户端提供的本地状态值相同

下面是一个重定向URI示例:

https://passport.csdn.net/account/login?oauth_provider=GitHubProvider&code=c2724ced3d15d7eeab65&state=1001

(D) 客户端带上前面步骤收到的授权码向授权服务器的令牌端点请求访问令牌。进行请求时,客户端需要在授权服务器进行身份认证。客户端请求中还包含了曾经用于获取授权码的重定向URI,以用于验证。因为请求和响应中包含了一些敏感信息(如客户端密码和访问令牌),为了安全性考量(主要为了防止被窃取),这一步通常在客户端的后端服务器上完成。完整的请求参数如下:

  • grant_type:授权类型,必须设置为authorization_code,指示授权服务器客户端使用授权码的方式进行授权
  • client_id:客户端标识符,在授权服务器注册应用后得到的唯一标识
  • client_secret:客户端密码,用于授权服务器对客户端的身份验证
  • redirect_uri:重定向URI,若(A)步骤请求中设置了该参数值,则这里的参数值必须与其一致
  • code:在(C)步骤重定向URI中获取到的授权码

请求示例如下:

https://github.com/login/oauth/access_token?grant_type=authorization_code&client_id=4bceac0b4d39cf045157& client_secret=idk88&code=c2724ced3d15d7eeab65&redirect_uri=https://passport.csdn.net/account/login?oauth_provider=GitHubProvider

(E) 授权服务器认证客户端,校验授权码并且保证重定向URI同之前在步骤(C)中用来重定向的URI一致。如果都是有效的话,就会发放访问令牌给客户端,授权服务器返回一个带访问令牌的响应,以及一个可选的刷新令牌。成功的响应包含的参数说明:

  • access_token:访问令牌,由授权服务器颁发并且可以用来访问资源服务器
  • token_type:令牌类型
  • expires_in:可选,访问令牌的存活时间
  • refresh_token:刷新令牌,可选,可以用来以同样的授权方式获取新的访问令牌
  • scope:访问范围,若步骤(A)中客户端的请求中访问范围值相同则可以忽略

下面是以json格式作为响应的一个例子:

     HTTP/1.1 200 OK
     Content-Type: application/json;charset=UTF-8
     Cache-Control: no-store
     Pragma: no-cache

     {
       "access_token":"2YotnFZFEjr1zCsicMWpAA",
       "token_type":"bearer",
       "expires_in":3600,
       "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
       "example_parameter":"example_value"
     }

至此授权完成。之后客户端就可以通过访问令牌访问资源服务器获取之前指定权限范围的资源。下图表示了在步骤(C)跳转到重定向URI时,客户端在后台服务器上完成了步骤(D)和(E)并把从GitHub资源服务器上获取到的用户信息显示在重定向后的页面。

隐式授权

隐式授权是授权码授权流程的简化版本,它对公共类型的客户端的做了优化,这些客户端通常在浏览器中通过脚本语言如JavaScript实现。在隐式授权流程中,客户端没有通过授权码的方式,而是通过认证请求的响应直接接受访问令牌。隐私授权常用在单页应用(Single-Page Apps)中。

当颁发访问令牌时,授权服务器不会对客户端进行认证,因为即使请求中包含了客户端密码,通过浏览器发送也会被暴露。若授权服务器和客户端的存在不安全的连接(如未使用TLS保护),被编码在重定向URI中的访问令牌也可能会被暴露给有权访问资源所有者浏览器的人或应用。

隐式授权流程提高了一些客户端的响应性和效率,因为它简化了获取访问令牌的流程。但是,这种方便性带来的安全隐患也应该要权衡,特别是当授权码授权方式可用的时候。

(A)客户端通过把资源所有者的浏览器导向授权端点来启动流程。客户端会(在请求中)附上客户端标识符,访问范围,本地状态,和重定向URI,一旦访问被准许(或者拒绝)后授权服务器将会把浏览器导回这个URI。

(B)授权服务器通过浏览器认证资源所有者并且确定资源所有者允许还是拒绝了客户端的访问请求。

(C) 假设资源所有者允许了访问请求,授权服务器就会使用重定向URI(可以是本次请求包含的或者客户端注册时的URI)使浏览器跳转回客户端。重定向URI中包含访问令牌 ,以片段标识符(Fragment identifier,#后面的标识符)的形式存在。

(D) 浏览器 跟随转址的指示,发出请求到网络托管的客户端资源服务器 ,URI片段不作为HTTP请求的一部分(因为URI片段设计目的就是只被浏览器使用), 浏览器 自己保留 URI片段 ,避免了网络传输过程中访问令牌被中间路由器或者服务器窃取,这样就很大程度保证了访问令牌的安全性。

(E) 网络托管的客户端资源服务器回传一个网页(通常为嵌入脚本的HTML文档),这个网页不仅可以拿到完整的重定向 URI (含先前浏览器保留的片段),还能把URI 片段里面的访问令牌提取出来。

(F) 浏览器本地执行从网络托管的客户端资源服务器获取的脚本,并提取出访问令牌 。

(G) 浏览器把访问令牌传给客户端。

密码凭证

资源所有者密码凭证(如用户名和密码)可以直接作为一种授权方式去获取访问令牌。这种方式比较适合资源所有者充分信任客户端的情况,如操作系统内建的应用或比较知名的官方应用。授权服务器应该特别注意的是只有当其他授权流程无法使用的时候才能启用这种授权方式。

尽管这种授权方式需要客户端直接访问资源所有者的凭据,但这些凭据仅仅在用来被交换访问令牌的时候使用了一次。通过用凭据交换长期的访问令牌或者刷新令牌 ,客户端就没有保存资源所有者凭据供将来使用的必要。

(A) 资源所有者向客户端提供他的用户名及密码。

(B) 客户端用资源所有者的用户名及密码,向授权服务器令牌端点申请访问令牌。进行请求时,客户端需要在授权服务器进行身份认证。

(C) 授权服务器认证客户端并且验证资源所有者的用户名及密码,如果都是有效的话,就会发放访问令牌。

客户端凭证

当访问范围被限制在受客户端控制的受保护的资源或之前被授权服务器处置过的受保护的资源时,可以使用客户端凭证授权。

(A) 客户端与授权服务器进行认证并且从令牌端点请求访问令牌。

(B) 授权服务器认证客户端,如果认证通过,就会发放访问令牌。

常见攻击

  • 跨站请求伪造(CSRF

攻击者可以利用跨站请求伪造使得受害者浏览器跟随一个恶意的URI(如诱导用户点击的链接、图片等)访问到受信任的服务器(一般通过出示有效的session cookie建立连接)。 针对客户端重定向URI的CSRF攻击,攻击者可以注入自己的授权码或访问令牌,从而导致客户端访问了关联该访问令牌的攻击者的受保护资源而不是受害者的。例如,一个重定向URI指向了S网站(客户端)的同步服务,该服务用来将S网站上用户的照片同步到Google云存储(资源服务器)上,攻击者伪造了包含自身授权码的重定向URI,并诱导用户点击,受害者点击并登录S网站后,会触发S网站使用攻击者的访问令牌调用Google云存储的API进行同步,从而保存在S网站上的受害者的照片被同步到攻击者的Google账户上,导致照片被窃取。

客户端必须对重定向URI实行CSRF保护。一般通过对任何发往重定向端点的请求中包含用户浏览器认证状态来实现(如用来认证浏览器的session cookie的hash值)。在发出授权请求时,客户端应当利用“state”请求参数传递该状态值给授权服务器。授权服务器重定向时,客户端要验证重定向URI中包含的状态值,以确保与之前授权请求包含的状态值一致。

在点击劫持中,攻击者会注册一个合法的客户端,制作一个恶意网站,在这个网站上有一个透明的iframe会载入授权服务器的授权端点网页。iframe会覆盖在一组虚假的按钮之上,并且别有用心地使虚假按钮正好处于授权页面上的重要按钮的下面。当使用者看到这些误导性按钮,并且点击之后,使用者实际上是按下了覆盖在授权页面之上的隐藏按钮(如「给予授权」按钮)。
如此攻击者可以欺骗资源所有者 ,使他们在不知情的情况下授权了客户端的访问。为了防范这种型式的攻击,本机应用程序在请求使用者授权的时候,最好要使用外部浏览器,而非在应用程序中内嵌一个浏览器。对于多数的新浏览器来说,可以设置 “X-Frame-Options” 头信息(非标准) 来强制要求授权服务器的网页不能从 iframe 载入。这个 头信息可以有两种值,分别是 “deny” (禁止任何形式的嵌入)和 “sameorigin” (只允许同源网站页嵌入)。对于旧的浏览器来说,可以用JavaScript Frame-Busting 技术,但是不见得对所有浏览器都有效。

OAuth协议和其他类似协议的广泛使用可能会使用户对被要求在重定后的网站上输入密码的做法习以为常。如果用户在输入凭证信息之前不能认证验证这些网站的真实性,则攻击者可能会利用这一点窃取资源所有者的密码。

服务提供者应当试着教育使用者关于钓鱼攻击的风险,并且应该要提供一种机制来让用户可以很容易就确认网站的真实性。客户端开发者应该考虑它们如何与浏览器(如外部浏览器,嵌入式浏览器)进行交互的安全隐患,以及最终用户验证授权服务器的真实性的能力。

  • 开放重定向器(Open Redirectors)

授权服务器,授权端点和客户端重定向端点可能配置不正确并且作为开放重定向器存在。开放重定向器是一个使用参数自动将用户代理重定向到参数指定的地址的端点,中间没有经过任何验证。

 开放重定向器可用于网络钓鱼攻击,或由攻击者通过使用熟悉且受信任地址的URI权威部分(URI  authority component),让最终用户访问恶意网站。另外,如果授权服务器允许客户端只注册部分重定向URI,攻击者可以使用由客户端操作的开放重定向器来构造重定向URI,此URI可以通过授权服务器验证,但会发送授权码或访问令牌到攻击者控制的端点。

  • 隐式流程中访问令牌的滥用

对于公共客户端(如本机应用程序或完全基于浏览器运行的应用)OAuth规范没有提供方法去判断哪个客户端被发放了访问令牌。

资源所有者可以通过向攻击者的恶意客户端授予访问令牌来自愿地委托对资源的访问。这可能是由于网络钓鱼或其他缘故。攻击者也可能通过其他机制窃取令牌。然后,攻击者可以通过向合法的公共客户端提供访问令牌来尝试模拟资源所有者。

在隐式流程(response_type = token)中,攻击者可以轻松地切换授权服务器响应中的访问令牌,将真实牌替换为之前发给攻击者的那个令牌。

一些本机应用程序依赖在后端通道被传递访问令牌以识别客户端用户,授权服务器与这些本机应用的通信同样可能会被攻击者危害,因为攻击者可能会创建能注入任意被盗访问令牌的恶意应用程序。

假设只有资源所有者可以向资源提供有效的访问令牌的任何公共客户端都容易受到这种类型的攻击。

这种类型的攻击可能会将合法客户端上资源所有者的信息暴露给攻击者(恶意客户端)。这也将允许攻击者在合法客户端执行与原始授予访问令牌或授权码的资源所有者相同的权限。

对客户端使用授权流程作为委托用户认证的形式的任何规范在没有额外安全机制使客户端能够确定访问发行令牌用于其使用的情况下,不得使用隐式授权类型。

  • 代码注入攻击(Code Injection Attacks)

当输入值或是其他外部参数没有被消毒导致应用程式的内部逻辑改变的时候,就是发生了代码注入攻击。这可能会允许攻击者取得应用程序设备或数据的存取权,造成服务阻断 (DoS) ,或是带来大范围的负面影响。

授权服务器必须对任何接收到的值消毒(可以的话还要验证是否正确),特别是 “state” 和 “redirection_uri” 参数。

  • 凭证猜测攻击(Credentials-Guessing Attacks)

授权服务器必须防止攻击者猜测访问令牌、授权码、刷新令牌、资源所有者密码和客户端凭证。

FAQ

1.授权码授权方式中,为什么不直接返回访问令牌,而是中间要经过获取授权码这个环节,授权码的作用是什么?

若直接返回访问令牌,只能在授权服务器成功认证资源所有者后,通过重定向URI附加请求参数的形式返回到用户浏览器,但是因为种种原因(证书花费、配置错误等)无法保证客户端使用了安全的(如HTTPS)连接,所以这种方式可能会直接暴露访问令牌,攻击者很容易进行网络窃听其并从响应中解析出访问令牌。而授权码对于攻击者来说是无用的,因为他们不知道客户端密码进而无法用来交换访问令牌。

2.隐式授权流程中,解析访问令牌的脚本为什么需要从托管的客户端资源服务器下载,这样做有什么好处?

这样做其实是充分利用了授权服务器响应回来的重定向URI,反正浏览器跳转回客户端时要重新请求客户端的资源服务器上HTML页面,所以此时可以顺带把解析URI片段中访问令牌的脚本一块加载到浏览器执行。

参考

https://tools.ietf.org/html/rfc6749

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.