关于OAuth
Kale

因为课程的原因,学习了一下OAuth协议,看了很多资料,发现网上的中文资源大多讲得都非常模糊,所以做一个整理。

OAuth协议主要是为了解决授权问题,当需要使用到一个第三方网站的服务时,我们可能需要在这个网站进行注册,或者说如果一个网站需要用到另外一个网站的受保护的资源时,可能没有权限这样做,OAuth协议很好地解决了这些问题。

OAuth1.0

角色分类

在OAuth1.0中,将参与协议的角色分为了三类:

1
2
3
- service provider     // 服务提供商
- consumer // 接入者
- user // 用户

协议流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
     consumer                        service provider

+------------------+ +------------------+
| request | | grant |
| request token +-------A------>| request token |
+------------------+ +--------+---------+
|
|
+------------------+ |
| direct user to | <-----B----------------+
| service provider |
+-------+----------+ +------------------+
| | obtain user |
+------------------C------>| authorization |
+--------+---------+
|
+--------v---------+
| direct user to |
| consumer |
+------------------+ +--------+---------+
| request | |
| access token |<------D----------------+
+-------+----------+
| +------------------+
| | grant |
+------------------E------>| access token |
+--------+---------+
+------------------+ |
| access protected | |
| access token |<------F----------------+
+-------+----------+
|
+------------------G---------------->
  1. user在consumer处需要用到service provider处的资源,consumer向service provider发起请求,要求request token;

    • request参数

      1
      2
      3
      4
      5
      6
      7
      oauth_consumer_key:         必须;consumer的key,这个值是consumer在service provider处的身份标识;
      oauth_signature_method: 必须;本次请求中使用到的签名方法,协议定义了三种HMAC-SHA1、RSA-SHA1 PLAINTEXT,也可以使用额外的;
      oauth_signature: 必须;签名,在验证消费者签名时,服务提供者应该检查请求随机数以确保它没有在之前的消费者请求中使用过;
      oauth_timestamp: 必须;时间戳,值是从格林威治时间1970年1月1日00:00:00以来的秒数;
      oauth_nonce: 必须;随机数,避免重放攻击;
      oauth_version: 可选;oauth的版本,这里固定为1.0;
      Additional parameters: 可选;service provider定义的一些额外参数;
    • response参数

      1
      2
      3
      oauth_token:                必须;发布给consumer的request token;
      oauth_token_secret: 必须;token的密钥;
      Additional parameters: 可选;service provider定义的一些额外参数;
  2. conusmer引导用户到service provider处授权请求,此时接入者会发起一个重定向的GET请求

    • request参数

      1
      2
      3
      oauth_token:                必须;从service provider处收到的request token;
      oauth_callback: 可选;user完后操作后重定向的地址;
      Additional parameters: 可选;service provider定义的一些额外参数;
  3. 用户进行授权

  4. 如果授权失败,service provider需要告知consumer,如果授权成功,service provider发起GET请求重定向到之前consumer定义的callback uri,并带回授权的request token

    • request参数

      1
      oauth_token:                必须;授权过的request token;
  5. consumer使用授权过的request token向服务提供商交换access token,注意这个操作只能进行一次

    • request参数

      1
      2
      3
      4
      5
      6
      7
      oauth_consumer_key:         必须;consumer的key,这个值是consumer在service provider处的身份标识;
      oauth_token: 必须;已经授权过的request token;
      oauth_signature_method: 必须;本次请求中使用到的签名方法;
      oauth_signature: 必须;签名的值;
      oauth_timestamp: 必须;时间戳;
      oauth_nonce: 必须;随机数,避免重放攻击;
      oauth_version: 可选;oauth的版本,这里固定为1.0;
    • response参数

      1
      2
      3
      oauth_token:                必须;consumer申请的access token;
      oauth_token_secret: 必须;access token的密钥;
      Additional parameters: 可选;service provider定义的额外参数;
  6. consumer通过access token访问user在service provider处存储的受保护的资源

    • request参数

      1
      2
      3
      4
      5
      6
      7
      8
      oauth_consumer_key:         必须;consumer的key;
      oauth_token: 必须;前面获取的access token;
      oauth_signature_method: 必须;本次请求中使用到的签名方法;
      oauth_signature: 必须;签名的值;
      oauth_timestamp: 必须;时间戳;
      oauth_nonce: 必须;随机数,避免重放攻击;
      oauth_version: 可选;oauth的版本,这里固定为1.0;
      Additional parameters: 可选;service provider定义的一些额外参数;

      安全缺陷

  • 会话固定攻击

    • attacker请求一个consumer网站的授权,当consumer申请到request token之后,这个时候attacker应该被重定向到service provider的网站进行验证,但是这个过程被截断,attacker获取到这个url,然后用某种方法诱使受害者点击,并且授权,这样相当于受害者登录了该网站。

    • 在这个过程中,如果attacker修改了oauth_callback中定义的重定向url,那么受害者会被重定向到别的地方,并不会发起将request token换成access token的请求,那么attacker则可以从容地向service provider请求将request token换成access token,如果增加了对callback定义的网址的校验,那么attacker仍然有机会抢在受害者前面去执行获取access token的操作。

    • 这是因为用request token换取access token的过程只能进行一次。这里网上很多资料都没有说清楚,最后在官网找到了如下表述:

      Service Provider Grants an Access Token

      The Service Provider MUST ensure that:

      • The request signature has been successfully verified.
      • The Request Token has never been exchanged for an Access Token.
      • The Request Token matches the Consumer Key.

