OAuth通过引入授权层以及分离客户端角色和资源所有者角色来解决第三方授权中的问题。客户端获得一个访问令牌(一个代表特定作用域、生命期以及其他访问属性的字符串),用以代替使用资源所有者的凭据来访问受保护资源。访问令牌由授权服务器在资源所有者认可的情况下颁发给第三方客户端。客户端使用访问令牌访问托管在资源服务器的受保护资源。OAuth 2.0协议与OAuth 1.0协议实现细节没有太多关联。
OAuth 2.0 流程主要包含以下步骤:
A: 用户打开客户端后,客户端要求用户授权。 B: 用户同意给予客户端授权。 C: 客户端利用授权向认证服务器请求令牌。 D: 认证服务器认证通过,发放令牌。 E: 客户端使用令牌,向资源服务器申请获取资源。 F: 资源服务器确认令牌,返回资源信息。
在OAuth流程之前,首先要注册一个app。注册信息应包含name,website,logo等,除此之外,必须包含 redirct URI用来将用户跳转到web server, 浏览器应用或移动app。
OAuth只会将用户重定向到注册URI,这可以防止一些攻击。所有HTTP redirect URI必须使用TLS,因此只会重定向”https”开头的URI,这可以防止授权过程中的数据拦截。原生应用可能注册demoapp://redirect类型URI。
注册app后会受到client id和secret。client id是公开信息,用户构建URL或包含在Javascript中,密钥必须保密,如果部署的应用程序无法保密,则不会使用该密钥。
授权许是一个可代表资源所有者授权访问受保护资源的凭据,客户端用它来获取访问令牌。授权过程对应上节步骤A, B。本文定义了四种许可类型:授权码,隐式许可,资源所有者密码凭据和客户端凭据。
授权码模式是功能最完整、流程最严密的授权模式,一般用于web server app, 桌面或移动app。客户端通过后台服务器与服务提供商进行交互。流程如下:
+----------+ | 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)A: 用户访问客户端,后者将前者导向认证服务器。 B: 用户选择是否给予客户端授权。 C: 假设用户给予授权,认证服务器将用户导向客户端事先指定的”重定向URI”(redirection URI),同时附上一个授权码。 D: 客户端收到授权码,附上早先的”重定向URI”,向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。 E: 认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。
A步骤实现授权逻辑应首先创建一个登录link发送给用户:
GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz &redirect_uri=https://client.example.com/cb HTTP/1.1 Host: server.example.com应包含以下参数: - response_type:表示授权类型,必选项,此处的值固定为”code” - client_id:表示客户端的ID,必选项 - redirect_uri:表示重定向URI,可选项 - scope:表示申请的权限范围,可选项 - state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。
B步骤用户应该看到这样一个页面:
用户Allow之后,进入C步骤。
C步骤,服务将用户导入redirect_uri:
HTTP/1.1 302 Found Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA &state=xyz请求包含以下参数: - code:表示授权码,必选项。该码的有效期应该很短,通常设为10分钟,客户端只能使用该码一次,否则会被授权服务器拒绝。该码与客户端ID和重定向URI,是一一对应关系。 - state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。
D步骤,客户端向认证服务器申请令牌(Token exchange):
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://client.example.com/cb请求应包含以下参数: - grant_type:表示使用的授权模式,必选项,此处的值固定为”authorization_code”。 - code:表示上一步获得的授权码,必选项。 - redirect_uri:表示重定向URI,必选项,且必须与A步骤中的该参数值保持一致。 - client_id:表示客户端ID,必选项。
E步骤认证服务器回应token exchange请求:
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:表示权限范围,如果与客户端申请的范围一致,此项可省略。
隐式授权类型被用于获取访问令牌(它不支持发行刷新令牌),并对知道操作具体重定向URI的公共客户端进行优化。这些客户端通常在浏览器中使用诸如JavaScript的脚本语言实现。
不同于客户端分别请求授权和访问令牌的授权码许可类型,客户端收到访问令牌作为授权请求的结果。
隐式许可类型不包含客户端身份验证而依赖于资源所有者在场和重定向URI的注册。因为访问令牌被编码到重定向URI中,它可能会暴露给资源所有者和其他驻留在相同设备上的应用。
+----------+ | 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 | | | +---------+隐式授权模式包含以下步骤:
A: 用户访问客户端,后者将前者导向认证服务器。 B: 用户选择是否给予客户端授权。 C: 假设用户给予授权,认证服务器将用户导向客户端指定的”重定向URI”,并在URI的Hash部分包含了访问令牌。 D: 浏览器向资源服务器发出请求,其中不包括上一步收到的Hash值。 E: 资源服务器返回一个网页,其中包含的代码可以获取Hash值中的令牌。 F: 浏览器执行上一步获得的脚本,提取出令牌。 G: 浏览器将令牌发给客户端。
A步骤客户端发出请求:
GET /authorize?response_type=token&client_id=s6BhdRkqt3&state=xyz &redirect_uri=https://client.example.com/cb HTTP/1.1 Host: server.example.com请求包含以下参数: - response_type:表示授权类型,此处的值固定为”token”,必选项。 - client_id:表示客户端的ID,必选项。 - redirect_uri:表示重定向的URI,可选项。 - scope:表示权限范围,可选项。 - state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。
B步骤用户应该看到这样一个页面:
用户Allow之后,进入C步骤。
C步骤认证服务器回调客户端URI:
HTTP/1.1 302 Found Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA &state=xyz&token_type=example&expires_in=3600请求包含以下参数: - access_token:表示访问令牌,必选项。 - token_type:表示令牌类型,该值大小写不敏感,必选项。 - expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。 - scope:表示权限范围,如果与客户端申请的范围一致,此项可省略。 - state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。
D步骤用户代理转向redirect_uri,但是不带Hash信息,代理本地保存Hash信息。
E步骤客户端托管的web返回网页,通常是一个嵌入脚本的HTML文档。
F步骤代理本地执行Web托管客户端资源提取的脚本
G步骤
用户向客户端提供自己的用户名和密码。客户端使用这些信息,向”服务商提供商”索要授权。
在这种模式中,用户必须把自己的密码给客户端,但是客户端不得储存密码。这通常用在用户对客户端高度信任的情况下,比如客户端是操作系统的一部分,或者由一个著名公司出品。而认证服务器只有在其他授权模式无法执行的情况下,才能考虑使用这种模式。
+----------+ | Resource | | Owner | | | +----------+ v | Resource Owner (A) Password Credentials | v +---------+ +---------------+ | |>--(B)---- Resource Owner ------->| | | | Password Credentials | Authorization | | Client | | Server | | |<--(C)---- Access Token ---------<| | | | (w/ Optional Refresh Token) | | +---------+ +---------------+执行步骤如下:
A: 用户向客户端提供用户名和密码。 B: 客户端将用户名和密码发给认证服务器,向后者请求令牌。 C: 认证服务器确认无误后,向客户端提供访问令牌。
A步骤由Client实现。
B步骤应包含以下参数: - grant_type:表示授权类型,此处的值固定为”password”,必选项。 - username:表示用户名,必选项。 - password:表示用户的密码,必选项。 - scope:表示权限范围,可选项。
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=A3ddj3wC步骤认证服务器响应
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" }当客户端请求访问它所控制的,或者事先与授权服务器协商(所采用的方法超出了本规范的范围)的其他资源所有者的受保护资源,客户端可以只使用它的客户端凭据(或者其他受支持的身份验证方法)请求访问令牌。
客户端凭证流程:
+---------+ +---------------+ | | | | | |>--(A)- Client Authentication --->| Authorization | | Client | | Server | | |<--(B)---- Access Token ---------<| | | | | | +---------+ +---------------+客户端包含以下步骤:
A: 客户端与授权服务器进行身份验证并向令牌端点请求访问令牌。 B: 授权服务器对客户端进行身份验证,如果有效,颁发访问令牌。
A步骤中,客户端发出的HTTP请求,包含以下参数: - granttype:表示授权类型,此处的值固定为”clientcredentials”,必选项。 - scope:表示权限范围,可选项。
POST /token HTTP/1.1 Host: server.example.com Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW Content-Type: application/x-www-form-urlencoded grant_type=client_credentials认证服务器验证身份。
B步骤中,认证服务器向客户端发送访问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, "example_parameter":"example_value" }如果用户访问的时候,客户端的”访问令牌”已经过期,则需要使用”更新令牌”申请一个新的访问令牌。 客户端发出更新令牌的HTTP请求,包含以下参数:
granttype:必需的。值必须设置为“refresh_token”。refresh_token:必需的。颁发给客户端的刷新令牌。scope:表示申请的授权范围,不可以超出上一次申请的范围,如果省略该参数,则表示与上一次一致。 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授权服务器可以颁发新的刷新令牌,在这种情况下,客户端必须放弃旧的刷新令牌,替换为新的刷新令牌。在向客户端颁发新的刷新令牌后授权服务器可以撤销旧的刷新令牌。若颁发了新的刷新令牌,刷新令牌的范围必须与客户端包含在请求中的刷新令牌的范围相同。
OAuth 2 Simplified OAuth 2.0 oauth2-rfc6749
