oAuth2.0简介

it2022-05-05  153

OAuth 2.0 是目前比较流行的做法,它率先被Google, Yahoo, Microsoft, Facebook等使用。之所以标注为 2.0,是因为最初有一个1.0协议,但这个1.0协议被弄得太复杂,易用性差,所以没有得到普及。

为了理解OAuth的适用场合,让我举一个假设的例子。

有一个"云冲印"的网站,可以将用户储存在Google的照片,冲印出来。用户为了使用该服务,必须让"云冲印"读取自己储存在Google上的照片。 传统方法是,用户将自己的Google用户名和密码,告诉"云冲印",后者就可以读取用户的照片了。这样的做法有以下几个严重的缺点。

(1)"云冲印"为了后续的服务,会保存用户的密码,这样很不安全。

(2)Google不得不部署密码登录,而我们知道,单纯的密码登录并不安全。

(3)"云冲印"拥有了获取用户储存在Google所有资料的权力,用户没法限制"云冲印"获得授权的范围和有效期。

(4)用户只有修改密码,才能收回赋予"云冲印"的权力。但是这样做,会使得其他所有获得用户授权的第三方应用程序全部失效。

(5)只要有一个第三方应用程序被破解,就会导致用户密码泄漏,以及所有被密码保护的数据泄漏。

协议的参与者

从引言部分的描述我们可以看出,OAuth的参与实体至少有如下4个:

Third-party application:第三方应用程序,本文中又称"客户端"(client)。RO (resource owner): 资源所有者,对资源具有授权能力的人。一般指的是“用户”。RS (resource server): 资源服务器,它存储资源,并处理对资源的访问请求。如Google资源服务器,它所保管的资源就是用户Alice的照片。它与认证服务器,可以是同一台服务器,也可以是不同的服务器。Client: 第三方应用,它获得RO的授权后便可以去访问RO的资源。如网易印像服务。User Agent:用户代理,本文中就是指浏览器。AS (authorization server): 授权服务器,它认证RO的身份,为RO提供授权审批流程,并最终颁发授权令牌(Access Token)。读者请注意,为了便于协议的描述,这里只是在逻辑上把AS与RS区分开来;在物理上,AS与RS的功能可以由同一个服务器来提供服务。

OAuth的作用就是让"客户端"安全可控地获取"用户"的授权,与"服务商提供商"进行互动。

OAuth的思路

OAuth在"客户端"与"服务提供商"之间,设置了一个授权层(authorization layer)。“客户端"不能直接登录"服务提供商”,只能登录授权层,以此将用户与客户端区分开来。"客户端"登录授权层所用的令牌(token),与用户的密码不同。用户可以在登录的时候,指定授权层令牌的权限范围和有效期。

"客户端"登录授权层以后,"服务提供商"根据令牌的权限范围和有效期,向"客户端"开放用户储存的资料。

授权类型

OAuth为了支持这些不同类型的第三方应用,提出了多种授权类型,如:

授权码 (Authorization Code Grant)隐式授权 (Implicit Grant)RO凭证授权 (Resource Owner Password Credentials Grant)Client凭证授权 (Client Credentials Grant)

授权码模式

授权码模式(authorization code)是功能最完整、流程最严密的授权模式。它的特点就是通过客户端的后台服务器,与"服务提供商"的认证服务器进行互动。 (A) web客户端通过将终端用户的user-agent重定向到授权服务器来发起这个流程。客户端传入它的客户端标识符、请求作用域、本地状态和一个重定向URI,在访问被许可(或被拒绝)后授权服务器会重新将终端用户引导回这个URI。 (B) 授权服务器验证终端用户(借助于user-agent),并确定终端用户是否许可客户端的访问请求。 © 假定终端用户许可了这次访问,授权服务器会将user-agent重定向到之前提供的重定向URI上去。授权服务器为客户端传回一个授权码做获取访问令牌之用。 (D) 客户端通过验证并传入上一步取得的授权码从授权服务器请求一个访问令牌。(需要带上ClientId和Secret,ClientId和Secret是通过平台授予) (E) 授权服务器验证客户端私有证书和授权码的有效性并返回访问令牌。