OAuth1.0a

由于OAuth1.0版本可能会受到会话固定攻击,所以在漏洞发布之后马上发布了OAuth1.0a版本。

协议流程

OAuth1.0a版本与OAuth1.0版本大致类似,与1.0版本相比,进行了以下改动:

  • 在consumer向service provider请求request token的过程中,请求中加上了oauth_callback参数,此时带上了特定的oauth_consumer_key,如果这个callback与该consumer存在service provider那里的callback一致,那么认为这个callback是可信的

    • request参数

      1
      2
      3
      4
      5
      6
      7
      8
      oauth_consumer_key:         必须;consumer的key,这个值是consumer在service provider处的身份标识;
      oauth_signature_method: 必须;本次请求中使用到的签名方法,协议定义了三种HMAC-SHA1、RSA-SHA1 PLAINTEXT,也可以使用额外的;
      oauth_signature: 必须;签名,在验证消费者签名时,服务提供者应该检查请求随机数以确保它没有在之前的消费者请求中使用过;
      oauth_timestamp: 必须;时间戳,值是从格林威治时间1970年1月1日00:00:00以来的秒数;
      oauth_nonce: 必须;随机数,避免重放攻击;
      - oauth_callback: 必须;重定向的url;
      oauth_version: 可选;oauth的版本,这里固定为1.0;
      Additional parameters: 可选;service provider定义的一些额外参数;
    • response参数

      1
      2
      3
      4
      oauth_token:                必须;发布给consumer的request token;
      oauth_token_secret: 必须;token的密钥;
      - oauth_callback_confirmed: 必须;必须设置为true,consumer可能会凭借此值判断service provider是否收到了oauth_callback;
      Additional parameters: 可选;service provider定义的一些额外参数;
  • 当用户授权完毕后,如果授权成功,此时会重定向到之前指定的callback中,与1.0版本不同的是此时会带上oauth_verifier参数,来验证授权的和请求access token的是同一个实体

    • request参数

      1
      2
      oauth_token:                必须;授权过的request token;
      - oauth_verifier: 必须;保证请求access token的和授权的是一个实体;
  • consumer在使用request token请求access token的过程中,必须带上oauth_verifier参数

    • request参数

      1
      2
      3
      4
      5
      6
      7
      8
      oauth_consumer_key:         必须;consumer的key,这个值是consumer在service provider处的身份标识;
      oauth_token: 必须;已经授权过的request token;
      oauth_signature_method: 必须;本次请求中使用到的签名方法;
      oauth_signature: 必须;签名的值;
      oauth_timestamp: 必须;时间戳;
      oauth_nonce: 必须;随机数,避免重放攻击;
      oauth_version: 可选;oauth的版本,这里固定为1.0;
      - oauth_verifier: 必须;用来验证身份;

      相关缺陷

  • 对移动端的应用的支持不够好,首先callback字段无法设置为相应的url,需要设置为oob,另外是返回的oauth_verifier字段可能需要以明文的方式显示给用户,并引导用户粘贴到待授权应用中,需要用户在应用与浏览器之间手动切换,影响了用户体验

  • 签名逻辑过于复杂

OAuth2.0

由于OAuth1.0a版本的签名逻辑复杂,所以提出了OAuth2.0版本

角色分类

1
2
3
4
- resource owner       // 资源所有者
- resource server // 资源服务器
- client // 接入者
- authorization server // 认证服务器,authorization server和resource server可以是一个服务器也可以是单独的实体

四种模式

  • Authorization Code Grant
  • Implicit Grant
  • resource owner password credentials
  • client credentials

Authorization Code Grant

适用于绝大多数情况,也是官方推荐的模式

