引言
关于应用系统用户身份管理需求,包括身份认证、权限授权、单点登录、联合身份认证等业务场景,业界有一堆的标准和规范,比如单点登录的CAS、Kerberos,第三方身份认证OpenID,第三方用户授权OAuth,联合身份认证和授权数据标准SAML等。每种技术有各自的应用场景,也存在交叉场景,想要把他们搞清楚,需要了解各种技术的工作原理和应用场景。今天就从其中一个技术开始,对OAuth2.0用户授权框架做一个简单介绍,想对框架全面了解的可以参考框架的标准 RFC6749。
OAuth概述
引用维基百科介绍,OAuth主要的应用场景为:
举一个通俗的例子,用户把照片、视频、联系人数据存储在内容托管云服务R(Resource)中的Picture、Video、Contact三个模块中;用户使用在线照片打印服务P(Printer),用户需要让P服务读取R服务中的照片进行打印,但不想让P服务读取R服务中的其他数据。
传统方式下,用户只能向P服务提供R服务的用户名密码,P服务通过用户名密码登录R服务,读取照片,并且不能限制P服务读取的范围。
使用OAuth框架,通过以下授权流程,在不暴露用户密码的情况下,向P服务授予有限的操作S服务的权限,整体流程如下:
- 用户登录P服务,点击获取R服务权限的链接。
- 浏览器跳转到R服务,用户登录R服务后,跳出向P服务授予权限的界面。
- 用户选择授予Picture模块、只读、有效期1小时三个权限的授权选项,并提交。
- 页面跳转回P服务,并携带R服务生产的授权码(Picture模块只读权限)。
- P服务获得授权码,通过授权码(附加clint_id和client_secret)向R服务发起读取Picture模块请求。
- R服务验证用户信息和授权码后,向P服务提供Picture的读取权限。
OAuth发展至今,共有三个版本,分别为:初始化版本OAuth1.0;漏洞修复版本OAuth 1.0a;不向后兼容的OAuth2.0版本。
2.0版本主要是修复了前面版本的安全漏洞,对授权的流程进行了优化,提供了更丰富的使用场景,由于优化精简了授权的步骤,所以不能向后兼容。本文重点介绍OAuth2.0的业务流程,为便于描述,下文如果没有特殊说明OAuth指的就是OAuth2.0。
服务角色
根据RFC描述,定义了4中服务角色,分别描述如下:
- 资源所有者 Resource Owner
- 资源服务器 Resource Server
- 客户端 Client
- 授权服务器 Authorization Server
协议运行流程
+--------+ +---------------+
| |--(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) 客户端向资源所有者申请授予资源访问权限。如示例中的P服务授予授权界面。
B) 资源所有者向客户端授予资源访问授权。如示例中的用户授予予Picture模块、只读、有效期1小时三个权限的授权选项,并把授予的权限凭证返回给客户端。根据使用场景,Oauth规范定义了四中权限授予类型,具体在下文描述。
C) 客户端向授权服务器提交授权凭证,申请获取授权令牌。
D) 授权服务器校验凭证,并向客户端返回授权令牌。
E) 客户端向资源服务器提交授权令牌,申请访问资源。
F) 资源服务器校验令牌合法性,并向客户端提供资源服务。
客户端注册
客户端在申请授权服务前,需要先到认证服务器上注册。以GitHub作为授权服务器为例,用户先到Register a new OAuth application注册一个第三方应用。注册后,系统为应用生成Client ID和Client Secret两个参数,用户后续的授权。
注册页面
注册结果
权限授予类型
授权授予是表示资源所有者授权客户端的权限凭据,客户端使用此凭证获取访问令牌。规范定义了四种授权类型:授权代码模式(Authorization code)、隐含模式(Implicit)、资源所有者密码凭据模式(Resource Owner Password)和客户端凭据模式(Client Credentials),概述如下。
授权代码模式(Authorization code)
授权代码模式是功能最完善,流程最严密,安全性最好的授权模式。这种模式和其他模式的最大区别,是授权过程中,由客户端的后台服务器端参与,即要求客户端是有后台服务器端处理能力的服务,如Web服务。
+----------+
| 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)
需要特别说明下,Client以Web服务为例,这里的Client是指Client的后台服务器端,User-Agent是指浏览器,授权的流程如下。
A) 用户打开浏览器,访问Client的Web页面,点击获取授权链接;浏览器把用户重定向到授权服务器。
B) 用户通过认证凭证(用户名密码)登录授权服务器。
C) 用户在授权服务器授权页面选择要授权给Client访问的权限并提交;提交后,授权服务器生成授权代码,把浏览器重定向到附带授权代码的Client指定的页面;Client页面将授权代码提交到自己的后台服务器端。
D) Client后台服务把授权代码+client_id+client_secret(后两者在客户端向授权服务器注册时生成)提交给授权服务器进行验证。
E) 授权服务器验证通过后,向Client返回Access Token和Refresh Token(可选)。
之后,Client就可以拿个Access Token,向资源服务器请求获取资源的服务。
下面在介绍下各步骤的请求和返回内容。
获取授权码请求
请求示例:
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
参数说明:
response_type
,必填。固定为code
。client_id
,必填。Client向授权服务器注册时生成的ID。redirect_uri
,可选。授权成功后返回授权代码的重定向地址,此地址也可以在授权服务器上注册。scope
,可选。指定访问请求的范围,含义由授权服务器设计。state
,推荐。客户端用来在请求和回调之间保持状态的不透明值。授权服务器在将user-agent重定向回客户端时包含此值。参数用于防止跨站请求伪造。
获取授权代码响应
如果授权成功,则授权服务器通过URL重定向向Client返回授权代码。
响应示例:
HTTP/1.1 302 Found
Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA
&state=xyz
参数说明:
code
,必填。授权成功后授权服务器生成的授权代码,基于安全性考虑,授权代码由如下要求- 授权代码需要设置较短的过期时间,以减轻授权代码泄露的风险,一般推荐10分钟。
- 授权代码只能使用一次,如果多次使用,则此授权代码相关的所有Token都会被撤销。
state
。和Request中的参数对应。
获取Access Token请求
请求示例:
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
参数说明:
grant_type
,必填。固定为authorization_code
。code
,必填。授权代码。redirect_uri
。和获取授权码请求地址一致。client_id
,必填。客户端的client_id。
获取Access Token响应
响应示例:
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"
}
参数说明:
- access_token,必填。访问令牌。
- token_type,必填。令牌类型,可以是bearer类型或mac类型。
- expires_in。过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。
- refresh_token,可选。更新令牌,用与获取下一次的访问令牌。
- scope,可选。权限范围,客户端申请填写的一致。
隐含模式(Implicit)
隐含模式(implicit grant type)授权过程不需要Client后台服务器端参与,直接在浏览器中向授权服务器申请令牌,跳过了"授权码"这个步骤。所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证。
+----------+
| 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 |
| |
+---------+
资源所有者密码凭据模式(Resource Owner Password)
资源所有者密码凭据模式中,用户向客户端提供自己的用户名和密码。客户端使用这些信息,向授权服务器索要授权。
+----------+
| Resource |
| Owner |
| |
+----------+
v
| Resource Owner
(A) Password Credentials
|
v
+---------+ +---------------+
| |>--(B)---- Resource Owner ------->| |
| | Password Credentials | Authorization |
| Client | | Server |
| |<--(C)---- Access Token ---------<| |
| | (w/ Optional Refresh Token) | |
+---------+ +---------------+
客户端凭据模式(Client Credentials)
当客户端请求访问其控制下的受保护资源时,客户端只能使用其客户端凭据(或其他支持的身份验证方法)来请求访问令牌。或者其他资源拥有者之前与授权服务器安排好的资源拥有者(其方法不在本文的范围之内)。客户端凭据授予类型必须仅供机密客户端使用。
+---------+ +---------------+
| | | |
| |>--(A)- Client Authentication --->| Authorization |
| Client | | Server |
| |<--(B)---- Access Token ---------<| |
| | | |
+---------+ +---------------+
刷新令牌Refresh Token
在授权代码模式和资源所有者密码凭据模式中,获取Access Token的过程中会返回可选的Refresh Token,Refresh Token用于获取Access Token。由于Access Token需包含在每个和资源服务器的请求中,使用频率高,所以应该设置较短的有效期,以减少泄露带来的风险,当Access Token过期是,可以通过Refresh Token中授权服务器中换取新的Access Token。
刷新过期的Access Token的流程如下。
+--------+ +---------------+
| |--(A)------- Authorization Grant --------->| |
| | | |
| |<-(B)----------- Access Token -------------| |
| | & Refresh Token | |
| | | |
| | +----------+ | |
| |--(C)---- Access Token ---->| | | |
| | | | | |
| |<-(D)- Protected Resource --| Resource | | Authorization |
| Client | | Server | | Server |
| |--(E)---- Access Token ---->| | | |
| | | | | |
| |<-(F)- Invalid Token Error -| | | |
| | +----------+ | |
| | | |
| |--(G)----------- Refresh Token ----------->| |
| | | |
| |<-(H)----------- Access Token -------------| |
+--------+ & Optional Refresh Token +---------------+