OAuth是一个关于授权(authorization)的开放网络标准,在全世界得到广泛应用,目前的版本是2.0版。
OAuth(开放授权)是一个开放标准。允许第三方网站在用户授权的前提下访问在用户在服务商那里存储的各种信息。而这种授权无需将用户提供用户名和密码提供给该第三方网站。OAuth允许用户提供一个令牌给第三方网站,一个令牌对应一个特定的第三方网站,同时该令牌只能在特定的时间内访问特定的资源。
在一个单位中,可能是存在多个不同的应用,比如汽车制造企业会有财务的系统,4S店的销售系统,面向车主的论坛系统,还有ERP、OA、CRM系统等等,如果每个系统都用独立的账号认证体系,会给用户带来很大困扰,也给管理带来很大不便。所以需要设计一种统一登录的解决方案。比如我登陆了百度账号,进贴吧时发现已经登录了,进糯米发现也自动登录了。常见的有两种情况,一种是SSO(单点登录)效果是一次输入密码多个网站可以识别在线状态;还有一种是多平台登录,效果是可以用一个账号(比如QQ账号)登录多个不同的网站。
OAuth 2.0的运行流程如下图,摘自RFC 6749。
用户打开客户端以后,客户端要求用户给予授权。 用户同意给予客户端授权。 客户端使用上一步获得的授权,向认证服务器申请令牌。 认证服务器对客户端进行认证以后,确认无误,同意发放令牌。 客户端使用令牌,向资源服务器申请获取资源。 资源服务器确认令牌无误,同意向客户端开放资源。
在应用 OAuth2.0之前,必须在授权方服务中注册应用。平台中要求开发者提供如下所示的授权设置项。
应用唯一ID(client_id)
应用的唯一标示,在服务器中唯一存在的分配给一个应用的ID,是公开透明的字符串,授权方服务使用该字符串来标识应用程序,并且还用于构建呈现给用户的授权 url 。
应用密钥(client_secret)
用于验证应用身份,并且必须在客户端和服务之间保持私有性。一般在临时code换取Token时使用。
重定向URI或回调URL(callback_url)
重定向URI是授权方服务在用户授权(或拒绝)应用程序之后重定向供用户访问的地址,因此也是用于处理授权码或访问令牌的应用程序的一部分。
OAuth2.0有五种授权模式。
(1)授权码模式(Authorization Code)
(2)授权码简化模式(Implicit)
(3)Pwd模式(Resource Owner Password Credentials)
(4)Client模式(Client Credentials)
但不论哪种模式,都是为了从认证服务器获取Access Token,用来访问资源服务器。
而申请Access Token,需要提交相应信息。例如,client_ID(我是谁),response_type或grant_type(申请哪种模式),scope(申请哪些权限,由授权服务器定义),redirect_uri(申请结果跳转至哪儿),state(自定义信息希望服务端原样返回)等。当然不同的模式,提交信息内容也不同,交互的步骤也不同。
授权码模式(authorization code)是功能最完整、流程最严密的授权模式,也是最繁琐最安全的一种模式。它的特点就是通过客户端的后台服务器,与”服务提供商”的认证服务器进行互动。
client向资源服务器请求资源,被重定向到授权服务器
浏览器向资源拥有者索要授权,之后将用户授权发送给授权服务器
授权服务器将授权码转经浏览器发送给client
client拿着授权码向授权服务器索要访问令牌
授权服务器返回AccessToken和RefreshToken给cilent
这种模式是四种模式中最安全的一种模式。一般用于client是Web服务器端应用或第三方的原生App调用资源服务的时候。因为在这种模式中AccessToken不会经过浏览器或移动端的App,而是直接从服务端去交换,这样就最大限度的减小了AccessToken泄漏的风险。
请求认证
第三应用开始进行认证时,直接浏览器调用如下url:
http://www.server.com/oauth2.0/authorize?response_type=code&client_id=
{client_id}&redirect_uri={redirect_uri}&scope={scope}&state={state}
参数说明 | ||
---|---|---|
参数 | 必须 | 描述 |
response_type | 是 | 必需参数。请求的响应中:一个访问令牌、一个授权码,或两者都有。请求访问令牌参数值必须设为“token”,请求授权码参数值必须设为“code”,或者使用参数值为“code_and_token”同时请求两者。授权服务器可能拒绝提供这些响应类型中的一种或多种。 |
client_id | 是 | 必需参数。客户端标识符。 |
redirect_uri | 是 | 必需参数,除非通过其它方式在客户端和授权服务器之间已经确定了一个重定向URI。这是当终端用户的授权步骤完成时授权服务器将要把user-agent重定向到的一个绝对URI。授权服务器应该要求客户端预先注册它们的重定向URI。 |
scope | 否 | 可选参数。访问请求的作用域,以空格隔开的字符串列表来表示。“scope”参数的值由授权服务器定义。如果这个值包含多个空格隔开的字符串,那么它们的顺序不分先后,而且每个字符串都为请求的作用域增加一个新的访问范围。 |
state | 否 | 可选参数。被客户端用来在请求和回调之间维护状态的值,对授权服务器来说是不透明的。授权服务器在将user-agent重定向回客户端时传回这个值。 |
返回说明
客户端通过user-agent使用HTTP重定向响应,或者其它可用的方式,将终端用户引导到构建好的URI上。对于终端用户授权endpoint,授权服务器必须支持HTTP的“GET”方法,也可以支持使用“POST”方法。
正确响应
重定向url格式如下:
redirect_uri?code=CODE&state=STATE
表示由授权服务器产生的授权码。授权码应该在分发后迅速过期,以降低泄露风险。客户端一定不能重用同一个授权码。如果一个授权码被多次使用,授权服务器可能撤销之前基于这个授权码分发的所有令牌。授权码与客户端标识符和重定向URI相绑定。
如果“state”参数在客户端授权请求中存在,则这个参数是必需的。需要精确地设置成从客户端接收到的值。
错误响应
如果终端用户拒绝了访问请求,或者由于除了缺少或无效重定向URI之外的其它原因而导致请求失败,
可选参数。提供额外信息的一段人类可读的文字,用来帮助理解和解决发生的错误。
可选参数。指明了一个人类可读的网页URI,带有关于错误的信息,用来为终端用户提供与错误有关的额外信息。
如果“state”参数在客户端授权请求中存在,则这个参数是必需的。需要精确地设置成从客户端接收到的值。
通过code获取accessToken
http://www.server.com/oauth2.0/accessToken?client_id={client_id}&client_secret={client_secret}&code={code}&grant_type=authorization_code&redirect_uri={redirect_uri}
参数说明 | ||
---|---|---|
参数 | 必须 | 描述 |
client_id | 是 | 必需参数。客户端标识符。 |
client_secret | 是 | 必需参数。包含客户端私有证书。 |
grant_typ | 是 | 必需参数。在请求中所包含的访问许可类型。它的值必须是“authorization_code” |
code | 是 | 必需参数。从授权服务器接收到的授权码。 |
redirect_uri | 是 | 必需参数。在最初请求中使用的重定向URI。 |
客户端通过user-agent使用HTTP重定向响应,或者其它可用的方式,将终端用户引导到构建好的URI上。对于终端用户授权endpoint,授权服务器必须支持HTTP的“GET”方法,也可以支持使用“POST”方法。
正确响应
由授权服务器分发的访问令牌。
分发的令牌类型。令牌类型告诉客户端一个信息,即当访问一个受保护资源时访问令牌应该如何被使用。
访问令牌生命周期的秒数。例如,“3600”表示自响应被授权服务器产生的时刻起,访问令牌将在一小时后过期。
错误响应
如果终端用户拒绝了访问请求,或者由于除了缺少或无效重定向URI之外的其它原因而导致请求失败,
错误码
请求缺少某个必需参数,包含一个不支持的参数或参数值,参数重复,包含多个私有证书,使用了多种验证客户端的机制,或者请求格式不正确。
提供的客户端标识符是无效的,客户端验证失败,客户端不包含私有证书,提供了多个客户端私有证书,或使用了不支持的证书类型。
经过验证的客户端没有权限使用提供的访问许可类型。
提供的访问许可是无效的、过期的或已撤销的(例如,无效的断言,过期的授权令牌,错误的终端用户密码证书,或者不匹配的授权码和重定向URI)。
包含的访问许可——它的类型或其它属性——不被授权服务器所支持。
请求的作用域是无效的、未知的、格式不正确的,或超出了之前许可的作用域。
可选参数。提供额外信息的一段人类可读的文字,用来帮助理解和解决发生的错误。
可选参数。指明了一个人类可读的网页URI,带有关于错误的信息,用来为终端用户提供与错误有关的额外信息。
通过前端渠道客户获取授权码 通过后端渠道,客户使用authorization code去交换AccessToken和可选的RefreshToken 假定资源拥有者和客户在不通的设备上或域名上 最安全的流程,AccessToken不会传递到或经过user-agent(浏览器前端)
在授权码模式中,Authorization Code和Access Token都由授权服务器生成和验证,而最终只用到Access Token,这让Authorization Code显得无足轻重。因此,授权码简化模式,去掉了Authorization Code的申请流程,从而通过User-Agent(Browser)直接申请Access Token。简化了请求步骤比授权码模式少一步操作,在返回code时改为了直接返回Token。
请求认证
第三应用开始进行认证时,直接浏览器调用如下url:
http://www.server.com/oauth2.0/authorize?response_type=token&client_id=
{client_id}&redirect_uri={redirect_uri}&scope={scope}&state={state}
参数说明 | ||
---|---|---|
参数 | 必须 | 描述 |
response_type | 是 | 必需参数。请求的响应中:一个访问令牌、一个授权码,或两者都有。请求访问令牌参数值必须设为“token”,请求授权码参数值必须设为“code”,或者使用参数值为“code_and_token”同时请求两者。授权服务器可能拒绝提供这些响应类型中的一种或多种。 |
client_id | 是 | 必需参数。客户端标识符。 |
redirect_uri | 是 | 必需参数,除非通过其它方式在客户端和授权服务器之间已经确定了一个重定向URI。这是当终端用户的授权步骤完成时授权服务器将要把user-agent重定向到的一个绝对URI。授权服务器应该要求客户端预先注册它们的重定向URI。 |
scope | 否 | 可选参数。访问请求的作用域,以空格隔开的字符串列表来表示。“scope”参数的值由授权服务器定义。如果这个值包含多个空格隔开的字符串,那么它们的顺序不分先后,而且每个字符串都为请求的作用域增加一个新的访问范围。 |
state | 否 | 可选参数。被客户端用来在请求和回调之间维护状态的值,对授权服务器来说是不透明的。授权服务器在将user-agent重定向回客户端时传回这个值。 |
返回说明
客户端通过user-agent使用HTTP重定向响应,或者其它可用的方式,将终端用户引导到构建好的URI上。对于终端用户授权endpoint,授权服务器必须支持HTTP的“GET”方法,也可以支持使用“POST”方法。
正确响应
重定向url格式如下:
redirect_uri?access_token=2YotnFZFEjr1zCsicMWpAA
&state=xyz&token_type=example&expires_in=3600
由授权服务器分发的访问令牌。
分发的令牌类型。令牌类型告诉客户端一个信息,即当访问一个受保护资源时访问令牌应该如何被使用。
访问令牌生命周期的秒数。例如,“3600”表示自响应被授权服务器产生的时刻起,访问令牌将在一小时后过期。
错误响应
如果终端用户拒绝了访问请求,或者由于除了缺少或无效重定向URI之外的其它原因而导致请求失败,
可选参数。提供额外信息的一段人类可读的文字,用来帮助理解和解决发生的错误。
可选参数。指明了一个人类可读的网页URI,带有关于错误的信息,用来为终端用户提供与错误有关的额外信息。
如果“state”参数在客户端授权请求中存在,则这个参数是必需的。需要精确地设置成从客户端接收到的值。
适用于公开的浏览器单页应用 AccessToken直接从授权服务器返回(只有前端渠道) 不支持refreshTokens 假定资源所有者和公开客户应用在同一个设备上 最容易受安全攻击
密码模式是用户直接将自己的用户名密码交给client,client用用户的用户名密码直接换取AccessToken。
用户将认证密码发送给client
client拿着用户的密码向授权服务器请求Access Token
授权服务器将Access Token和Refresh Token发送给client
这种模式十分简单,但是却意味着直接将用户敏感信息泄漏给了client,因此这就说明这种模式只能用于client是我们自己开发的情况下。因此密码模式一般用于我们自己开发的,第一方原生App或第一方单页面应用。
| Request:POST /token HTTP/1.1Host: server.example.comContent-Type: application/x-www-form-urlencoded client_id=XXXX&grant_type=password&username=user&password=pwd | | —- |
Response:{“access_token”:”2YotnFZFEjr1zCsicMWpAA”,”token_type”:”example”,”expires_in”:3600} |
---|
使用用户名密码登录的应用,例如桌面App 使用用户名/密码作为授权方式从授权服务器上获取accessToken 一般不支持refreshToken 假定资源拥有者和公开客户在相同设备上
这是一种最简单的模式,只要client请求,我们就将AccessToken发送给它。
client向授权服务端发送自己的身份信息,并请求AccessToken。
服务端确认client信息无误后,将AccessToken发送给client。
这种模式是最方便但最不安全的模式。因此这就要求我们对client完全的信任,而client本身也是安全的。因此这种模式一般用来提供给我们完全信任的服务器端服务,在这个过程中不需要用户的参与。
| Request:POST /token HTTP/1.1Host: server.example.comAuthorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JWContent-Type: application/x-www-form-urlencoded client_id=XXXX&grant_type=client_credentials | | —- |
Response:{“access_token”:”2YotnFZFEjr1zCsicMWpAA”,”token_type”:”example”,”expires_in”:3600} |
---|
适用于服务器间通信场景 只有后端渠道,使用客户凭证获取一个access token 因为客户凭证可以使用对称或者非对称加密,该方式支持共享密码或者证书
在OAuth2早期的时候爆发过不少相关的安全方面的漏洞,其实仔细分析后会发现大都都是没有严格遵循OAuth2的安全相关的指导造成的,相关的漏洞事件百度以下就有了。
其实OAuth2在设计之初是已经做了很多安全方面的考虑,并且在RFC6749中加入了一些安全方面的规范指导。
要求Authorization server进行有效的Client验证,比如对clientID和callback地址的校验; client_serect,access_token,refresh_token,code等敏感信息的安全存储(不得泄露给第三方)、传输通道的安全性(TSL的要求); 维持refresh_token和第三方应用的绑定,刷新失效机制的设计不允许长期有效的token存在; 维持Authorization Code和第三方应用的绑定,这也是state参数为什么是推荐的一点,以防止CSRF; 保证上述各种令牌信息的不可猜测行,以防止被猜测得到; 服务提供者应尝试教育最终用户有关钓鱼攻击构成的风险,并且应该为最终用户提供使确认它们的站点的真伪变得简单的机制。 授权服务器必须为它的授权端点实现CSRF保护并且确保在资源所有者未意识到且无显式同意时恶意客户端不能获得授权。