OAuth 2.0 开放受权的那些事儿

OAuth 2.0 协议是一种三方受权协议,目前大部分的第三方登陆与开放受权都是基于该协议的标准或改进实现。OAuth 1.0 版本于 2007 年发布,2.0 版本则在 2011 年发布,其中 2.0 版本取消了全部 token 的加密过程,并简化了受权流程,但因强制使用 HTTPS 协议,被认为安全性高于以前的版本。html

项目地址: https://github.com/plotor/oau...

一. 小场景带你感觉 OAuth 2.0 的交互过程

为了让你对 OAuth 2.0 协议有一个总体上的感知,这里先设置一个小场景对协议的交互过程进行模拟。话说阿冰在花果山上有几亩果园,种了各类各样的水果,有苹果、荔枝、西瓜等等,并由管理员老王进行看管。git

夏天到了,果园里的的水果涨势喜人,阿冰的好朋友小桂子想去果园摘一些西瓜和荔枝解解馋,因而小桂子提着果篮吭哧吭哧就跑到了花果山,结果被管理员老王一把拦住,呵斥道:“要摘水果,必须通过阿冰的赞成,快出示相关凭证”,小桂子固然没有,还纳闷去哪搞什么鬼凭证。这时候老王拎着小桂子来到了一个茅草房前,只见上面写着“花果山街道办事处”。老王告诉小桂子这里能够开具凭证。github

小桂子来到柜台前,业务人员询问了其姓名,并要求出示身份证件。核实身份以后,业务人员询问小桂子要去谁的果园,采摘什么水果?小桂子如实答复,不一会只见业务人员打印出了一张凭证,并将其丢入一个“时光通道”,凭证上写着:算法

小桂子请求在您的果园采摘如下水果:
- 采摘您的西瓜 2 个
- 采摘您的荔枝 3 斤

通道那头的阿冰收到凭证以后盖上本身的印章以示赞成,而后将其投回了“时光通道”。最终小桂子拿到了通过阿冰受权的凭证,跳着奔向果园。管理员老王验证了小桂子出示的凭证,并摘了 2 个西瓜和 3 斤荔枝交到了小桂子手上。json

一个星期后,小桂子嘴又馋了,拿着上次的凭证直奔花果山,到了果园门口又被老王拦住了,老王说:“你这凭证已通过期了,必须再次请求阿冰受权”。小桂子满脸委屈,嘟囔着:“不就想吃俩西瓜嘛,怎么就管的这么严”。浏览器

小编不才,实在编不下去了,就这样结束吧。。。缓存

在这个故事中,凭证限制了小桂子是否能够得到水果,以及得到哪些水果,每种水果多少斤。此外凭证还具有生命周期,超出凭证范围的请求都会被老王拒绝。经过凭证,小桂子能够获取到本身想要的水果,阿冰也不须要亲临花果山,在作好果园管理的同时,又不影响阿冰的生活。安全

回到正题,对于 OAuth 2.0 协议(如下简称 OAuth 协议),我相信大部分读者都有所接触,最多见的就是使用微信登陆第三方应用。的确,OAuth 协议被普遍应用于第三方受权登陆中,借助第三方登陆可让用户免于再次注册之苦,支持第三方登陆也对这些网站、APP 起到了积极的做用,免去了复杂的注册过程,用户体验更佳。这样在提升留存率的同时,也更加易于收集用户的一些非敏感信息等,另外还能够借助一些社交类的第三方账号进行站点推广。服务器

二. OAuth 2.0 中的基本概念与受权流程

做为第三方登陆服务提供方,其核心矛盾点是 既要让用户在对接服务的 APP 上完成登陆,同时还不能让该 APP 拿到用户的密码凭证 。解决这一矛盾的利器就是 token(中文译为令牌),而 OAuth 协议的最终目的就是给第三方应用下发 token,它记录了用户的登陆或受权状态。经过将 token 下发给第三方应用,既能让 APP 登陆并拿到用户许可的数据,也能够将用户的密码凭证紧紧拽在服务本身手里。微信

