OAuth2.0协议分析
不知不觉,在软件行业过了许多个年头了。也读了许多大V的文章,受益匪浅,在这里向各位前辈致敬。这是我的第一篇原创文章,供大家参考交流,不足之处敬请留言提示。
公司在做海外市场,可能是由于海外知识产权保护的力度太大吧,国外的软件普遍更新很慢。哪像国内,正在开发的东东,很多还没有听说就被淘汰了。所以像扫码支付,第三方登录的功能,虽然在国内早已遍地都是应用,但是国外还没有这么普及。
所以,公司准备在产品中增加一个OAuth2.0认证的功能,以供海外使用。好了,不再卖官司了,步入正题。
本文主要介绍OAuth2.0协议原理,OAuth2.0四种授权模式下的具体流程。
1 OAuth2.0简介
1.1 技术背景
OAuth2.0关注客户端开发者简易性。不向后兼容OAuth1.0协议。2012年10月,OAuth2.0协议正式发布为RFC6749.
1.2 典型应用
OAuth(开放授权)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方。常见使用场景如使用新浪微博或是腾讯QQ帐号登录百度网盘,登录界面如下图2.1所示:
图1.1 百度网盘登录界面
1.3 协议流程
传统的客户端-服务器身份验证模式中,客户端使用密码请求服务器上受保护的资源。用户为了让第三方应用也能访问受保护的资源,就需要让第三方应用共享用户的密码信息。这就造成了一些问题:
> 需要第三方应用保存用户的用户名与密码,以供将来使用,通常是明文密码;
> 第三方应用获得的用户的密码信息后,访问权限过于宽泛,从而导致用户失去对资源使用时限或使用范围的控制;
> 用户不能仅撤销某个第三方的访问权限而不影响其它,并且,用户只有通过改变密码才能改变第方的访问权限;
> 与任何第三方应用的让步导致对终端用户的密码及该密码所保护的所有数据的让步。
OAuth2.0通过引入授权层以及分离客户端角色和资源所有者角色来解决这些问题。OAuth2.0协议的流程如图2.2所示:
图1.2 OAuth2.0协议流程
图2.2中所示的抽象OAuth2.0流程描述了四个角色之间的交互,包括以下步骤:
- 客户端向从资源所有者请求授权;
- 客户端收到授权许可(OAuth2.0中定义了四种许可类型,也可以是由用户定义的扩展许可);
- 客户端与授权服务器进行身份认证并出示授权许可请求访问令牌;
- 授权服务器验证客户端身份与授权许可,若验证通过则颁发访问令牌;
- 客户端从资源服务器请求受保护资源并出示访问令牌以供资源服务器对其进行身份验证;
- 资源服务器验证访问令牌,若有效则满足客户端请求;
2 认证流程的参与者
2.1 参与者角色
OAuth2.0认证流程中涉及到了OAuth2.0定义的四种角色,如图3.1所示:.
图2.1 OAuth2.0角色
- 资源所有者:能够许可受保护资源访问权限的实体。当资源所有者是个人时,它作为最终用户被提及。
- 资源服务器:托管受保护资源的服务器,能够接收和响应使用访问令牌对受保护资源的请求。
- 客户端:使用资源所有者的授权代表资源所有者发起对受保护资源的请求的应用程序。术语“客户端”并非特指任何特定的的实现特点(例如:应用程序是否在服务器、台式机或其他设备上执行)。
- 授权服务器:在成功验证资源所有者且获得授权后颁发访问令牌给客户端的服务器。
2.2 客户端
OAuth2.0定义了两种客户端,分别是机密客户端与公开客户端:
-
机密客户端
能够维持其凭据机密性(如客户端执行在具有对客户端凭据有限访问权限的安全的服务器上),或者能够使用 其他方式保证客户端身份验证的安全性。 -
公开客户端
不能够维持其凭据的机密性(如客户端执行在由资源所有者使用的设备上,例如已安装的本地应用程序或基于Web浏览器的应用),且不能通过其他方式保证客户端身份验证的安全性。 客户端类型的选择基于授权服务器的安全身份认证定义以及其对客户端凭据可接受的暴露程度。授权服务器不应该对客户端类型做假设。
2.3 协议端点
OAuth2.0认证过程中涉及的协议端点如图3.2所示:
- 授权端点——客户端通过此端点利用用户代理重定向功能从资源所有者获取授权。
- 令牌端点——客户端通过此端点将授权许可交换为访问令牌,通常伴有客户端身份验证。
- 重定向端点——授权服务器用其通过资源所有者用户代理向客户端返回含有授权凭据的响应。
并不是每种授权许可类型都采用两种端点。扩展许可类型可以按需定义其他端点。
图2.2 OAuth2.0 协议端点
3 授权流程
授权许可是一个代表资源所有者授权(访问受保护资源)的凭据,客户端用它来获取访问令牌。OAuth2.0定义了四种许可类型:授权码、隐式许可、资源所有者密码凭据和客户端凭据,以及用于定义其他类型的可扩展性机制。
3.1 授权码授权
3.1.1 授权流程
授权码授权流程是一个基于重定向的流程,要求客户端:
必须能够与资源所有者的用户代理(通常是Web浏览器)进行交互;
能够接收来自授权服务器的传入请求(通过用户代理的重定向)。
具体流程如图4.1所示:
图3.1 授权码授权流程
在图3.1中所示的流程包括以下步骤:
- 假设是资源所有者想利用客户端访问资源所有者在他本人在其它网站(资源服务器)上的内容;
- 客户通过向授权端点引导资源所有者的IE(用户代理)开始流程。向授权服务器发送客户端标识、请求范围、本地状态和重定向URI,以请求授权码;
- 授权服务器返回授权登录界面(用户未登录系统时);
- 用户输入用户名与密码,并确认授权;
- IE中授权脚本提交用户的授权与密码信息;
- 授权服务器验证资源拥有者的身份,并确定资源所有者是否授予或拒绝客户端的访问请求。然后发送授权码给IE并通过URL重定向到客户端;
- 客户端通过包含上一步中收到的授权码从授权服务器的令牌端点请求访问令牌;
- 授权服务器对客户端进行身份验证,验证授权代码并确保接收的重定向URI与用于重定向客户端的URI相匹配;
- 授权服务器响应返回访问令牌与可选的刷新令牌;
- 客户端使用访问令牌访问资源服务器;
- 资源服务器向客户端返回用户需要访问的资源;
3.1.2 请求与响应
3.1.2.1 授权码
请求方式:使用HTTP GET方法请求;
请求参数:
response_type :必需的。值必须为“code”;
client_id:必需的;
redirect_uri:可选的;
scope:可选的;
state:推荐的;该参数应该用于防止跨站点请求伪造;
示例:客户端使用TLS定向用户代理发起下述HTTP请求(额外的换行仅用于显示目的):
GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: http://server.example.com
授权响应:
code:必需的。授权服务器生成的授权码;
l state:必需的,若“state”参数在客户端授权请求中提交。从客户端接收的精确值;
例如,授权服务器通过发送以下HTTP响应重定向用户代理:
HTTP/1.1 302 Found
Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz
错误响应
error:必需的;
error_description:可选的,提供额外信息的人类可读的ASCII[USASCII]文本,用于协助客户端开发人员理解所发生的错误;
error_uri:可选的。指向带有有关错误的信息的人类可读网页的URI,用于提供客户端开发人员关于该错误的额外信息;
state:若“state”参数在客户端授权请求中提交则此参数为必需的;
示例:授权服务器通过发送以下HTTP响应重定向用户代理:
HTTP/1.1 302 Found
Location: https://client.example.com/cb?error=access_denied&state=xyz
3.1.2.2 访问令牌
请求方式:使用HTTP GET或POST方法请求;
请求参数:
grant_type :必需的。值必须为“authorization_code”;
code:必需的,从授权服务器收到的授权码;
redirect_uri:必需的,如果;
client_id:必需要的;
示例:客户端使用TLS发起如下的HTTP请求(额外的换行符仅用于显示目的):
POST /token HTTP/1.1
Host: http://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
授权响应:以JSON串方式
access_token:必需的。授权服务器生成的授权码;
token_type:必需的,若“state”参数在客户端授权请求中提交。从客户端接收的精确值;
expires_in:必需的;
refresh_token:可选的;
example_parameter:必需的;
其中响应头中:
Cache-Control必须为no-store
Pragma必须为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”
}
3.1.3 技术实现分析
A. 客户端操作
-
使用HTTP GET方法发送字段:response_type、client_id、redirect_uri、scope、state给授权服务器,以请求授权码;
-
客户端取到授权码后,向授权服务器发送GET或POST请求,发送字段grant_type、code、redirect_uri、client_id以请求访问令牌;
-
客户接收授权服务器传回的访问令牌JSON格式的响应,包括字段:access_toke,token_type,expires_in,refresh_token,example_parameter;
-
解析获取的访问令牌,然后向资源服务器发送HTTP GET请求,请求参数中包含Authorization(访问令牌)与请求的资源名称;
-
接收并解析资源服务的响应,获取最终的信息;
B. 浏览器(或客户端内嵌的)
- 浏览器显示客户端请求授权码时接收到的授权服务器传回的用户登录及授权页面;
- 用户输入用户名和密码,并确认授权后,提交给授权服务器;
- 接收授权服务器传回的授权码code、state、redirect_uri,并用redirect_uri将授权码重定向到客户端,重定向的请求中包含code和state参数;
C. 服务器(由客户端内部实现的)
- 接收浏览器重定向请求,并将请求中的授权码code解析出来给客户端使用;
D. 授权服务器
-
接收客户发来的请求授权码的请求;
-
向客户端发送要求用户登录,并确认授权的HTML页面;
-
验证客户端身份,验证成功后,需要作两件事:
a. 通过资源服务器在授权服务器注册的授权回调URI,将用户的身份信息合成相应的处理后,以参数形式回调给资源服务器,以供资源服务器验证访问令牌;
b. 向浏览器发送带有重定向redirect_uri和授权码code的HTTP请求; -
接收客户端发来的获取访问令牌的HTTP GET 或POST请求;
-
向客户发送带有以下字段的JSON格式的响应: access_toke,token_type,expires_in,refresh_token,example_parameter;
E. 资源服务器
- 接收授权服务器通过回调URI(此URI并不是客户端的重定向URI)发送来的用户信息;
- 接收客户端发送的资源请求名称与访问令牌Authorization;
验证访问令牌,验证通过后,将客户端想要获取的资源返回给客户端;
3.2 隐式许可
3.2.1 授权流程
隐式授权类型被用于获取访问令牌,并对知道操作具体重定向URI的公共客户端进行优化。这些客户端通常在浏览器中使用诸如JavaScript的脚本语言实现。
此流程也是基于重定向的流程,所以要求客户端必须:
- 能够与资源所有者的用户代理(通常是Web浏览器)进行交互;
- 能够接收来自授权服务器的传入请求(通过重定向);
注意:
- 此流程不支持发行刷新令牌;
- 此流程不包含客户端身份验证而依赖于资源所有者和重定向URI的注册;
具体流程如图4.1所示:
图4.2 隐式授权流程
在图4.1中所示的流程包括以下步骤:
-
假设是资源所有者想利用Web客户端访问资源所者其他网站上的内容;
-
客户端向授权端点发送客户端标识、请求访问的资源范围、本地状态、重定向URI以请求访问令牌;
-
如果资源所有者没有登录过,授权端点返回包含授权项的登录界面,让资源所有者登录;
-
资源所有者输入用户名与密码,并确认授权;
-
用户代理将资源所有者的授权确认与身份验证信息发送给授权服务器的授权端点;授权端点验证用户身份及授权;
-
授权服务器使用之前客户端提供的重定向URI重定向用户代理(IE浏览器)。在授权服务器返回的重定向URI的片段中包含访问令牌;
-
用户代理(IE浏览器)顺着重定向指示向Web托管的客户端资源发起请求,此请求不包含URI片段,用户代理(IE浏览器)保留片段信息;
-
Web托管的客户端返回一个网页,此网页中通常带有可以访问用户代理保留的片段的嵌入式脚本。
-
用户代理运行嵌入式脚本,提取访问令牌;
-
用户代理将访问令牌传送给客户端;
-
Web托管的客户端利用访问停牌访问资源服务器;
-
资源服务器验证令牌与客户身份然后返回相应的信息给客户端;
3.2.2 请求与响应
请求方式:使用HTTP GET方法请求;
请求参数:
response_type :必需的。值必须为“token”;
client_id:必需的;
redirect_uri:可选的;
scope:可选的;
state:推荐的;该参数应该用于防止跨站点请求伪造;
示例:客户端使用TLS定向用户代理发起下述HTTP请求(额外的换行仅用于显示目的):
GET /authorize?response_type=token&client_id=s6BhdRkqt3&state=xyz&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: http://server.example.com
返回响应:
access_token:必需的。授权服务器生成的授权码;
token_type:必需的,若“state”参数在客户端授权请求中提交。从客户端接收的精确值;
expires_in:必需的;
scope_token:若与客户端请求的范围相同,则是可选的;否则,是必需的;
state:若“state”参数在客户端授权请求中提交,则是必需的;
示例,授权服务器通过发送以下HTTP响应重定向用户代理:(额外的换行符仅用于显示目的):
HTTP/1.1 302 Found
Location:
http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA&state=xyz&token_type=example&expires_in=3600
错误响应:
error:必需的;
error_description:可选的,提供额外信息的人类可读的ASCII[USASCII]文本,用于协助客户端开发人员理解所发生的错误;
error_uri:可选的。指向带有有关错误的信息的人类可读网页的URI,用于提供客户端开发人员关于该错误的额外信息;
state:若“state”参数在客户端授权请求中提交则此参数为必需的;
示例:授权服务器通过发送以下HTTP响应重定向用户代理:
HTTP/1.1 302 Found
Location: https://client.example.com/cb#
3.2.3 资源所有者密码凭据
3.2.3.1 授权流程
此流程适合于资源所有者与客户端具有信任关系的情况,如设备操作系统、高特权应用。通过转换已存储的凭据为访问令牌,它也用于将使用如HTTP基本身份验证或摘要身份验证的直接身份验证方案的客户端迁移至OAuth2.0。
图3.3 资源所有者密码凭据授权流程
在图3.3中所示的流程包括以下步骤:
- 假设是资源所有者想利用客户端访问资源所者其他网站上的内容;
- 客户端要求资源所有者输入用户名与密码;
- 资源所有者输入用户名与密码并确认授权;
- 客户端向授权服务器的令牌端点发送资源所有者的密码凭据;若客户端是机密的或客户端被颁发了客户端凭据(或选定的其他身份验证要求),客户端需要同时发送客户端标识给令牌端点;
- 授权服务器验证客户端身份与资源所有者的密码凭据;
- 授权服务器给客户端颁发访问令牌;
- 客户端利用访问停牌访问资源服务器;
- 资源服务器验证令牌与客户身份然后返回相应的信息给客户端;
3.2.2 请求与响应
请求方式:使用HTTP GET或POST方法请求;
请求参数:
grant_type :必需的。值必须为“password”;
username:必需的,资源所有者的用户名;
password:必需的,资源所有者的密码;
scope:可选的;
client_id:若客户端是机密的或客户端被颁发了客户端凭据(或选定的其他身份验证要求),则必需要的;
示例:客户端使用TLS发起如下的HTTP请求(额外的换行符仅用于显示目的):
POST /token HTTP/1.1
Host: http://server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=johndoe&password=A3ddj3w
授权响应:以JSON串方式
access_token:必需的。授权服务器生成的授权码;
token_type:必需的,若“state”参数在客户端授权请求中提交。从客户端接收的精确值;
expires_in:必需的;
refresh_token:可选的;
example_parameter:必需的;
其中响应头中:
Cache-Control必须为no-store
Pragma必须为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”
}
3.3 客户端凭据
3.3.1 授权流程
当客户端请求访问它所控制的,或者事先与授权服务器协商(不属性OAuth2.0规范内容)的其他资源所有者的受保护资源,客户端可以只使用它的客户端凭据(或者其他支持的身份验证方法)请求访问令牌。此许可类型必须只能由机密客户端使用。
图3.4 客户端凭据认证流程
在图3.3中所示的流程包括以下步骤:
-
假设是资源所有者想利用客户端访问资源所者其他网站上的内容;
-
客户端向授权服务器的令牌端点发送请求;
-
授权服务器验证客户端身份;
-
授权服务器向客户端颁发访问令牌;
-
客户端利用访问停牌访问资源服务器;
-
资源服务器验证令牌与客户身份然后返回相应的信息给客户端;
3.3.2 请求与响应
请求方式:使用HTTP GET或POST方法请求;
请求参数:
grant_type :必需的。值必须为“client_credentials”;
scope:可选的;
client_id:若客户端是机密的或客户端被颁发了客户端凭据(或选定的其他身份验证要求),则必需要的;
示例:客户端使用TLS发起如下的HTTP请求(额外的换行符仅用于显示目的):
POST /token HTTP/1.1
Host: http://server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
授权响应:以JSON串方式
access_token:必需的。授权服务器生成的授权码;
token_type:必需的,若“state”参数在客户端授权请求中提交。从客户端接收的精确值;
expires_in:必需的;
refresh_token:可选的;
example_parameter:必需的;
其中响应头中:
Cache-Control必须为no-store
Pragma必须为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”
}
3.4 扩展机制
通过使用绝对URI作为令牌端点的“grant_type”参数的值指定许可类型,并通过添加任何其他需要的参数,客户端使用扩展许可类型。
例如,采用[OAuth-SAML]定义的安全断言标记语言(SAML)2.0断言许可类型请求访问令牌,客户端可以使用TLS发起如下的HTTP请求(额外的换行仅用于显示目的):
POST /token HTTP/1.1
Host: http://server.example.com
Content-Type: application/x-www-form-urlencodedgrant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type
26/51%3Asaml2bearer&assertion=PEFzc2VydGlvbiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDU[…为简洁起见省略…]aG5TdGF0ZW1lbnQ-PC9Bc3NlcnRpb24-
4 注意事项
以下内容是OAuth2.0中应该重点注意的内容和条款:
4.1 隐式许可
此流程中,授权服务器不对客户端进行身份验证,客户端的身份可以通过用于向Client传送访问令牌的重定向URI进行验证。
4.2 访问令牌
可以有不同的格式结构及采用的方法(如, 加密属性等)。
4.3 刷新令牌
刷新令牌设计只与授权服务器使用,并不会发送到资源服务器。
4.4 符号约定
除非另有说明,所有协议参数的名称和值都是大小敏感的。
4.5 授权端点
-
授权服务器对授权端点必须支持HTTP“GET”方法,也可以支持使用“POST”的方法。
-
发送的没有值的参数必须被对待为好像它们在请求中省略。
-
授权服务器必须忽略不能识别的请求参数。
-
请求和响应参数不能包含超过一次。
4.6 重定向端点
-
重定向端点URI不得包含片段部分。
-
必须注册重定向端点的客户端:公开客户端、采用隐式许可类型的机密客户端。
-
同一个客户端可以注册多个重定向端点;
-
客户端不应该在重定向端点的响应中包含任何第三方的脚本;
4.7 令牌端点
客户端必须使用HTTP “POST”方法发起访问令牌请求。