流程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
+----------+
| Resource |
| Owner |
| |
+----------+
^
|
(B)
+----|-----+ Client Identifier +---------------+
| -+----(A)-- & Redirection URI ---->| |
| User- | | Authorization |
| Agent -+----(B)-- User authenticates --->| Server |
| | | |
| -+----(C)-- Authorization Code ---<| |
+-|----|---+ +---------------+
| | ^ v
(A) (C) | |
| | | |
^ v | |
+---------+ | |
| |>---(D)-- Authorization Code ---------' |
| Client | & Redirection URI |
| | |
| |<---(E)----- Access Token -------------------'
+---------+ (w/ Optional Refresh Token)
  1. resource owner请求client,client向authorization server发起请求,并将resource owner重定向到authorization server提供的认证页面

    • request参数

      1
      2
      3
      4
      5
      response_type:              必须;必须设置为"code";
      client_id: 必须;client在authorization server处的唯一id;
      redirect_uri: 可选;重定向的uri;
      scope: 可选;授权范围;
      state: 可选;防止csrf攻击;
    • example

      1
      2
      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
  2. 接下来resource owner在authorization server端进行授权,如果授权通过,authorization server重定向到client指定的redirect_uri中

    • request参数

      1
      2
      code:          				必须;即authorization_code,并且必须存在过期时间,官方推荐过期时间设置为10分钟,并且这个code只能使用一次,如果第							二次使用,authorization必须也同时剥夺第一次请求授权的权限;
      state: 如果client发送时带上了state参数,该参数则为必须;
    • example

      1
      2
      HTTP/1.1 302 Found
      Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz
  3. 如果client对authorization server发出的请求有错,authorization server应当通知resource owner,反之如果resource owner拒绝了授权,也应该通知client

    • example

      1
      2
      HTTP/1.1 302 Found
      Location: https://client.example.com/cb?error=access_denied&state=xyz
  4. 如果resource owner成功授权,此时client已经拿到了authoriztion server下发的authorization code,这个code是有时效性的并且只能使用一次,client向authorizaiton server发起请求

    • request参数

      1
      2
      3
      4
      grant_type:    				必须;必须被设置为"authorization code"
      code: 必须;即authorization code
      redirect_uri: 必须;如果在前面的请求中已经发过了,则两次的uri必须一致
      client_id: 必须;该client必须已经被记录到authorization server中
    • example

      1
      2
      3
      4
      5
      6
      7
      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
  5. 此时authorization server成功收到client请求access code的请求,如果此时access code是可授权的且client认证通过,则返回access token和refresh token,否则返回错误信息

    • response参数

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      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"
      }

      Implicit Grant

适用于在浏览器中用纯脚本语言实现的客户端,没有server端,无法保存code

流程
  1. resource owner访问client,client将resource owner重定向到authorization server的授权页面,并附带上相关信息,与前文一致

  2. 如果授权通过,authorization server重定向到client指定的redirect_uri页面,并附带上如下信息,参数中不能带上refresh token

    • request参数

      1
      2
      3
      4
      5
      access_token:   			必须;即访问令牌;
      token_type: 必须;token的类型;
      expires_in: 建议;即访问令牌的过期时间;
      scope: 可选;授权范围;
      state: 防止csrf攻击;
    • example

      1
      2
      HTTP/1.1 302 Found
      Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA&state=xyz&token_type=example&expires_in=3600
  3. 当请求发生错误时,返回结果同上

Resource Owner Password Credentials Grant

此模式适用于client与resource owner高度可信,比如设备操作系统或者高权限的应用程序,仅在别的模式不能使用时才能使用该模式

流程
  1. resource owner将自己的credentials(比如username和password)交付给client

  2. client拿着resoruce owner的credentials去向authorization server发去请求

    • request参数

      1
      2
      3
      4
      grant_type:     			必须;必须设置为"password";
      username: 必须;credentials内容;
      password: 必须;credentials内容;
      scope: 可选;授权范围;
  3. 如果验证通过,authorization server返回access token

    • response example

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      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

适用于认证服务器不提供像用户数据这样的重要资源,仅仅是有限的只读资源或者一些开放的API,该模式下不允许返回refresh token

流程
  1. client发送请求,进行验证,需要id和密钥
  2. 服务端验证通过后进行返回

关于refresh token的使用

refresh token用来在access token过期时刷新access token

  • client向authorization server发起请求

    • request参数

      1
      2
      3
      grant_type:      			必须;必须设置为"refresh_token";
      refresh_token: 必须;
      scope: 可选;授权范围;
    • example

      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
  • authorization server对client身份进行验证,如果验证通过且该refresh_token有效,则返回一个新的access token,此时有可能会新返回一个新的refresh token,client必须用新的refresh token替换旧的,authorization server必须在返回了新的refresh token后立即剥夺旧的refresh token的权限

关于返回的access token

返回的access token可能有特定的格式,如果client不理解,则不能使用该access token,比如bearer类型的通过简单地在请求中包含access token来使用

1
2
3
GET /resource/1 HTTP/1.1
Host: example.com
Authorization: Bearer mF_9.B5f-4.1JqM

比如mac类型的:

