OAuth2 协议简介

OAuth是一个关于授权的开放网络标准,用来授权第三方应用,获取用户的数据。其最终的目的是为了给第三方应用颁发一个有时效性的令牌access_token,第三方应用根据这个access_token就可以去获取用户的相关资源,如头像,昵称,email这些信息。现在大家用的基本是2.0的版本。

协议流程

在详细介绍oAuth2协议流程之前,先来简单了解几个角色,方便后续的理解。

  • Resource Owner: 资源所有者,因为是请求用户的头像和昵称的一些信息,所以资源的所有者一般指用户自己;

  • Client:客户端,如web网站,app等;

  • Resource Server: 资源服务器,托管受保护资源的服务器;

  • Authorization Server: 授权服务器,一般和资源服务器是同一家公司的应用,主要是用来处理授权,给客户端颁发令牌;

  • User-agent:用户代理,一般为web浏览器,在手机上就是app

了解了上面这些角色之后,来看下oAuth2.0的运行流程是怎么样的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
+--------+                               +---------------+
| |--(A)- Authorization Request ->| Resource |
| | | Owner |
| |<-(B)-- Authorization Grant ---| |
| | +---------------+
| |
| | +---------------+
| |--(C)-- Authorization Grant -->| Authorization |
| Client | | Server |
| |<-(D)----- Access Token -------| |
| | +---------------+
| |
| | +---------------+
| |--(E)----- Access Token ------>| Resource |
| | | Server |
| |<-(F)--- Protected Resource ---| |
+--------+ +---------------+
  • (A). 用户打开客户端(Client),客户端向资源所有者(Resource Owner)也就是用户发送一个授权请求;

  • (B). 用户同意给客户端(Client)授权

  • (C). 客户端使用刚才的授权去向认证服务器(Authorization Server)认证

  • (D). 认证服务器认证通过后,会给客户端发放令牌(Access Token)

  • (E). 客户端拿着令牌(Access Token),去向资源服务器(Resource Server)申请获取资源

  • (F). 资源服务器确认令牌之后,给客户端返回受保护的资源(Protected Resource)

授权方式

在oAuth2当中,定义了四种授权方式,针对不同的业务场景:

  • 授权码模式(authorization code)
    流程最完整和严密的一种授权方式,服务器和客户端配合使用,主要是针对web服务器的情况采用

  • 简化模式(implicit)
    主要用于移动应用程序或纯前端的web应用程序,主要是针对没有web服务器的情况采用

  • 密码模式(resource owner password credentials)
    不推荐,用户需要向客户端提供自己的账号和密码,如果客户端是自家应用的话,也是可以的

  • 客户端模式(client credentials)
    客户端以自己的名义,而不是用户的名义,向“服务提供商”进行认证,如微信公众号以此access_token来拉取所有已关注用户的信息,docker到dockerhub拉取镜像等

授权码模式

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
 +----------+
| 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 |
| (Web后端)| |
| |<---(E)----- Access Token -------------------'
+---------+ (w/ Optional Refresh Token)

Note: The lines illustrating steps (A), (B), and (C) are broken into two parts as they pass through the user-agent.

授权码模式如上图所示,这种流程是功能最完整,流程也是最严密的授权方式,适用于那些有后端的web应用。它的特点是通过客户端的后台服务器和服务商的认证服务器进行通讯。它的流程如下,如果我想使用github来接入第三方登录:

(A). 用户(Resource Owner)在用户代理(User-Agent,如web浏览器,app)上选择了第三方应用(如github)来进行登录,会重定向到github的授权端点:

1
2
3
4
5
6
https://github.com/login/oauth/authorize?
response_type=code&
client_id=your_code&
redirect_uri=重定向的url&
scope=read&
state=uuid
字段 描述
response_type 必须,在授权码模式中固定为code
client_id 必须,唯一标识了客户端,在github注册时获得的客户端ID
redirect_url 客户端在github注册的重定向url,用户同意或拒绝的时候都会跳转到这个重定向url
scope 可选,请求资源范围,如有多项,使用多个空格隔开
state 推荐,客户端生成的随机数,资源服务器会原样返回,防止CSRF的攻击

(B). 页面跳转后,github会要求用户登录,然后询问是否给予客户端授权,用户点击同意。

(C). 然后github就会将授权码(Authorization Code)返回给redirect_uri(重定向uri)。