上面的论述可能侧重了第三方登陆,实际上登陆只是一个受权的过程,OAuth 2.0 协设计的目的在于开放受权。对于一个应用,其最终目的仍是在于拿到用户存储在资源服务器上的用户数据,因此登陆受权还只是第一步,后续 APP 还须要携带 token 去资源服务器请求用户数据,这个时候是一个鉴权的过程,OAuth 协议的主要目的在于受权,至于鉴权,实现上主要是仍是对请求传递过来的 token 进行解析和验证,这一块相对要简单一些,因此本文主要讲解 OAuth 受权的过程。

2.1 OAuth 2.0 定义的 4 种角色

  • 资源全部者(resource owner)

受保护资源所属的实体,好比资源的全部人,下文的用户即为资源全部者。

  • 资源服务器(resource server)

托管受保护资源的服务器,可以响应持有访问令牌的资源访问请求,能够与受权服务器是同一台服务器,也能够分开。

  • 客户端(client)

客户端是 OAuth 服务的接入方,其目的是但愿请求用户存储在资源服务器上的受保护资源。

  • 受权服务器(authorization server)

受权服务器的主要职责是验证资源全部者的身份,并依据资源全部者的许可向客户端下发访问令牌。

在以前的故事中,果园中的水果就是资源,而资源全部者是阿冰,果园能够看作是资源服务器,小桂子就是客户端,而街道办事处是整个流程的受权中心,也就是上面的受权服务器。

2.2 基本概念

2.2.1 访问令牌(access token)

还记得故事中老王问小桂子要的凭证吗?凭证限制了小桂子只能摘 2 个西瓜和 3 斤荔枝,而且凭证仍是具有生命周期的,一个星期以后小桂子再拿着过时的凭证老王也不认了。

实际上故事中的凭证对应的是 OAuth 2.0 中的访问令牌,访问令牌是在用户受权许可下,受权服务器下发给客户端的一个受权凭证,该令牌所要表达的意思是“ 用户授予该 APP 在多少时间范围内容许访问哪些与本身相关的服务或数据 ”,因此访问令牌主要在 时间范围权限范围 两个维度进行控制,此外访问令牌对于客户端来讲是非透明的,外在表现就是一个字符串,客户端没法知晓字符串背后所隐藏的用户信息,所以不用担忧用户的密码凭证会所以泄露。

2.2.2 刷新令牌(refresh token)

故事中小桂子最后之因此以为委屈是由于意识到本身须要再重复走一次受权过程,这让小桂子以为很麻烦,专业点说就是用户体验太差,解决之道就是引入刷新令牌。

刷新令牌的做用在于更新访问令牌,访问令牌的生命周期通常较短,这样能够保证在发生访问令牌泄露时,不至于形成太坏的影响,可是访问令牌有效期设置过短致使的反作用就是用户须要频繁受权,虽然能够经过必定的机制进行静默受权,可是频繁的调用受权接口之于受权服务器也是一种压力。此时能够在下发访问令牌的同时下发一个刷新令牌,刷新令牌的生命周期明显长于访问令牌,这样在访问令牌失效时,能够利用刷新令牌去受权服务器换取新的访问令牌,不过协议对于刷新令牌没有强制规定,是否须要该令牌客户端能够自行选择。

2.2.3 回调地址(redirect uri)

OAuth 2.0 是一类基于回调的受权协议,以受权码模式为例,整个受权须要分为两步进行,第一步下发受权码,第二步根据受权码请求受权服务器下发访问令牌。OAuth 在第一步下发受权码时,是将受权码以参数的形式添加到回调地址后面,并以 302 跳转的形式进行下发,这样简化了客户端的操做,不须要再主动去触发一次请求,便可进入下一步流程。

回调的设计存在必定的安全隐患,坏人能够利用该机制引导用户到一个恶意站点,继而对用户发起攻击。对于受权服务器而言,也存在必定的危害,坏人能够利用该机制让受权服务器变成“肉鸡”,以受权服务器为代理请求目标地址,这样在消耗受权服务器资源的同时,也对目标地址服务器产生 DDOS 攻击。

为了不上述安全隐患,OAuth 协议强制要求客户端在注册时填写本身的回调地址,其目的是为了让回调请求可以到达客户端本身的服务器,从而能够走获取访问令牌的流程。客户端能够同时配置多个回调地址,并在请求受权时携带一个地址,服务器会验证客户端传递上来的回调地址是否与以前注册的回调地址相同,或者前者是后者集合的一个元素,只有在知足这一条件下才容许下发受权码,同时协议还要求两步请求客户端携带的回调地址必须一致,经过这些措施来保证回调过程可以正常到达客户端本身的服务器,并继续后面拿受权码换取访问令牌的过程。