隐授权模式(implicit grant type)

也称为简化模式,简化模式(implicit grant type)不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了"授权码"这个步骤,因此得名。所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证。

User-Agent适用于客户端不能保存客户端私有证书的App(纯客户端App,无Server参与)。因为纯客户端的程序不能保存密钥

(A) 客户端将user-agent引导到终端用户授权endpoint。客户端传入它的客户端标识符、请求作用域、本地状态和一个重定向URI,在访问被许可(或被拒绝)后授权服务器会重新将终端用户引导回这个URI。 (B) 授权服务器验证终端用户(通过user-agent)并确认终端用户是许可还是拒绝了客户端的访问请求。 © 如果终端用户许可了这次访问,那么授权服务器会将user-agent引导到之前提供的重定向URI。重定向URI会在URI片断{译者注:URI片断是指URI中#号之后的内容}中包含访问令牌。 (D) user-agent响应重定向指令,向web服务器发送不包含URI片断的请求。user-agent在本地保存URI片断。 (E) web服务器返回一个web页面(通常是嵌入了脚本的HTML网页),这个页面能够访问完整的重定向URI,它包含了由user-agent保存的URI片断,同时这个页面能够将包含在URI片断中的访问令牌(和其它参数)提取出来。 (F) user-agent在本地执行由web服务器提供的脚本,该脚本提取出访问令牌并将它传递给客户端。

密码模式

密码模式(Resource Owner Password Credentials Grant)中,用户向客户端提供自己的用户名和密码。客户端使用这些信息,向"服务商提供商"索要授权。

在这种模式中,用户必须把自己的密码给客户端,但是客户端不得储存密码。这通常用在用户对客户端高度信任的情况下,比如客户端是操作系统的一部分,或者由一个著名公司出品。而认证服务器只有在其他授权模式无法执行的情况下,才能考虑使用这种模式。

(A)用户向客户端提供用户名和密码。

(B)客户端将用户名和密码发给认证服务器,向后者请求令牌。

(C)认证服务器确认无误后,向客户端提供访问令牌。

客户端模式

客户端模式(Client Credentials Grant)指客户端以自己的名义,而不是以用户的名义,向"服务提供商"进行认证。严格地说,客户端模式并不属于OAuth框架所要解决的问题。在这种模式中,用户直接向客户端注册,客户端以自己的名义要求"服务提供商"提供服务,其实不存在授权问题。 这种模式不需要终端用户的参与,只是Client和Server端的交互。通常只用于Client状态的获取。 (A)客户端向认证服务器进行身份认证,并要求一个访问令牌。

(B)认证服务器确认无误后,向客户端提供访问令牌。

OAuth设计上的安全性考虑

为何引入authorization_code? 协议设计中,为什么要使用authorization_code来交换access_token?这是读者容易想到的一个问题。也就是说,在协议的第3步,为什么不直接将access_token通过重定向方式返回给Client呢?

如果直接返回access_token,协议将变得更加简洁,而且少一次Client与AS之间的交互,性能也更优。那为何不这么设计呢?协议文档[1]中并没有给出这样设计的理由,但也不难分析:

(1) 浏览器的redirect_uri是一个不安全信道,此方式不适合于传递敏感数据(如access_token)。因为uri可能通过HTTP referrer被传递给其它恶意站点,也可能存在于浏览器cacher或log文件中,这就给攻击者盗取access_token带来了很多机会。另外,此协议也不应该假设RO用户代理的行为是可信赖的,因为RO的浏览器可能早已被攻击者植入了跨站脚本用来监听access_token。因此,access_token通过RO的用户代理传递给Client,会显著扩大access_token被泄露的风险。 但authorization_code可以通过redirect_uri方式来传递,是因为authorization_code并不像access_token一样敏感。即使authorization_code被泄露,攻击者也无法直接拿到access_token,因为拿authorization_code去交换access_token是需要验证Client的真实身份。也就是说,除了Client之外,其他人拿authorization_code是没有用的。 此外,access_token应该只颁发给Client使用,其他任何主体(包括RO)都不应该获取access_token。协议的设计应能保证Client是唯一有能力获取access_token的主体。引入authorization_code之后,便可以保证Client是access_token的唯一持有人。当然,Client也是唯一的有义务需要保护access_token不被泄露。

(2) 引入authorization_code还会带来如下的好处。由于协议需要验证Client的身份,如果不引入authorization_code,这个Client的身份认证只能通过第1步的redirect_uri来传递。同样由于redirect_uri是一个不安全信道,这就额外要求Client必须使用数字签名技术 来进行身份认证,而不能用简单的密码或口令认证方式。引入authorization_code之后,AS可以直接对Client进行身份认证(见步骤4和5),而且可以支持任意的Client认证方式(比如,简单地直接将Client端密钥发送给AS)。

在我们理解了上述安全性考虑之后,读者也许会有豁然开朗的感觉,懂得了引入authorization_code的妙处。那么,是不是一定要引入authorization_code才能解决这些安全问题呢?当然不是。

一切只是看上去很美好,但其实很多坑

OAuth2看上去很美好,但是细心观察其实还是有一些漏洞的。

对于授权码和access_token的篡改,在OAuth1中是反复的对Code和Token进行签名,来保证Token不会被篡改,但是OAuth2中却没有,因为OAuth2是基于Https的,所以如果没有Https的支持OAuth2可能还不如OAuth1.

对于redirect_uri的校验,OAuth1中没有提到redirect_uri的校验,那么OAuth2中要求进行redirect_uri的校验。但是如果校验规则过松,也会导致跳转的安全问题。 例如:校验的时候只校验根域名,或者二级域名,但是第三方App对自己的域名保护的不好,导致二级域名被hack那么此时授权码和Token会被窃取。 校验规则不严谨,例如www.baidu.com 但是redirect_uri为:www.a.com.\www.baidu.com,这样授权就被a.com钓走了。 对于CSRF攻击(跨站请求伪造):由于这个授权过程服务器和Client和用户之间有几次交互,但是在得到授权码的时候需要一次回跳,但是这次回跳是可以被阻塞的。

攻击者使用自己的账户申请第三方授权登陆

授权后服务端返回授权码,但是此时组织授权回跳,此时Client并没有接到授权码,也就是阻断了授权流程 攻击者将此跳转链接发给一个正在处于在Client登陆状态的账户 诱骗正常用户点击,那么此时攻击者第三方账户和被攻击账户进行绑定(相当于账户绑定了第三方的账户) 攻击者再次进行第三方授权登陆。这样就劫持了诱骗的账户。转账?删好友?等等就所以搞了。

解决办法:

在进行授权码申请或者是Token申请的时候带上state参数,服务器返回请求时要求携带state参数,在Client处理授权的时候校验此参数。参数可以是当前账户的SessionId,或Cookie的签名串。在Client申请accessToken会验证相关state和当前用户的关系,这样就防止了篡改。 OAuth的校验流程为什么这么复杂,直接授权之后redirect回accessToken不就结了吗?为什么还要返回auth_code之后请求accessToken? 首先,redirect是不安全的,随时可以暂停回调而拿到accessToken,拿到了accessToken也就意味着拿到了授权,但是auth_code是和client相对应的,那么即使拿到了auth_code还需要再次申请accessToken,申请accessToken时需要校验Client和state。同时协议设计的原则就是只有Client能拿到accessToken而用户是拿不到的。

最后,切记HTTPS


最新回复(0)