最近在项目中需要实现SSO
(单点登录)功能,以实现一处注册,即可在任何平台之间登录的功能。我们项目中并没有直接对接第三方认证系统而是通过集成keycloak
完成一系类安全协议的对接工作。如果我们在代码级别自己完成各种安全协议的对接是一项十分大的工程。不仅要走统一的一套安全体系还有兼容各种安全协议的对接。唉,想想就头疼。
为了简单高效实现SSO
功能,项目就集成了keycloak
.项目中只需要验证Keycloak
签发Token
即可。至于协议的对接交由keycloak
完成即可。
上图所示:keycloak
提供了各种协议的配置。我们只需简单配置就可以完成某种协议的对接工作。大大提高了开发效率。
前前后后陆陆续续对接了SAML
, OPENID
,OAuth2.0
等协议。 这个不得不说keycloak
对安全协议的集成还是很力的。在对接不同协议时,也想知道协议背后认证的执行流程及细节。今天我们就讨论一下今天的主题OAuth2.0
协议。
OAuth2.0
OAuth2.0
规范来自RFC-6749。事实上,核心OAuth
规范甚至不被称为规范。从技术上将,它是一个“框架”,可以用它来构建规范。这样做的部分原因是因为它留下了很多可选的东西,并要求实现者决定支持哪些授权类型,刷新令牌是否是一次性使用,甚至访问令牌是应该持有者令牌还是使用某种签名令牌机制。
这通常被认为是OAuth
的最大失败,但也是OAuth
在过去10年中成功部署在大型公司中的很大一部分原因。
在过去十年中,OAuth
在部署使用它的系统的经验中得到了很多修补和扩展。它以原作者甚至看不到的方式进行了扩展。请记住,当OAuth 2.0
在2012年发布时,iPhone 5
是全新的,Microsoft
的最新浏览器是Internet Explorer 9
,单页应用程序被称为“AJAX应用程序”,而CORS
还不是W3C
标准。
在传统的客户端-服务器身份验证模型中,客户端通过使用资源所有者的凭据向服务器进行身份验证,从而请求服务器上的访问受限资源(受保护的资源)。为了向第三方应用程序提供对受限资源的访问权限,资源所有者与第三方共享其凭据。这会产生一些问题和限制:
OAuth
通过引入授权层并将客户端的角色与资源所有者的角色分开来解决这些问题。在 OAuth
中,客户端请求访问由资源所有者控制并由资源服务器托管的资源,并颁发一组与资源所有者不同的凭据。
客户端不会使用资源所有者的凭据来访问受保护的资源,而是获取访问令牌 – 表示特定范围、生存期和其他访问属性的字符串。访问令牌由授权服务器在资源所有者的批准下颁发给第三方客户端。客户端使用访问令牌访问资源服务器托管的受保护资源。
此规范设计用于 HTTP
([RFC2616])。在 HTTP
以外的任何协议上使用 OAuth
都超出了范围。
OAuth
定义了四个角色:
+--------+ +---------------+
| |--(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
请求授权。授权请求可以由Client
直接向Resource Owner
发出,或者最好通过授权服务器间接发出(即通过重定向到Authorization Server
授权页)。推荐后者
B: Client
收到Resource Owner
的授权许可,授权许可的类型是此规范中定义的四种授权类型之一或者是一个扩展授权类型。授权类型取决于Client
请求授权的方法和Authorization Server
支持的授权类型。
C: Client
通过向授权服务器进行认证,且出示授权许可来请求access token
。
D: Authorization Server
鉴定客户端并且验证授权许可,如果有效,则颁发access token
。
E: Client
通过access_token
请求Resource Server
上的受保护资源。
F: Resource Server
鉴定access token
,如果有效,处理此次请求。
授权授予是一种凭据,表示客户端用于获取访问令牌的资源所有者的授权(访问其受保护的资源)。此规范定义了四种授权类型(授权代码、隐式、资源所有者密码凭据和客户端凭据),以及用于定义其他类型的扩展性机制。
通过使用授权服务器作为客户端和资源所有者之间的中介来获取授权代码。客户端不是直接从资源所有者请求授权,而是将资源所有者重定向到授权服务器(通过 [RFC2616] 中定义的用户代理),而授权服务器又使用授权代码将资源所有者重定向回客户端。
Client
引导资源拥有者访问授权服务器来发起授权,授权服务器引导用户携带授权码返回Client
之前,会认证资源拥有并生成授权码。因此用户的凭证并不会与Client
分享。之后使用授权码请求access token
。
上图GitHub按钮就是Client引导资源拥有者的方式,鼠标放上去时,浏览器坐下角的地址为:
https://ruby-china.org/account/auth/github
此请求返回302响应:
location:https://github.com/login/oauth/authorize?
clinet_id=f252166dc1760d1478aacd&redirect_uri=https%3A%2F@2Fruby-
china.org%2Fauth%2Fcallback&response_type=code&scope=user%3Aemail&state=2715116841611131dsds8114141
隐含模式是授权码模式的简化。隐含模式在用户授权后直接给Client
颁发access token
。
隐含模式颁发access token
时不会鉴定Client
,在某些情况下,可以使用redirect url
来验证Client
。
资源所有者密码凭据(即用户名和密码)可以直接用作获取访问令牌的授权。仅当资源所有者和客户端之间存在高度信任(例如,客户端是设备操作系统或高特权应用程序的一部分)以及其他授权授予类型(例如授权代码)不可用时,才应使用凭据。
尽管此授权类型需要客户端直接访问资源所有者凭据,但资源所有者凭据将用于单个请求,并交换为访问令牌。此授权类型可以通过将凭据与长期访问令牌或刷新令牌交换来消除客户端存储资源所有者凭据以供将来使用的需要。
客户端凭证模式适用于Client要访问的受保护资源本就在Client的控制下时,即客户端为自己行事(客户端也是资源拥有者)
访问令牌是用于访问受保护资源的凭据。访问令牌是一个字符串,表示向客户端颁发的授权。字符串通常对客户端不透明。令牌表示特定的访问范围和持续时间,由资源所有者授予,并由资源服务器和授权服务器强制执行。
令牌可以表示用于检索授权信息的标识符,也可以以可验证的方式自包含授权信息(即,由某些数据和签名组成的令牌字符串)。客户端可能需要超出此规范范围的其他身份验证凭据才能使用令牌。
访问令牌提供了一个抽象层,将不同的授权结构(例如,用户名和密码)替换为资源服务器理解的单个令牌。这种抽象使颁发的访问令牌比用于获取访问令牌的授权更严格,并且消除了资源服务器了解各种身份验证方法的需要。
根据资源服务器的安全要求,访问令牌可以具有不同的格式、结构和使用方法(例如,加密属性)。访问令牌属性和用于访问受保护资源的方法超出了本规范的范围,并由配套规范(如 [RFC6750])定义。
Refresh Token是Access Token过期、无效时获取新的access token是的凭证。它也有Authorization Server颁发,且与Access Token同时颁发。它对于Client也是不透明的。
+--------+ +---------------+
| |--(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 -------------| |
+--------+ +---------------+
A: 客户端通过向授权服务器进行身份验证并提供授权来请求访问令牌。
B: 授权服务器对客户端进行身份验证并验证授权授予,如果有效,则颁发访问令牌和刷新令牌。
C: 客户端通过提供访问令牌向资源服务器发出受保护的资源请求。
D: 资源服务器验证访问令牌,如果有效,则为请求提供服务。
E: 重复步骤 (C) 和 (D),直到访问令牌过期。如果客户端知道访问令牌已过期,则跳到步骤 (G);否则,它会发出另一个受保护的资源请求。
F: 由于访问令牌无效,资源服务器将返回无效令牌错误。
G: 客户端通过向授权服务器进行身份验证并提供刷新令牌来请求新的访问令牌。客户端身份验证要求基于客户端类型和授权服务器策略。
H: 授权服务器对客户端进行身份验证并验证刷新令牌,如果有效,则颁发新的访问令牌(以及可选的新刷新令牌)。
在Client使用服务提供商提供的登录服务之前,需要在服务提供商上注册Client
应用,具体注册方法OAuth2.0
不作要求,由实现者自行决定。
但是注册时Client
需要提供:
Client
类型Client
重定向URI
OAuth
定义了两种客户端类型,具体取决于它们向授权服务器安全进行身份验证的能力(即维护其客户端凭据机密性的能力):
客户端类型指定基于授权服务器对安全认证的定义及其可接受的客户端凭证暴露级别。授权服务器不应对客户端类型做出假设。
一般实现中不要求Client类型
OAuth2.0
是围绕以下Client
设计的:
web application
: web
应用 web
应用程序是在web
服务器上运行的机密客户端。资源所有者通过呈现在其用户代理(即浏览器)上的由Client
提供的UI
来访问Client
。客户端凭据以及发给客户端的任何访问令牌都存储在web
服务器上,并且不向资源所有者公开或由资源所有者访问。user-agent-based application
: 基于用户代理的应用程序 基于用户代理的应用程序是一个公共客户端,其中客户端代码从 Web
服务器下载,并在资源所有者使用的设备上的用户代理(例如 Web
浏览器)中执行。资源所有者可以轻松访问(并且通常可见)协议数据和凭据。由于此类应用程序驻留在用户代理中,因此它们可以在请求授权时无缝使用用户代理功能。native application
: 本机应用程序 本机应用程序是在资源所有者使用的设备上安装和执行的公共客户端。资源所有者可以访问协议数据和凭据。假定可以提取应用程序中包含的任何客户端身份验证凭据。另一方面,动态颁发的凭据(如访问令牌或刷新令牌)可以获得可接受的保护级别。至少,这些凭据受到保护,不会受到应用程序可能与之交互的恶意服务器的攻击。在某些平台上,这些凭据可能会受到保护,不会受到驻留在同一设备上的其他应用程序的影响。授权服务器向已注册的客户端颁发客户端标识符 – 表示客户端提供的注册信息的唯一字符串。客户端标识符不是机密;它向资源所有者公开,不得单独用于客户端身份验证。客户端标识符对于授权服务器是唯一的。
如果客户端类型为机密,则客户端和授权服务器建立适合授权服务器安全要求的客户端认证方法。授权服务器可以接受任何形式的客户端身份验证,以满足其安全要求。
机密客户端通常颁发(或建立)一组客户端凭据,用于向授权服务器进行身份验证(例如,密码、公钥/私钥对)。
授权服务器可以与公共客户端建立客户端身份验证方法。但是,授权服务器不得依赖公共客户端身份验证来标识客户端。
拥有Client Password
的客户端应该使用RFC2617中定义的HTTP Basic
认证方式与授权服务器进行认证。即客户端标识符作为username
,将username:password
使用application/x-www-form-urlencoded
编码,并添加到Authorization
请求头中。如:Authorization: Basic czZCaGRSa3F0Mzo3RmpmcDBaQnIxS3REUmJuZlZkbUl3。
或者,授权服务器可以提供接受request-body的认证接口,request-body包含:
虽然OAuth标准不建议使用第二种方案认证,但是真实场景中并不使用Basic认证方式,例如github的OAuth实现就是使用第二种方式,
但是使用时绝对不能让秘钥暴露在URI中。
授权过程中涉及两个授权服务器端点:
1. Authorization EndPoint
:Client
通过用户代理重定向到此端点,依此来获取授权。
2. Token EndPoint
:Client
携带授权许访问此端点,以交换access token
,此端点需要认证客户端。
和一个客户端端点:
3. Redirection EndPoint
:授权服务器通过用户代理重定向到此端点,以此向客户端返回包含授权凭证的响应
授权端点用于与资源拥有者交互并且获得其对Client
的授权许可。但在此之前,授权服务器需要先验证资源拥有者的身份(通过用户名、密码登录,或session cookie
等)。
获取授权端点的访问地址的方式OAuth2.0
不作规范,但是授权端点地址通常会在服务提供商的文档中给出。
由于授权端点的访问会导致用户认证和密码的明文传输,所以授权服务器必须使用TLS
(也就是授权服务器必须使用HTTPS
)
授权服务器必须支持GET方式请求,也应该支持POST方式请求。
我的理解是,授权端点包括授权页面和授权接口。
对端点的GET请求返回授权页面,资源拥有者在授权页面同意授权后,对端点进行POST请求以生成正真的授权。
授权端点的参数:
client_id
: 在lattice
中注册应用时生成的id
redirect_uri
:注册应用时指定的callback URL
response_type
:授权类型 code
为授权码类型scope
:需要授权范围state
: client server
生成的随机状态码授权端点只在授权码模式和隐含模式流程中使用。Client
使用以下参数告知授权服务器自己想要的授权类型:
response_type
: 参数值为"code
"时,授权类型为授权码;为"token
"时,授权类型为隐含模式。参数值也可以是其它的扩展值。如果授权请求没有携带respone_type参数,或者参数值不受授权服务器支持时,授权服务器必须返回一个如4.1.2.1的错误响应
在授权服务器完成与资源拥有者的交互后,授权服务器会将用户引导回Client
(也就是返回302响应,重定向到Client
提供的UI
)。
重定向的URI
是在Client
向服务提供商注册自身时提供的URI
,或者是在Client
引导资源拥有者执行授权请求时所指定的redirect_uri
参数中的值。
当重定向请求中包含敏感信息时,重定向端点必须使用TLS
,也就是要使用HTTPS
。
但是由于使用HTTPS
对于大部分Client
开发者来说有一些障碍(证书贵,免费的证书没有完整的授权链路等),所以如果Client
不使用HTTPS
,则授权服务器必须在重定向之前提醒资源拥有着重定向端点的不安全性。
授权服务器必须要求以下类型的Client
注册他们的重定向端点:
Public Clients
Confidential Clients
也就是说,在Client在向服务提供商注册自身时必须提供重定向端点。
服务提供商应要求Client
提供完整的重定向URI, If requiring the registration of the complete redirection URI is not possible, the authorization server SHOULD require the registration of the URI scheme, authority, and path (allowing the client to dynamically vary only the query component of the redirection URI when requesting authorization)。我的理解是,如果不能注册完整的URI
,则应该注册URI
的部分结构,让Client
在请求授权时指定redirect_uri
时,只能变更相应的查询参数。
比如,如果Client
注册的是https://example.client/index?custom_arg=12
,则在Client
请求授权时指定的redirect_uri
的值可以是:
https://example.client/index?custom_arg=1&append_arg=123
或https://example.client/index/callback?custom_arg=1&append_arg=123
等。也就是说注册时的URI
格式必须包含在redirect_uri
中。
服务提供商应该允许注册多个重定向端点。
如果不要求注册重定向端点,会导致攻击者将授权服务器用做开放的重定向器。也就是发送授权请求时redirect_uri
不是已注册Client
的重定向端点,而服务提供商没有要求Client
注册重定向端点,则这个redirect_uri
无从验证。
如果Client
在服务提供商中注册了多个重定向端点,或只注册了重定向端点的部分,或没有注册重定向端点,则Client
在发起授权请求时必须带上redirect_uri
参数。
当Client
在授权请求中提供了redirect_uri
,授权服务器必须将其值与已注册的重定向URI
进行比较,确保其有效性。如果没有注册重定向URI
,那么没办法,只能直接使用这个URI
。
如果授权服务器发现授权请求中的重定向URI
无效,授权服务器必须提醒用户,并且不能自动重定向到这个无效URI
。
通常,对重定向端点的请求最终都会返回HTML
响应。如果HTML
响应直接由重定向端点返回,任何在这个HTML
文档上的脚本都能读这个取重定向URI
,即能获取授权凭证。
因此,重定向端点的响应内容不应该包含第三方脚本。
为了防止第三方脚本读取凭证:
Client
要让自身的脚本把URI
中的授权凭证提取出来,也就是重写URI
,要确保Client
脚本在第三方脚本之前执行。
或者,重定向端点应该从其URI
中提取出授权凭证,然后将用户代理重定向到另一个Client
端点,重定向时授权凭证不能暴露在URI
中。
Client
携带授权许可或refresh token
来访问token
端点以获取获取access token
。除了隐含模式,任何授权类型都可以访问token
端点(隐含模式直接颁发access token
,没必要访问token
端点)。
Client
如何获取troken
端点的URI
本规范不作要求,但是服务提供商文档中一般会给出。token
端点要使用post
请求,且请求参数的Content-Type
为application/x-www-form-urlencoded
。token
端点必须使用HTTPS
。
当请求Token
端点时,保密Client
或其它被颁发凭据的客户端必须与授权服务器进行身份认证。Client
认证是为了:
refresh token
和授权码绑定到它们被颁发的Client
。当授权码通过不安全的通道传递到重定向端点或重定向端点没有注册时,Client
认证非常由必要。Client
泄漏时,防止攻击者滥用盗取的refresh token
,并且可以通过改变客户端凭据来恢复客户端的保密性。且更改客户端凭据比警用刷新令牌快的多。Client
凭据。刷新Client
凭据要比刷新refresh token
更快。Client
应该在访问token
端点时携带client_id
参数来标识本身。且client_id
参数可以用来防止授权码被替换而不自知。
授权端点和token
端点允许Client
通过"scope
"请求参数指定要获取的access_token
的作用域。相应的,授权服务器使用"scope
"响应参数来告知Client
它颁发的access_token
的作用域。
scope
参数的值是一个大小写敏感的以空格分隔的字符串列表。列表中的每个字符串都有授权服务器定义,每个字符串代表一个作用域。
基于授权服务器的策略和资源拥有者的指示,授权服务器应该忽略Client
请求的部分作用域,也就是说,Client
要求的作用域只起声明作用,真正要给予它的作用域主要依靠资源拥有着的授权。如果授权服务器颁发的access_token
的作用域与Client
要求的作用域不一样,授权服务器要在响应中带上scope
参数来告知Client
被授予的实际作用域。
如果Client
请求token
端点时没有指定scope
参数,授权服务器可以:
scope
无效而拒绝此请求服务提供商应该在其文档中明确指出其支持的授权作用域或默认作用域。
为了获取access token
,Client
要从资源拥有者手中获取授权。授权以授权许可的形式表示,Client
使用授权许可来获取access token
。OAuth
定义了四种授权许可类型:授权码,隐含授权,资源拥有者凭证和客户端凭据。同样只支持自定义的扩展授权类型。
授权码类型的许可被用来获取access token
和 refresh token
,对保密客户端来说它是最佳的选择。
因为授权码授予是一个基于重定向的流程,Client
必须具有与用户代理交互的能力,它也需要能够接收从授权码服务器发送的请求。
直白点就是Client要能够将用户代理重定向到授权端点,授权端点要能够将用户代理重定向到重定向端点。
授权码授权流程:
+----------+
| 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步骤由于要经过用户代理,所以被分为了两部分。
A: Client
使用302响应将资源拥有者的用户代理重定向到授权端点。
B: 授权服务器认证用户,并确定资源拥有者是准许还是拒绝Client
的授权请求。
C: 假设资源拥有者准许,授权服务器将资源拥有者的用户代理重定向到A
步骤中指定的重定向端点URI
,重定向端点URI
的路径参数中将包含授权码和Client
提供的state
。 资源拥有者要准许授权,也必须要通过用户代理向授权服务器发送准许授权请求,也就是点击同意授权按钮时会发送这个请求,准许授权请求将返回302响应,用户代理籍此响应重定向
D: Client
携带上一步中获得的授权码和重定向端点的URI
去获取access token
。 在这个请求中,授权服务器需要认证Client
的身份。认证方法就是,Client
在请求时,将请求授权时指定的redirect_uri
(也就是A步骤指定的)也附加到请求参数中。
E: 授权服务器对Client
进行认证,验证授权码,确保redirect_uri
参数值域步骤C中的重定向端点URI
相同。如果这些验证通过,授权服务器返回access token
,且选择性的也返回refresh token
,这取决于授权服务器的策略。
授权请求就是A步骤中302响应要发出的请求。Client
通过将以下参数以"application/x-www-form-urlencoded
"的形式添加到授权端点URI
后来构造这个请求,参数为:
response_type(REQUIRED)
:授权码模式中固定为“code
”,它表示授权许可类型client_id(REQUIRED)
:向服务提供商注册Client
后得到的client id
,它标识这个授权请求是对哪个Client
的授权redirect_uri(OPTIONAL)
:重定向端点URI
,如果Client
在向服务提供商注册自身时只提供了一个重定向端点URI
,那么可以不用填;scope(OPTIONAL)
:Client
申请的权限范围state(OPTIONAL BUT RECOMMENDED)
:Client
用于维护授权端点与重定向端点之间状态的不透明值。此参数用于防止csrf
攻击。授权服务器在接收到授权请求时,必须保证所有的参数是有效且正确的。确认请求有效后,授权服务器将认证资源拥有者然后获取它的授权决定。当资源拥有者决定后,授权服务器通过302响应将用户代理重定向到redirect_uri
。
授权服务器在获得资源拥有着的授权后要做两件事:颁发授权码给Client
;将用户代理重定向到重定向端点。因为重定向端点属于Client
,所以这两件事可以通过授权请求的302响应同时完成。
即授权响应可以将授权码与重定向端点URI
结合。结合方式就是,将以下参数以"application/x-www-form-urlencoded
"的形式添加到重定向端点URI
,然后将结合体作为302响应的location
响应头的值,参数为:
code(REQUIRED)
:授权服务器生成的授权码,为了降低泄露它产生的风险,code
通常只有10分钟的有效期,且只能被使用一次。如果一个code
被重复使用,授权服务器应该拒绝请求并且将之前基于此code
颁发的所有token
都撤销。授权码与client id
和重定向端点URI
绑定。state
:如果授权请求中有这个参数,那么授权响应也必须包含它,且值与授权请求中的值保持一致。Client必须忽略不认识的响应参数。
响应示例:
HTTP/1.1 302 Found
Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA
&state=xyz
如果授权请求是由于重定向URI
的缺失、无效或不匹配,或client id
的缺失或无效而被认定为是无效的。授权服务器要以某种方式告知资源拥有者,且不能自动重定向到无效的redirect_uri
。
如果授权请求是由于除上述两个请求参数外的其它参数错误和资源拥有者拒绝授权而被认定为失败,授权服务器应该将以下参数以"application/x-www-form-urlencoded
"的形式添到重定向端点URI
中来告知Client
请求授权的原因,参数为:
error(REQUIRED)
:错误代码,其值可以为:invalid_request
:请求缺失必要参数、包含无效参数值、包含多个相同参数或包含其它畸形参数unauthorized_client
:Client
无权使用此方法获取授权代码access_denied
:资源拥有着拒绝授权 unsupported_response_type
:授权服务器不支持使用此返回获取授权代码(用户代理不支持在Location
响应头中包含Query
参数时使用此代码) invalid_scope
:授权请求的scope
值无效、位置、或畸形server_error
:授权服务器内部异常,阻止其完成请求(因为503HTTP状态代码不能通过302重定向来传递,所以需要此错误代码)temporarily_unavailable
:由于授权服务器过载或维护,无法处理授权请求(也是与f一样的原因而需要此代码)error_description(OPTIONAL)
:错误描述信息,不能包含ASCII中%x20-21 / %x23-5B / %x5D-7E error_uri(OPTIONAL)
:关于错误信息解读的网页URI
state
:如果授权请求中有这个参数,那么授权响应也必须包含它,且值与授权请求中的值保持一致。错误响应示例:
HTTP/1.1 302 Found
Location: https://client.example.com/cb?error=access_denied&state=xyz
Client
通过携带以下参数向Token
端点发送请求,这些参数以UTF-8
编码并包含在request body
中,且请求的Content-Type
为"application/x-www-form-urlencoded
",参数为:
grant_type(REQUIRED)
:授权码授权中,只能设置为"authorization_code
". code(REQUIRED)
:获取到的授权码.redirect_uri
:如果授权请求中有此参数,则此参数必填,且值与授权请求中的值相同client_id
:如果Client
还没有与授权服务器进行认证,则必填。虽然rfc6749说的是if the client is not authenticating with the authorization server as described in Section 3.2.1。但是client_id其实永远都要填,因为授权码与client id和重定向端点URI绑定
在请求token端点时,保密Client或其它被颁发凭据的客户端必须与授权服务器进行身份认证,认证方法参考2.3.1。
请求示例:
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
如果对token
端点的请求有效且资源拥有者已对Client
授权,那么授权服务器将给Client
颁发access token
(选择性颁发refresh token
)具体响应在5.1;如果Client
认证失败,则返回错误响应,具体响应在5.2。
响应示例:
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
(不支持获取refresh token
),对操作已知的重定向URI
的公共客户端来说是最佳的选择。
隐含授权也是基于重定向的流程。不同于授权码授权将授权请求和access token
请求分开,隐含授权的授权请求直接返回access token
。
因为access token
将附在重定向端点URI
上返回,所以access token
会暴露给资源拥有着和设备上的其它应用;因此隐含授权不包括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 |
| |
+---------+
A: Client
通过将资源拥有者的用户代理重定向到授权端点开始授权流程;重定向请求要包含client id
、scope
、state
和redirect uri
等参数
B: 授权服务器认证资源拥有者,并确定资源拥有者是准许还是拒绝Client
的授权请求
C: 假设资源拥有者同意授权,授权服务器使用302响应将资源拥有者的用户代理重定向回Client
,重定向URI
为A步骤中指定的redirect uri
,且重定向URI
中将包含access token
。
D: 资源拥有者的用户代理遵循收到的302响应来请求相应的Client
资源(Client
接收此请求的方法不应该接收请求参数),请求参数应由用户代理保存在本地。
就是说重定向端点接收请求时忽略参数,而用户代理保存参数的方法就是不管它,让他在URI中待着。
E: Client
资源返回一个能够处理完整重定向URI
和提取access token
的网页(也就是内嵌js的HTML文档)。
F: 用户代理执行Client
资源返回的脚本来提取access token
。
G: 用户代理将access token
传递给Client
。
隐含授予的授权请求与授权码授予的授权请求大致一样,不同之处在于response_type
的值必须为token
.
请求示例:
GET /authorize?response_type=token&client_id=s6BhdRkqt3&state=xyz
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: server.example.com
rfc6749将此节命名为access token response,个人觉得还是叫授权响应更好
隐含授予的授权请求的响应直接包含access token
,所以当资源拥有者同意授权后,授权服务器直接给Client
颁发access token
(绝对对不能颁发refresh token
),并将它以"application/x-www-form-urlencoded
"格式编码然后附在重定向端点URI
后作为参数。响应要携带的所有参数为:
access_token(REQUIRED)
token_type(REQUIRED)
:具体值参照7.1expires_in(RECOMMENDED)
:表示access token
从颁发到过期的时间间隔,单位为秒。如果忽略它,那么授权服务器应该以其它方式指定过期时间,且应在服务提供商文档中给出默认值。scope
:代表授权服务器颁发的access token
的作用域,如果授权的作用域与Client
请求的作用域相同,可以选择性填写;如果授权的作用域与Client
请求的作用域不同,则必须填写state
:如果授权请求中包含此参数,则必填,且值要与授权请求中的值保持一致。成功响应示例:
HTTP/1.1 302 Found
Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA
&state=xyz&token_type=example&expires_in=3600
开发者应该注意:
某些用户代理可能不支持在在302响应的Location响应头中包含Query参数,所以可能需要使用其它方法来重定向到重定向端点。比如,同意授权后的响应返回一个HTML页面,页面中包含一个“continue”按钮,使用这个按钮来重定向到重定向端点。
隐含授权的错误响应与授权码授权的错误响应一致。
错误响应示例:
HTTP/1.1 302 Found
Location: https://client.example.com/cb#error=access_denied&state=xyz
此授权类型在资源拥有者高度信任Client
的场景中适用(例如:Client
是操作系统,或Client
是服务提供商旗下应用)。授权服务器应只在其它授权类型不可用时开启此授权类型,且启用此授权类型时要多加注意。
它适用于能够获取资源拥有者凭证(用户名和密码)的Client
。也适用于将现有的、使用如HTTP Basic
认证等直接认证规范的Client
迁移到OAuth
规范。迁移方法是通过将Client
存储的凭证转换为access token
。
授权流程:
+----------+
| 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: 资源拥有者向Client
提供其凭证,也就是用户名和密码。
B: Client
携带资源拥有者凭证向token
端点直接请求access token
。请求时,Client
要与授权服务器进行认证。
C: 授权服务器认证Client
,并且验证资源拥有者凭证。如果凭据有效,则颁发access token
可以看出此授权类型只涉及token端点
Client
获取资源拥有者的凭证的方法此规范不作要求。但Client
使用凭证获取到access token
后必须丢弃凭证。
补充:
此授权类型的请求授权方式与其它类型不同。当用户想要登录Client时,Client提供的登录表单就是授权请求,提交表单发出的请求就是授权响应。
Client
应使用"application/x-www-form-urlencoded
"格式将参数放在请求体中来请求token
端点,也就是POST
请求,参数如下:
grant_type(REQUIRED)
:此授权类型下,必须设置为"password
"username(REQUIRED)
:资源拥有者用户名password(REQUIRED)
:资源拥有者密码scope(OPTIONAL)
:Client
想要的授权作用域Client
是保密客户端或者被颁发了凭据,Client
就必须与授权服务器进行认证,认证方法参考.请求示例:
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=A3ddj3w
如果请求有效且所有认证通过,授权服务器要颁发如5.1所述的access token
(选择性颁发refresh token
)。
反之,授权服务器返回如5.2所述的错误响应。
成功响应示例:
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
想要访问在它自己掌握下的资源,或者那些已由资源拥有者授权给Client
访问的资源时,Client
只需要使用其自身的凭证来获取access_token
。
只有保密客户端才能使用Client
凭据授权
授权流程:
+---------+ +---------------+
| | | |
| |>--(A)- Client Authentication --->| Authorization |
| Client | | Server |
| |<--(B)---- Access Token ---------<| |
| | | |
+---------+ +---------------+
A: Client
携带自身凭证请求token
端点以获取access token
。
B: 授权服务器对Client
进行认证。如果验证通过,则颁发access token
。
因为Client认证本身就可以用做权限授予,所以不需要额外的授权请求。
Client
将请求参数以"application/x-www-form-urlencoded
"编码并放在请求体中来请求token
端点以获取access token
,参数为:
grant_type(REQUIRED)
:Client
凭据授权中,此参数值必须为"client_credentials
"scope(OPTIONAL)
:Client
请求授权的作用域请求示例:
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
如果对token
端点的请求有效且Client
认证通过,则颁发如5.1所述的access token
,但是不能颁发refresh token
。
否则,授权服务器返回如5.2所述的错误响应。
响应示例:
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"
}
如果对token
端点的请求有效且Client
认证通过,则颁发如5.1所述的access token
,选择性颁发refresh token
。
否则,授权服务器返回如5.2所述的错误响应。
成功响应(200)包含以下响应参数:
access_token(REQUIRED)
:授权服务器颁发的access token
token_type(REQUIRED)
:具体值参照7.1expires_in(RECOMMENDED)
:表示access token
从颁发到过期的时间间隔,单位为秒。如果忽略它,那么授权服务器应该以其它方式指定过期时间,且应在服务提供商文档中给出默认值。refresh_token
:授权码收授权和资源拥有者密码凭证授权选填,Client
授权和隐含授权不填scope
:代表授权服务器颁发的access token
的作用域。在授权码收钱中不要写,因为在其授权请求与响应阶段就已经确定了作用域。对于其它授权类型,如果授权的作用域与Client
请求的作用域相同,可以选择性填写;如果授权的作用域与Client
请求的作用域不同,则必须填写。application/json
"。如果授权服务器的响应包含任何token
、凭证或者其它敏感信息时,必须将"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"
}
Client
必须忽略响应中不认识的参数。在服务提供商文档中应该给出任何授权服务器返回的任何值得长度和格式。
错误响应(400)包含以下参数:
error(REQUIRED)
:错误代码,其值可以为:invalid_request
:请求缺失必要参数、包含无效参数值、包含多个相同参数或包含其它畸形参数invalid_client
:Client认证失败。The authorization server MAY return an HTTP 401 (Unauthorized) status code to indicate which HTTP authentication schemes are supported. If the client attempted to authenticate via the “Authorization” request header field, the authorization server MUST respond with an HTTP 401 (Unauthorized) status code and include the “WWW-Authenticate” response header field matching the authentication scheme used by the client。 这个解释还是用原文比较好理解。invalid_grant
:提供的授权许可或refresh token
无效、过期或被撤销,或者它们与redirect uri
不匹配。unauthorized_client
:Client
无权使用此授权类型获取授权代码unsupported_grant_type
:授权服务器不支持此授权类型invalid_scope
:授权请求的scope
值无效、位置、或畸形error_description(OPTIONAL)
:错误描述信息,不能包含ASCII中%x20-21 / %x23-5B / %x5D-7Eerror_uri(OPTIONAL)
:关于错误信息解读的网页URI
这些响应参数应该放在响应体中,且媒体类型为"application/json
"。
错误响应示例:
HTTP/1.1 400 Bad Request
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
"error":"invalid_request"
}
如果授权服务器给Client
颁发了refresh token
,Client
将请求参数以"application/x-www-form-urlencoded
"格式编码并添加到请求体中来向token
端点发送刷新请求,请求参数为:
1.grant_type(REQUIRED)
:值必须是"refresh_token
"
2. refresh_token(REQUIRED)
:颁发给Client
的refresh token
3. scope(OPTIONAL)
:请求的授权作用域。scope
的值不得包含资源拥有者最初未授予Client
的作用域,若果省略scope
,那么默认其值为资源拥有者最初授予Client
的作用域。
因为refresh token
是长期凭证,所以它会被绑定到它被颁发的Client
。如果Client
是保密的或被颁发了凭证,Client
必须与授权服务器进行认证,认证方法如3.2.1所述。
请求示例:
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
收到请求后,授权服务器必须:
Client
和被颁发了凭证的Client
refresh token
是被颁发为认证Client
的refresh token
如果认证成功且验证通过,授权服务器将颁发access token
给Client
。如果请求无效或没通过验证,授权服务器返回如5.2的错误响应。
授权服务器也要颁发新的refresh token
给Client
以替换旧的,然后撤销Client
与旧refresh token
的绑定,并将新refresh token
与Client
绑定。新refresh token
的作用域要与旧的一样。
Client
携带access token
以访问受保护资源。资源服务器必须验证access token
,确保它没有过期,并且确保它的作用域包含要请求的资源。资源服务器验证access token
的方法本规范不做要求,但是通常包含资源服务器与授权服务器的交互和协作。
Client
利用access token
向资源服务器进行认证的方法取决于授权服务器颁发的access token
的类型。
Access token
类型为Client
提供了能够成功利用access token
访问受保护资源的必要信息,Client
不应在不知道access token
的类型下使用它。
具体的Access Token
类型用例,参照RFC6759。
我们一般使用的类型为Bearer
类型。
请求示例:
GET /resource/1 HTTP/1.1
Host: example.com
Authorization: Bearer mF_9.B5f-4.1JqM
Authorization请求头中,Bearer为access token类型,后面的字符串为access token
每个access token类型定义指定了授权服务器颁发给Client access token时要返回的额外参数(如果存在)。同样也定义了Client访问受保护资源时使用的认证方法。
如果资源访问请求失败。资源服务器应该告知Client
失败的原因。虽然错误的响应规范超出了本文档的范围,但是总的来说,一个错误响应选择应该包含以下信息以与本规范其它错误响应对应:
error_description
error_uri
参考:
RFC-6749
是时候推出 OAuth 2.1 了