2.2.4 权限范围(scope)

访问令牌自带过时时间,能够在时间维度上对受权进行控制,而在权限范围上,OAuth 引入了一个 scope 的概念。Scope 能够看作是一个对象,包含一个权限的 ID,名称,以及描述信息等,好比 “获取您的基本资料(头像、昵称)”。应该在接入 OAuth 服务时必须向服务提供方申请相应的 scope,并在请求受权时指明该参数,这些权限在用户确认受权时,必须毫无保留的展现给用户,以让用户知道本次请求须要访问用户的哪些数据或服务。

在以前的故事中凭证容许小桂子只能摘取 2 个西瓜和 3 斤荔枝,这里就对应两个 scope,这些信息是写入到凭证(访问令牌)中的,从而限制凭证的权限范围。

2.3 基本受权流程

OAuth 2.0 协议定义了 4 种受权模式,其中最具表明性的是受权码模式,咱们将在 3.1 节中详细介绍,这里先简单体会一下 OAuth 2.0 的受权流程,交互时序图以下:

基本交互流程

假设整个流程开始以前,用户已经登陆,那么整个受权流程以下:

  1. 客户端请求资源全部者(用户)受权,通常都是由受权服务器进行引导
  2. 资源全部者实施受权(采用 4 种受权模式中的一种),客户端拿到用户的受权凭证
  3. 客户端携带用户受权凭证请求受权服务器下发访问令牌
  4. 受权服务器验证客户端出示的受权凭证,并下发访问令牌
  5. 客户端携带访问令牌请求存储在资源服务器上的用户受保护资源
  6. 资源服务器验证客户端出示的访问令牌,经过则响应客户端的请求

整个过程当中,客户端都没法接触到用户的密码凭证信息,客户端经过访问令牌请求受保护资源,用户能够经过对受权操做的控制来间接控制客户端对于受保护资源的访问权限范围和周期。

三. 四种受权模式

OAuth 2.0 相对于 1.0 版本在受权模式上作了更多的细化,已定义的受权模式分为四种:

  1. 受权码模式(Authorization Code Grant)
  2. 隐式受权模式(Implicit Grant)
  3. 资源全部者密码凭证模式(Resource Owner Password Credentials Grant)
  4. 客户端凭证模式(Client Credentials Grant)

3.1 受权码受权模式(Authorization Code Grant)

受权码模式在整个受权流程上与 1.0 版本最为贴近,可是流程上仍是要简化许多,也是 OAuth 2.0 中最标准,应用最为普遍的受权模式。这类受权模式很是适用于具有服务端的应用,固然如今大多数 APP 都有本身的服务端,因此受权码模式拥有最普遍的应用场景,下图为受权码各个角色之间的交互时序:

受权码模式

整个受权流程说明以下(具体参数释义见下文):

  1. 客户端携带 client_id, scope, redirect_uri, state 等信息请求受权服务器下发 code
  2. 受权服务器验证客户端身份,经过则询问用户是否赞成受权(此时会跳转到用户可以直观看到的受权页面,等待用户点击确认受权)
  3. 假设用户赞成受权,此时受权服务器会将 code 和 state 拼接在 redirect_uri 后面,并以 302 形式下发 code
  4. 客户端携带 code, redirect_uri, 以及 client_secret 请求受权服务器下发 access_token
  5. 受权服务器验证客户端身份,同时验证 code,以及 redirect_uri 是否与第一步相同,经过则下发 access_token,并选择性下发 refresh_token

3.1.1 获取受权码

受权码是受权流程的一个中间临时凭证,是对用户确认受权这一操做的一个短暂性表征,其生命周期通常较短,协议建议最大不要超过 10 分钟,在这一有效时间内,客户端能够经过受权码去受权服务器请求换取访问令牌,受权码应该采起防重放措施。

请求参数说明:

名称 是否必须 描述信息
response_type 必须 对于受权码模式来讲 response_type=code
client_id 必须 客户端 ID,用于标识一个客户端,在注册应用时生成
redirect_uri 可选 受权回调地址,具体参见 2.2.3 小节
scope 可选 权限范围,用于对客户端的权限进行控制,若是客户端没有传递该参数,那么服务器则以该应用被许可的全部权限代替
state 推荐 用于维持请求和回调过程当中的状态,防止 CSRF攻击,服务器不对该参数作任何处理,若是客户端携带了该参数,则服务器在响应时原封不动的进行返回