1
redirect_uri?code=xxxxxxx
字段 描述
code 必须,授权码
state 防止CSRF攻击的参数

(D). 客户端(Client)在通过在URL中取出授权码之后,就可以在后端向github请求令牌

1
2
3
4
5
6
https://github.com/login/oauth/access_token?
client_id=your_code&
client_secret=your_secret&
grant_type=authorization_code&
code=取出的code&
redirect_uri=重定向的url
字段 描述
client_id 必须,客户端在github注册的唯一标识
client_secret 必须,客户端在github注册时返回的密钥
grant_type 必须,authorization_code/refresh_code
code 必须,上一步中取出的授权码
redirect_uri 必须,完成授权之后的回调地址,与在github注册时的一致

(E). github给redirect_uri指定的地址返回AccessToken,通过JSON格式返回

1
2
3
4
5
6
{
"access_token":"xxxxxxx",
"token_type":"bearer",
"expires_in":3600,
"refresh_token":"xxxxxxx"
}

客户端就可以在后端取到access_token, 在这段json中,还返回了一个refresh_token,这个refresh_token表示用于访问下一次的更新令牌,refresh_token的时效性比access_token长,当access_token过期时,可以使用refresh_token换取新的access_token。

简化模式

简化模式主要针对没有后端的纯前端应用,在这种情况下,因为没有后端,所以就不能采用授权码模式的这种流程了,必须要把access_token存在前端。

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
+----------+
| Resource |
| Owner |
| |
+----------+
^
|
(B)
+----|-----+ Client Identifier +---------------+
| -+----(A)-- & Redirection URI --->| |
| User- | | Authorization |
| Agent -|----(B)-- User authenticates -->| Server |
| | | |
| |<---(C)--- Redirection URI ----<| |
| | with Access Token +---------------+
| | in Fragment
| | +---------------+
| |----(D)--- Redirection URI ---->| Web-Hosted |
| | without Fragment | Client |
| | | Resource |
| (F) |<---(E)------- Script ---------<| |
| | +---------------+
+-|--------+
| |
(A) (G) Access Token
| |
^ v
+---------+
| |
| Client |
| |
+---------+

Note: The lines illustrating steps (A) and (B) are broken into two parts as they pass through the user-agent.

主要是B这个步骤,页面跳转到github网站,用户同意给予客户端授权。github就会把令牌作为URL参数,跳转回到redirect_uri的这个回调地址。

1
回调地址#token=xxxxxx

注意,令牌的位置是 URL 锚点(fragment),而不是查询字符串(querystring),这是因为 OAuth 2.0 允许跳转网址是 HTTP 协议,因此存在”中间人攻击”的风险,而浏览器跳转时,锚点不会发到服务器,就减少了泄漏令牌的风险。

密码模式

如果你高度信任某个应用,RFC 6749 也允许用户把用户名和密码,直接告诉该应用。该应用就使用你的密码,申请令牌,这种方式称为”密码式”(password)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
+----------+
| Resource |
| Owner |
| |
+----------+
v
| Resource Owner
(A) Password Credentials
|
v
+---------+ +---------------+
| |>--(B)---- Resource Owner ------->| |
| | Password Credentials | Authorization |
| Client | | Server |
| |<--(C)---- Access Token ---------<| |
| | (w/ Optional Refresh Token) | |
+---------+ +---------------+

密码模式就是用户向客户端提供自己的账号和密码,客户端使用这些信息去向我们的服务提供商去索要一个授权。

客户端模式

客户端以自己的名义,而不是用户的名义,向“服务提供商”进行认证,如微信公众号以此access_token来拉取所有已关注用户的信息,docker到dockerhub拉取镜像等。

1
2
3
4
5
6
7
+---------+                                  +---------------+
| | | |
| |>--(A)- Client Authentication --->| Authorization |
| Client | | Server |
| |<--(B)---- Access Token ---------<| |
| | | |
+---------+ +---------------+

客户端模式,顾名思义就是指客户端以自己的名义而不是用户的名义去向服务的提供商去做一个认证,严格来说,这种模式并不是 oAuth 框架要解决的问题,在这种客户端模式下呢,它是直接通过客户端的密钥和id去获取一个access_token的,不需要用户去参与。

参考文章:
理解 OAuth2 协议