OAUTH

Open Authorization, 开放的网络授权标准

解决问题


开放网络下直接提供用户名密码给第三方风险很高
代理访问,无需提供用户名密码,可以访问用于在其他应用服务上的数据

场景


档案馆的仓库存有用户的资料,用户小李可以出示身份证进馆查看。某一天小李想委托作家写一本传记,作家想要查询档案。作家自己没办法查看客户资料,但是身份证很重要,客户小李也不傻,他不会把身份证交给作家保管。于是最终的解决方案是小李在档案馆的办卡中心为作家申请了为期一个月的临时通行卡,这样作家就可以在期限内自己查阅资料了。但是小李可能比较很忙,没时间亲自去办卡中心,于是委托了秘书帮忙处理。

这个场景中:

  • 作家:第三方应用(Third-party Application,Client)
  • 用户小李:资源所有者(Resource Owner,User)
  • 档案馆:服务提供商(Service Provider)
  • 办卡中心:认证服务器(Authorization Server)
  • 仓库:资源服务器(Resource Server)
  • 临时通行卡:访问令牌(Access Token)
  • 秘书:用户代理(User Agent)

好处


  • 资源服务和授权服务解耦
  • 第三方应用接触不到用户真正的认证信息,只能获取临时的授权
  • 可以限制授权的时间和范围

定位


  • 主要是授权协议,允许访问资源,非认证用户信息
  • 协议主要规范宽泛的流程框架,细节未定义,比如token格式,如何对token进行处理

授权概念流程


第一个来回,征求用户的同意。
第二个来回,申请令牌。
第三个来回,获取数据。

授权实际流程


授权的关键如何让第三方应用获取到访问令牌(Access Token)

  • 授权码模式(Authorization code grant type flow)
    这个流程最为完备,认证服务器先发行授权码再发行访问令牌。

第三方首先请求用户代理,用户代理征求用户同意后发起授权请求,拿到授权码。第三方应用可以用授权码去请求访问令牌。
redirect_uri表示用户代理获得授权码后要跳转的地址,一般是第三方服务提供的接收点。

适用场景
这种流程适合有Server配合的应用。有Server配合的应用意味着这个第三方应用是和用户代理(浏览器)解耦的,比如我们用A网站的账号登录B网站,那么B网站需要访问我们在A网站的信息。那么最终需要获取访问令牌的是B网站的一台服务器。这里的好处是用户接触不到最后的访问令牌,令牌可以被网站B安全保管。

交互范例
回合1:用户代理<->认证服务器,发行授权码,使用ClientId作为识别
一般不是直接返回302,而是返回一个授权页面,用户点击同意后,进行302跳转

1
2
3
4
5
6
7
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
-----------------------------------------------------------------
HTTP/1.1 302 Found
Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA
&state=xyz

回合2:第三方应用<->认证服务器,发行访问令牌,可以附加使用ClientId和ClientSecret

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
-----------------------------------------------------------------
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"
}

现实中的使用流程:

  1. 用户在网站A,网站A想要请求用户在网站B的信息(A对于B来说是第三方应用)
  2. 网站A将页面导向网站B提供的授权引导界面(用户代理)
  3. 引导界面要求用户输入网站B的账户信息,通过后拿到了用户id,并询问是否同意授权
  4. 同意后引导界面向认证服务器请求授权码,附上A指定的跳转链接
  5. 认证服务器发回302跳转(redirect_uri)附加授权码,这样网站A拿到了授权码
  6. 网站A后端用授权码请求访问令牌,令牌对用户不可见
  7. 网站A用访问令牌请求用户数据

申请授权码时有个state参数,这个参数会原样附加在跳转url上,可以通过state验证和ClientId的相关性,避免csrf攻击,state一般使用无法预测的随机串
比如攻击者完成第一回合交互,但是截获跳转url,让受害者点,这样可能造成受害者直接进行第二回合交互并且使用了攻击者的授权码,账户绑定了攻击者的账户
加入state参数后,攻击者申请授权码时,clientId和state进行了绑定,受害者点击伪装链接,但是clientId和state并不匹配,可以被拒绝

  • 隐式授权流程(Implicit grant type flow)
    认证服务器直接发放令牌。

适用场景
适用于无Server配合的应用,比如js单页应用及mobile原生应用。这种应用是和用户代理(浏览器)紧密耦合的,最终拿到的访问令牌和代理在同一台机器上,用户可以使用一些手段看到访问令牌。由于无法安全的保管信息,因此没必要绕弯子去搞授权码,简化了流程。隐式流程适合临时访问,要时刻在用户的监督下,不允许脱离用户发行刷新令牌。

交互范例
直接发放访问令牌

1
2
3
4
5
6
7
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
-----------------------------------------------------------------
HTTP/1.1 302 Found
Location: http://client.example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA
&state=xyz&token_type=example&expires_in=3600

可以看出发行的访问令牌编码在URL的Fragment段,以#开头。
实际上用户代理在拿到Fragment段后不能直接转给第三方应用,因为Fragment段是不会被携带的。可以请求第三方应用的资源服务器,获取解析脚本,用户代理本地运行脚本解析出访问令牌后再发给第三方应用。上述过程在流程图中略过。

  • 密码授权流程(Resource owner password credentials grant flow)
    用户向第三方应用提供用户名密码。

适用场景
需要高度信任第三方不会私自存储密码,比如应用是操作系统的一部分或者是可信赖的公司产品。
这种流程接近于退化到了没有OAuth的时代,只有在其他流程都不行的情况下考虑这种方式。


交互范例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
-----------------------------------------------------------------
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"
}
  • 客户端模式(Client credentials grant type flow)
    客户端以自己的名义授权,其实跟用户关系不大了,严格的说有点偏离了OAuth的根本需求场景。

适用场景
比较通用公开的资源,不必须以用户的身份访问。用户在服务提供商上有共享资源,第三方服务用自己的账号也可以访问用户资源。

交互范例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
-----------------------------------------------------------------
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"
}

访问令牌格式


令牌可以有不同的形式,格式不在标准的规定范围,甚至可以自定义一种格式。
最常见的格式是Bearer。另外还有一种MAC格式的令牌,采用对称加密的方式,更加复杂。

访问令牌刷新


第三方应用的访问令牌过期后,可以拿之前一并获取的刷新令牌重新申请。

交互范例

1
2
3
4
5
6
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA

授权服务器


提供典型端点

  • oauth2/authorize: 请求授权
  • oauth2/token: 交换token,刷新token
  • oauth2/introspect: 资源服务器来校验token
  • oauth2/revoke: 撤销token