请求参数示例:

GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1  
Host: server.example.com

客户端携带上述参数请求受权服务器,受权服务器会验证客户端的身份以及相关参数,并在确认用户已登陆的前提下弹出受权页询问用户是否赞成受权,若是用户赞成则会将受权码(code)和 state 信息添加到回调地址后面,并以 302 的形式下发。

成功响应参数说明:

名称 是否必须 描述信息
code 必须 受权码,受权码表明用户确认受权的暂时性凭证,推荐最大生命周期不超过 10 分钟
state 可选 若是客户端传递了该参数,则必须原封不动返回

成功响应示例:

HTTP/1.1 302 Found
Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz

若是请求参数错误,或者服务器端响应错误,那么须要将错误信息添加在回调地址后面,一样以 302 形式下发(回调地址错误,或客户端标识无效除外)。

错误响应参数说明:

名称 是否必须 描述信息
error 必须 错误代码
error_description 可选 具有可读性的错误描述信息
error_uri 可选 错误描述信息页面地址
state 可选 若是客户端传递了该参数,则必须原封不动返回

错误响应示例:

HTTP/1.1 302 Found
Location: https://client.example.com/cb?error=access_denied&state=xyz

3.1.2 下发访问令牌

受权服务器的受权端点在以 302 形式下发 code 以后,用户 User-Agent,好比浏览器,将携带对应的 code 回调请求用户指定的 redirect_url,这个地址应该可以保证请求打到应用服务器的对应接口,该接口能够由此拿到 code,并附加相应参数请求受权服务器的令牌端点,受权端点验证 code 和相关参数,验证经过则下发 access_token。

请求参数说明:

名称 是否必须 描述信息
grant_type 必须 对于受权码模式 grant_type=authorization_code
code 必须 上一步骤获取的受权码
redirect_uri 必须 受权回调地址,具体参见 2.2.3 小节,若是上一步有设置,则必须相同
client_id 必须 客户端 ID,用于标识一个客户端,在注册应用时生成

若是在注册应用时有下发客户端凭证信息(client_secret),那么客户端必须携带该参数以让受权服务器验证客户端的真实性。针对客户端凭证须要多说的一点就是不能将其存储或传递到客户端,客户端没法保证 client_secret 的安全,应该始终将其存储在应用的服务器端,当下发受权码回调请求到应用服务器时,在服务器端携带上 client_secret 继续请求下发令牌。

请求参数示例:

POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb

受权服务器须要验证客户端的真实性,以及是否与以前请求受权码的客户端属同一个(请求受权时的信息能够记录在受权码中,或以受权码为 key 创建缓存),受权服务器还要保证受权码处于生命周期内,且只能被使用一次。验证经过以后,受权服务器生成 access_token,并选择性下发 refresh_token,OAuth 2.0 协议明确了 token 的下发策略,对于 token 的生成策略没有作太多说明,不过相关 RFC 补充文档为生成 token 提供了指导,目前主要的 token 有 BEARER、MAC 等类型。

成功响应参数说明:

名称 是否必须 描述信息
access_token 必须 访问令牌
token_type 必须 访问令牌类型,好比 BEARER、MAC 等
expires_in 推荐 访问令牌的生命周期,以秒为单位,表示令牌下发后多久时间过时,若是没有指定该项,则使用默认值
refresh_token 可选 刷新令牌,选择性下发,参见 2.2.2
scope 可选 权限范围,若是最终下发的访问令牌对应的权限范围与实际应用指定的不一致,则必须在下发访问令牌时用该参数指定说明

最后访问令牌以 JSON 格式响应,并要求指定响应首部 Cache-Control: no-storePragma: no-cache

成功响应示例:

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
    "access_token":"2YotnFZFEjr1zCsicMWpAA",
    "token_type":"example",
    "expires_in":3600,
    "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
    "example_parameter":"example_value"
}

错误响应参数说明:

名称 是否必须 描述信息
error 必须 错误代码
error_description 可选 具有可读性的错误描述信息
error_uri 可选 错误描述信息页面地址

错误响应示例:

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

{
    "error":"invalid_request"
}