1
2
3
4
5
GET /resource/1 HTTP/1.1
Host: example.com
Authorization: MAC id="h480djs93hd8",
nonce="274312:dj83hs9s",
mac="kDZvddkndxvhGRXZhvuDjEWhGeE="

client需要自行构造一部分内容

关于native applications

native applications的认证可以呼起额外的用户代理或者使用内嵌的用户代理,但是呼唤起额外的用户代理会影响用户体验,因为用户还需要手动粘贴access token,而使用内嵌的用户代理是不安全的,因为内嵌的用户代理使得用户在一个身份不明的窗口中进行身份验证,可能会教育用户使其信任不明窗口的身份验证请求,从而使得网络钓鱼攻击更加容易。

另外在implicit grant和authorization code模式之间,如果使用authorizatin code模式,需要本地应用需要保存client_id和密钥,这是不安全的,本地应用没有能力保存这两者,如果使用implicit grant模式,由于authorizaiton server不会返回refresh token,所以当access token失效时就会要求重新进行认证请求access token逻辑,影响体验。

一些安全考虑

  • Client Authentication
    • authorization server应该考虑比client password更强健的认证方式,并且不能将client credentials发布给native application以及user-agent-based application
  • Client Impersonation
    • authorization server必须尽可能对client进行身份认证,可以采取请求resource owner协助的方式进行认证,并且授权服务器不应该在没有验证客户端或依赖其他措施的情况下自动处理重复的授权请求。
  • Access Tokens
    • access token在传输和存储的过程中必须保证加密,并且只能被resource server, authorizaiton server和client之间使用,当使用implicit grant模式时,access token在url中,可能会产生泄露风险;authorization server必须保证access token不能被生成和修改或者猜到。client必须尽可能请求更小的scope,authorization server也要尽可能在满足要求的情况下给予更小的scope。
  • Refresh Tokens
    • refresh token在传输和存储的过程中必须保证加密,并且只能被authorization server和client之间使用,authorization server需要保证refresh token和client是唯一绑定关系
  • Authorization Codes
    • authorizaiton code必须在安全信道中传输,并且client应该使用tls,并且该code只能使用一次
  • Authorization Code Redirection URI Manipulation
    • 为了防止会话固定攻击,首先在client注册时就要求提供redirect_uri,并且在请求authorizaiton code的过程中也要求发送redirect_uri,要求前后一致,否则拒绝成功响应
  • Resource Owner Password Credentials
    • Resource Owner Password Credentials Grant模式可能会导致client存储用户密码,或者在无意中将密码泄露给了攻击者,此外,由于用户无法控制授权粒度,所以client所拿到的授权scope可能比实际用到的要大得多,所以authorization server需要考虑access token的时效以及scope
  • Request Confidentiality
    • ccess tokens, refresh tokens, resource owner passwords, and client credentials不能以明文方式传输
  • Ensuring Endpoint Authenticity
    • 为了避免中间人攻击,client与authorization server之间必须使用tls
  • Credentials-Guessing Attacks
    • 所生成的token必须要避免猜测攻击
  • Phishing Attacks
    • 由于本协议的大规模应用,用户可能习惯了从一个站点跳到另外一个站点,然后输入自己的密码或者凭证来进行认证,这很有可能遭受钓鱼攻击,所以service providers应该考虑到这种风险,应该尽力做到很容易就能辨认本网站是否是钓鱼网站。
  • Cross-Site Request Forgery
    • 要避免csrf攻击,方式是在字段中加上state,这个字段是无法被猜测的,可以是cookie的散列。
  • Clickjacking
    • 需要避免点击劫持攻击,攻击者可能会构建一个恶意站点,在一个透明的iframe中加载授权服务器的授权端点网页,这个iframe覆盖在一组虚拟按钮之上,当最终用户点击一个误导性的可见按钮时,最终用户实际上是在点击授权页面上的一个不可见按钮,为了防止这种攻击,本机应用程序应该在请求最终用户授权时使用外部浏览器而不是在应用程序中嵌入浏览器,对于大多数浏览器,授权服务器可以使用(非标准)“x-frame-options”标头强制避免 iframe 。此标头可以有两个值,“deny”和“sameorigin”,它们将分别阻止任何框架或由具有不同来源的站点构成的框架。
  • Misuse of Access Token to Impersonate Resource Owner in Implicit Flow
    • 任何client不得在没有额外安全机制的情况下使用implicit grant,因为如果没有额外安全措施,可能会被钓鱼攻击等方式让受害者无意中将access token返回到攻击者的恶意client中,然后攻击者就可以正常访问protected resource

相关链接

  • 本文标题:关于OAuth
  • 本文作者:Kale
  • 创建时间:2021-10-08 10:20:26
  • 本文链接:https://kalew515.com/2021/10/08/关于OAuth/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!