3.1.3 对于受权码模式的一点小感悟

受权码受权模式是 OAuth 2.0 协议已定义 4 种模式中最严谨的模式,其他 3 中模式都是创建在一些特殊场景下,并对这些场景作了一些妥协和优化。受权码受权流程分为两步走,将用户受权与下发访问令牌分开,这给受权带来了更多的灵活性,正常受权过程当中必须通过用户登陆这一步骤,在用户已登陆的前提下,能够直接询问用户是否赞成受权,可是在一些场景下,好比内部走 SSO(单点登陆)的应用集成了基于 OAuth 受权的第三方应用,这个时候在 OAuth 受权登陆第三方应用时,用户体验较好的流程是不须要用户再次输入用户名和密码的,这就须要将外围 APP 的登陆态传递给嵌套的应用,可是这样是存在安全问题的,用户的登陆态必须把握在走 SSO 登陆流程的应用手上,这样的场景下受权码受权模式的两步走流程就能够知足在不交出用户登陆态的状况下,无需再次登陆便可受权。

内部应用能够拿着第三方应用的 client_id 等信息代替第三方应用去请求获取 code,由于本身持有用户的登陆态,因此过程当中无需用户再次输入用户名和密码,拿到 code 以后将其交给第三方应用,第三方应用利用 code 和本身的 client_secret 信息去请求受权服务器下发 token,整个流程内部应用不须要交出本身持有的用户登陆态,第三方应用也无需交出本身的 client_secret 信息,最终却可以实如今保护用户密码凭证的前提下无需再次登陆便可完成整个受权流程。

3.2 隐式受权模式(Implicit Grant)

对于一些纯客户端应用,每每没法妥善的保管其客户端凭证,同时由于没有服务器端,因此没法向受权服务器传递凭证信息,而且纯客户端应用在请求交互上要弱于有服务器的应用,这时候减小交互可让应用的稳定性和用户体验更好,隐式受权模式是对这一应用场景的优化。

隐式受权模式在安全性上要弱于受权码模式,由于没法对当前客户端的真实性进行验证,同时对于下发的 access_token 存在被同设备上其它应用窃取的风险,为了下降这类风险,隐式受权模式强制要求不能下发 refresh_token,这一强制要求的另一个考量是由于 refresh_token 的生命周期较长,而客户端没法安全的对其进行存储和保护。下图为受权码各个角色之间的交互时序:

隐式受权

整个受权流程说明以下:

  1. 客户端携带 client_id, scope, redirect_uri, state 等信息请求受权服务器下发 access_token
  2. 受权服务器验证客户端身份,经过则询问用户是否赞成受权(此时会跳转到用户可以直观看到的受权页面,等待用户点击确认受权)
  3. 假设用户赞成受权,此时受权服务器会将 access_token 和 state 等信息以 URI Fragment 形式拼接在 redirect_uri 后面,并以 302 形式下发
  4. 客户端利用脚本解析获取 access_token

3.2.1 请求获取访问令牌

不一样于受权码模式的分两步走,隐式受权码模式一步便可拿到访问令牌。

请求参数说明:

名称 是否必须 描述信息
response_type 必须 对于受权码模式 response_type=token
client_id 必须 客户端 ID,用于标识一个客户端,在注册应用时生成
redirect_uri 可选 受权回调地址,具体参见 2.2.3 小节
scope 可选 权限范围,用于对客户端的权限进行控制,若是客户端没有传递该参数,那么服务器则以该应用被许可的全部权限代替
state 推荐 用于维持请求和回调过程当中的状态,防止 CSRF攻击,服务器不对该参数作任何处理,若是客户端携带了该参数,则服务器在响应时原封不动的返回

请求参数示例:

GET /authorize?response_type=token&client_id=s6BhdRkqt3&state=xyz&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: server.example.com

成功响应参数说明:

名称 是否必须 描述信息
access_token 必须 访问令牌
token_type 必须 访问令牌类型,好比 BEARER,MAC 等
expires_in 推荐 访问令牌的生命周期,以秒为单位,表示令牌下发后多久时间过时,若是没有指定该项,则使用默认值
scope 可选 权限范围,若是最终下发的访问令牌对应的权限范围与实际应用指定的不一致,则必须在下发访问令牌时用该参数指定说明
state 可选 若是客户端传递了该参数,则必须原封不动返回

隐式受权模式不下发刷新令牌,访问令牌以 URI Fragment 的形式拼接在受权回调地址后面以 302 形式下发,并要求指定响应首部 Cache-Control: no-storePragma: no-cache

成功响应示例:

HTTP/1.1 302 Found
Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA&state=xyz&token_type=example&expires_in=3600

错误响应参数说明:

名称 是否必须 描述信息
error 必须 错误代码
error_description 可选 具有可读性的错误描述信息
error_uri 可选 错误描述信息页面地址
state 可选 若是客户端传递了该参数,则必须原封不动返回

受权服务器将上述元素以 URI Fragment 形式拼接在受权回调地址后面以 302 形式下发(redirect_uri 或 client_id 错误除外)。

错误响应参数示例:

HTTP/1.1 302 Found
Location: https://client.example.com/cb#error=access_denied&state=xyz

3.3 资源全部者密码凭证受权模式(Resource Owner Password Credentials Grant)

资源全部者密码凭证受权模式创建在资源全部者充分信任客户端的前提下,由于该模式客户端能够拿到用的登陆凭证,从而在用户无感知的状况下完成整个受权流程,毕竟都有用户的登陆凭证了,再弹窗让用户确认受权也是画蛇添足。

这里可能有一个比较疑惑的地方是既然已经拿到了用户的登陆凭证,为何还须要绕一大圈子走 OAuth 受权,拿到令牌再去请求用户的受保护资源呢?实际中事情可能并不会这么简单,拿到用户登陆凭证的不必定是用户自己,并且这里协议指的用户登陆凭证是用户的用户名和密码,实际中还能够是走 SSO 登陆下发的 token,token 在持有权限上要小于等于用户的用户名和密码,这是从客户端角度出发。对于资源服务器来讲,有些敏感数据须要在用户级别作权限控制,对于服务级别的控制粒度太粗,因此这些服务每每须要服务携带 access_token 来请求某一个用户的敏感数据。

举个例子来讲,好比有一个服务是获取某个用户的通信录,这是用户十分敏感的数据,且通常只能授予内部应用,若是是在服务级别进行控制,那么只要拿到服务权限,该应用能够请求获取任何一个用户的通信录数据,这是一件十分危险的事情。若是基于 access_token 作鉴权,那么就能够将粒度控制在用户级别,前面讲的两种受权方式在这里应用时都有一个共同的缺点,须要弹出受权页让用户确认受权,要知道这样的场景每每是发生在内部应用里面,内部应用是能够持有用户登陆态的,这里的确认受权对于一个用户体验好的 APP 来讲就应该发生在用户登陆时,经过用户协议等方式直接告诉用户,从而让用户在一次登陆过程当中可让应用拿到用户的登陆态和访问令牌。资源全部者密码凭证受权模式的交互时序以下:

资源全部者凭证

整个受权流程说明以下:

  1. 用于授予客户端登陆凭证(好比用户名和密码信息,亦或是 token)
  2. 客户端携带用户的登陆凭证和 scope 等信息请求受权服务器下发 access_token
  3. 受权服务器验证用户的登陆凭证和客户端信息的真实性,经过则下发 access_token,并选择性下发 refresh_token

3.3.1 用户授予登陆凭证

用于登陆凭证如何传递给客户端这一块协议未作说明,实际中该类受权通常用于内部应用,这类应用的特色就是为用户提供登陆功能,当用户登陆以后,这类应用也就持有了用户的登陆态,能够是用户登陆的 session 标识,也能够是走 SSO 下发的 token 信息。

3.3.2 请求获取访问令牌

请求参数说明:

名称 是否必须 描述信息
grant_type 必须 对于本模式 grant_type=password
username 必须 用户名
password 必须 用户密码
scope 可选 权限范围,若是最终下发的访问令牌对应的权限范围与实际应用指定的不一致,则必须在下发访问令牌时用该参数指定说明

若是在注册应用时有下发客户端凭证信息(client_secret),那么客户端必须携带该参数以让受权服务器验证客户端的真实性。

请求参数示例:

POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=password&username=johndoe&password=A3ddj3w

成功响应参数说明:

名称 是否必须 描述信息
access_token 必须 访问令牌
token_type 必须 访问令牌类型,好比 BEARER、MAC 等
expires_in 推荐 访问令牌的生命周期,以秒为单位,表示令牌下发后多久时间过时,若是没有指定该项,则使用默认值
refresh_token 可选 刷新令牌,选择性下发,参见 2.2.2
scope 可选 权限范围,若是最终下发的访问令牌对应的权限范围与实际应用指定的不一致,则必须在下发访问令牌时用该参数指定说明

最后访问令牌以 JSON 格式响应,并要求指定响应首部 Cache-Control: no-storePragma: no-cache

成功响应参数示例:

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

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

错误响应参数说明:

名称 是否必须 描述信息
error 必须 错误代码
error_description 可选 具有可读性的错误描述信息
error_uri 可选 错误描述信息页面地址

错误响应示例:

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

{
    "error":"invalid_request"
}

3.4 客户端凭证受权模式(Client Credentials Grant)

客户端凭证受权模式基于客户端持有的证书去请求用户的受保护资源,若是把这里的受保护资源定义得更加宽泛一点,好比说是对一个内网接口权限的调用,那么这类受权方式能够被改造为内网权限验证服务。客户端凭证受权模式的交互时序以下:

客户端凭证

整个受权流程说明以下:

  1. 客户端携带客户端凭证和 scope 等信息请求受权服务器下发 access_token
  2. 受权服务器验证客户端真实性,经过则下发 access_token

3.4.1 请求获取访问令牌:

请求参数说明:

名称 是否必须 描述信息
grant_type 必须 对于本模式 grant_type=client_credentials
scope 可选 权限范围,若是最终下发的访问令牌对应的权限范围与实际应用指定的不一致,则必须在下发访问令牌时用该参数指定说明

请求参数示例:

POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials

成功响应参数说明:

名称 是否必须 描述信息
access_token 必须 访问令牌
token_type 必须 访问令牌类型,好比 BEARER、MAC 等等
expires_in 推荐 访问令牌的生命周期,以秒为单位,表示令牌下发后多久时间过时,若是没有指定该项,则使用默认值
scope 可选 权限范围,若是最终下发的访问令牌对应的权限范围与实际应用指定的不一致,则必须在下发访问令牌时用该参数指定说明

最后访问令牌以 JSON 格式响应,并要求指定响应首部 Cache-Control: no-storePragma: no-cache

成功响应参数示例:

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

{
    "access_token":"2YotnFZFEjr1zCsicMWpAA",
    "token_type":"example",
    "expires_in":3600,
    "example_parameter":"example_value"
}

错误响应参数说明:

名称 是否必须 描述信息
error 必须 错误代码
error_description 可选 具有可读性的错误描述信息
error_uri 可选 错误描述信息页面地址

错误响应示例:

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

{
    "error":"invalid_request"
}

四. 本篇小结

本文介绍了 OAuth 2.0 受权协议的理论知识,OAuth 2.0 被普遍应用于第三方受权登陆,不少其它的协议均可以基于该协议进行改造,好比前面提到的 SSO,做为开发人员,仍是建议对该协议或多或少有些了解。若是要本身实现一个受权和鉴权服务,该协议为咱们绘制指明了思路,可是还有不少细节实现须要咱们再去查阅各类资料和实践。

关于 token 的生成最后再补充一点,OAuth 2.0 协议只是一笔带过的说它是一个字符串,用于表示特定的权限、生命周期等,却没有明确阐述 token 的生成策略,以及如何去验证一个 token。协议不去详细阐述,我的以为是由于这一块是与具体业务绑定的,没法彻底作到抽象,而且在这一块去作详细的规定,意义也不大。

Token 本质上就是对用户受权这一操做在时间和权限范围两个维度上的一个表征,协议能够对 token 的传递和基本验证作相应规定,可是具体的一个 token 包含哪些元素,采用什么样的生成算法仍是须要由本身去把握。一些文档,好比参考文献 3 和 4 都为 token 的生成进行了扩展说明,鉴于篇幅,再也不展开。

欢迎扫码关注指间生活

参考文献

  1. RFC5849 - The OAuth 1.0 Protocol
  2. RFC6749 - The OAuth 2.0 Authorization Framework
  3. RFC6750 - The OAuth 2.0 Authorization Framework: Bearer Token Usage
  4. HTTP Authentication: MAC Authentication (draft-hammer-oauth-v2-mac-token-02)
相关文章
相关标签/搜索