OAuth是第三方应用授权(Authorization)的开放标准,目前最新版本是2.0,以下将要介绍的内容和概念主要来源于该版本。 恐篇幅太长,OAuth 的诞生背景就不在这里赘述了,可参考 The OAuth 2.0 Authorization Framework。
四种角色定义:
- Resource Owner:资源所有者,即终端用户
- Resource server:资源服务器,即提供资源存储访问一方
- Client:通常指第三方应用
- Authorization server:授权服务器
协议端点(URI):OAuth给授权过程定义了Authorization、Token和Redirection三种端点。Authorization端点用来完成用户授权,在授权码模式(Authorization Code)和隐含模式(Implicit)下被用到;Token端点用来交换与获取access token,不能包含fragment(hash),在隐含模式(Implicit)下则无需提供该端点;Redirection端点用来接收授权凭证,Public客户端或者Implicit授权的Confidential客户端必须注册其Redirection端点。
客户端类型:OAuth根据是否能够进行安全认证定义了两种客户端类型:机密型客户端(Confidential)和公开型客户端(Public)。其中机密型客户端有Web应用,公开型客户端包括User Agent Based和Native应用。客户端的类型注册时确定,不能由授权服务器假定。如果一套应用包含多个不同类型的客户端,这些不同部分应分开单独注册。
客户端认证(Client Authentication):客户端认证当满足授权服务器的安全要求,对机密型客户端的认证可依赖授权服务器发布的认证凭证(比如Password, Public/Private密钥对),而要对公共客户端进行认证很可能是不可靠的。客户端在每次请求中只能使用一种认证方法,如果客户端持有Password,可采用HTTP Basic认证或request-body传递身份凭证参数方法。客户端认证带来的益处:
- 强制绑定Refresh Token或Authorization Code到客户端
- 通过禁用或修改其凭证来快速恢复沦陷客户端
- 定期凭证轮换,更容易实现认证管理
访问令牌(Access Token)是什么?Access Token是访问被保护资源的凭证,一个用来表明被授予权限的字符串,可以是一种可取回授权信息的标识,也可以自包含授权信息于内。可参考 RFC6750 - OAuth 2.0 Bearer Token Usage 。
更新令牌(Refresh Token)是什么?当Access Token无效或过期后,客户端将使用Refresh Token来请求授权服务器更新Access Token,除此而外别无他用。通常Refresh Token是授权服务器在发布新Access Token的同时可选发布的,与客户端绑定并长期有效,但是只有授权码模式(Authorization Code)和用户密码模式(Resource Owner Password Credentials)支持Refresh Token。授权服务器须执行如下操作:
- 对于认证型客户端,要求进行认证
- 如果请求中包含客户端认证,则执行认证流程,还要保证Refresh Token是发布给认证客户端的
- 验证Refresh Token是否有效
Transport Layer Security (TLS):授权服务器和资源服务器都必须实现TLS,至于客户端最好也实现TLS。如果客户端没有实现TLS,授权服务器在发出重定向之前应向用户发出安全告警信息。
OAuth授权的基本流程如下:
- 用户打开客户端以后,客户端要求用户给予授权
- 用户同意给予客户端授权
- 客户端使用上一步获得的授权,向授权服务器申请令牌
- 授权服务器对客户端进行授权以后,确认无误,同意发放令牌
- 客户端使用令牌,向资源服务器申请获取资源
- 资源服务器确认令牌无误,同意向客户端开放资源
OAuth针对不同场景详细定义了四种授权模式:授权码模式(Authorization Code)、隐含模式(Implicit)、用户密码模式(Resource Owner Password Credentials)和客户端证书模式(Client Credentials)。另外,你也可以使用其他扩展模式。
一. 授权码模式(Authorization Code)
授权码模式是流程最严密的授权模式,但是如果被用于Public客户端授权,由于该客户端不能持有客户端证书,因此无法进行身份认证。
-
流程解析:
1. 用户访问客户端,后者将前者导向授权服务器
2. 用户选择是否给予客户端授权
3. 假设用户给予授权,授权服务器将用户导向客户端事先指定的"重定向URI"(redirection URI),同时附上一个授权码
4. 客户端收到授权码,附上早先的"重定向URI",向授权服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见
5. 授权服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)或者更新令牌(refresh token)
该流程中客户端先获取授权码再交换访问令牌,似乎获取授权码显得多余?其实授权码除了代表授权范围之外,还避免了暴露访问令牌于用户端:
The authorization code provides a few important security benefits,such as the ability to authenticate the client, as well as the transmission of the access token directly to the client without passing it through the resource owner's user-agent and potentially exposing it to others, including the resource owner
- 流程参数解释:此处不再详述,可参考 理解OAuth 2.0 或 RFC6749
二. 隐含模式(Implicit)
授权码模式的简化版,跳过了"授权码"这一步,可适用于在浏览器中实现的应用,访问令牌暴露于用户端;在该模式下,授权服务器不会认证客户端,不能使用Refresh Token,一旦Access Token过期,需要重新进行授权处理;隐含模式是一个基于redirection的流,在某些情况下,客户端身份是可以通过redirection URI来验证的。在授权码模式可用的情况下,应权衡隐含模式的便利性和安全性。
-
流程解析:
1. 客户端将用户导向认证服务器
2. 用户决定是否给于客户端授权
3. 假设用户给予授权,认证服务器将用户导向客户端指定的"重定向URI",并在URI的Hash部分包含了访问令牌
4. 浏览器向Web-Hosted服务器发出请求,其中不包括上一步收到的Hash值
5. Web-Hosted服务器返回一个网页,其中包含的代码可以获取Hash值中的令牌
6. 浏览器执行上一步获得的脚本,提取出令牌
7. 浏览器将令牌发给客户端 - 流程参数解释:此处不再详述,可参考理解OAuth 2.0 或 RFC6749
三. 用户密码模式(Resource Owner Password Credentials)
用户向客户端提供自己的用户名和密码,客户端使用这些信息向授权服务器索要授权,但不得保存用户的密码。须在用户对客户端高度信任(比如客户端是操作系统的一部分,或者由一个著名公司出品,或者客户端是本公司出品),而认证服务器无法在其他授权模式下完成授权的情况下,才能考虑使用这种模式。
-
流程解析:
1. 用户向客户端提供用户名和密码
2. 客户端将用户名和密码发给认证服务器,向后者请求令牌
3. 认证服务器确认无误后,向客户端提供访问令牌
- 流程参数解释:此处不再详述,可参考理解OAuth 2.0 或 RFC6749
四. 客户端证书模式(Client Credentials)
顾名思义,客户端模式不是针对单个用户的授权,而是针对客户端授权,适用于受保护资源已经处于客户端控制之下(相当于资源所有者)或者授权服务器已经预先配置好客户端访问权限的的情况。
-
流程解析:
1. 客户端向认证服务器进行身份认证,并要求一个访问令牌
2. 认证服务器确认无误后,向客户端提供访问令牌 流程参数解释:此处不再详述,可参考理解OAuth 2.0 或 RFC6749
五. 授权模式选择
- 本公司出品应用一般适用用户密码模式(Resource Owner Password Credentials)
- Web应用一般适用授权码模式(Authorization Code)
-
Native应用一般适用授权码模式(Authorization Code)或隐含模式(Implicit):Native应用指安装、运行在用户设备上的客户端,包括桌面应用、手机客户端等
When choosing between the implicit grant type and the authorization code grant type, the following should be considered: . Native applications that use the authorization code grant type SHOULD do so without using client credentials, due to the native application's inability to keep client credentials confidential.
. When using the implicit grant type flow, a refresh token is not returned, which requires repeating the authorization process once the access token expires. User Agent Based应用一般适用隐含模式(Implicit)
- 机器对机器一般适用客户端证书模式(Client Credentials)
六. OAuth不是认证协议
OAuth消费认证(Authentication)而不提供认证,因此OAuth必须结合第三方认证协议如 OpenID Connect:OAuth 2.0协议之上的简单身份层 才可使用。但OAuth协议中确实有些内容说的就是认证事件,给人的感觉就是它已定义了认证流程,当然也就算是有效的认证协议了,这是很容易让人误解的;其实啊,定义认证流根本就不是OAuth的题中之意(比如用户注册这个认证流中的重要环节就不是OAuth所关心的内容,所以也就不曾定义)。下边的引文来自 The OAuth 2.0 Authorization Framework :
Before initiating the protocol, the client registers with the
authorization server. The means through which the client registers
with the authorization server are beyond the scope of this
specification but typically involve end-user interaction with an HTML
registration form.
只是认证和授权硬生生地割裂开来,井水不犯河水是不可能的,所以OAuth里边也只是简单地提到了与授权流相关的认证流要害节点以使授权流的定义完整,而没有详细定义认证流,这也是本人刚开始认为OAuth是一个弱认证授权协议的原因。下边的引用文字来自 User Authentication with OAuth 2.0:
OAuth 2.0 is not an authentication protocol.
an OAuth process does usually include several kinds of authentication in its process:
the resource owner authenticates to the authorization server in the authorization step,
the client authenticates to the authorization server in the token endpoint, and there may be others.
The existence of these authentication events within the OAuth protocol does not translate to the
Oauth protocol itself being able to reliably convey authentication.
假如OAuth可以完全抛弃所涉及的任何认证流部分,也就不会有人认为OAuth也是认证协议了,而一个完整的认证协议应该是这样的:
Authentication in the context of a user accessing an application tells an application who
the current user isand whether or not they're present. A full authentication protocol will probably
also tell you a number of attributes about this user, such as a unique identifier, an email address,
and what to call them when the application says "Good Morning". Authentication is all about the
user and their presence with the application, and an internet-scale authentication protocol needs
to be able to do this across network and security boundaries.
七. 采用OAuth认证的常见陷阱
- Access Token作为访问受保护的资源身份认证的证明:Token应该包含Client可理解和解析的结构,如同时使用Client可理解和解析ID Token。
- Access Token作为访问UserInfo API身份认证的证明:通常在用户不存在后,Access Token通常还会存在很长时间,如果一个Client想要确保身份认证是有效的,那么简单的使用Access Token获取用户信息是不够的。
- Access Token注入:这可能会发生在使用Implicit流程中,并且Client不正确使用state参数的时候;对此可以使用Authorization Code来缓解这一点,并且只能通过授权服务器的Token API并使用一个state的值来避免被攻击者猜中。
- Access Token缺乏Client受众限制:通过将Client的认证信息与Client可以识别和验证的标识符一起传递给Client,可以缓解此问题,从而允许客户端区分自身的身份认证与另一应用程序的身份认证。
- 对UserInfo API响应结果注入无效的用户信息:如果攻击者能够拦截或者替换来自Client的一个调用,它可能会改变返回的用户信息,而客户端却无法感知这一情况;对此可通过在身份认证协议过程中(比如跟随OAuth的Token的颁发过程)直接从身份提供程序中获取身份认证信息,并通过可校验的签名保护身份认证信息,可以缓解这一问题。
- 不同身份认证提供商实现了不同的身份认证协议:基于OAuth 身份(identity)API的最大问题在于,即使使用完全符合OAuth的机制,不同的身份认证提供程序不可避免的会使用不同方式实现身份(identity)API。OAuth定义了一个没有特定格式的token,一个没有通用范围的access token,并且没有解决受保护资源如何验证access token。
八. 安全风险
-
客户端认证(Client Authentication):对Public客户端进行认证很可能是不可靠的,授权服务器可以尝试使用redirection URI或征募用户来验证客户端身份
The authorization server MUST NOT issue client passwords or other
client credentials to native application or user-agent-based
application clients for the purpose of client authentication. The
authorization server MAY issue a client password or other credentials
for a specific installation of a native application client on a
specific device.
......
A valid
redirection URI is not sufficient to verify the client's identity
when asking for resource owner authorization but can be used to
prevent delivering credentials to a counterfeit client after
obtaining resource owner authorization. -
更新令牌(Refresh Tokens):授权服务器可以给Web客户端和Native客户端发布Refresh Token。Refresh Token在整个生命周期中都应该保持机密性,不能泄露给任何无关的第三方;Refresh Token必须与客户端身份相绑定;Refresh Token不能未获授权而被生成、修改、猜测产生
the authorization server could employ refresh token
rotation in which a new refresh token is issued with every access
token refresh response. The previous refresh token is invalidated
but retained by the authorization server. If a refresh token is
compromised and subsequently used by both the attacker and the
legitimate client, one of them will present an invalidated refresh
token, which will inform the authorization server of the breach -
授权码(Authorization Codes):授权码有效期短,单一用途
If the
authorization server observes multiple attempts to exchange an
authorization code for an access token, the authorization server
SHOULD attempt to revoke all access tokens already granted based on
the compromised authorization code. -
跨站请求伪造(Cross-Site Request Forgery):一同发送一个non-guessable state请求参数可用来防止CSRF
A CSRF attack against the client's redirection URI allows an attacker
to inject its own authorization code or access token, which can
result in the client using an access token associated with the
attacker's protected resources rather than the victim's (e.g., save
the victim's bank account information to a protected resource
controlled by the attacker). The client MUST implement CSRF protection for its redirection URI.
This is typically accomplished by requiring any request sent to the
redirection URI endpoint to include a value that binds the request to
the user-agent's authenticated state (e.g., a hash of the session
cookie used to authenticate the user-agent). The client SHOULD
utilize the "state" request parameter to deliver this value to the
authorization server when making an authorization request. -
点击劫持(Clickjacking):
To prevent this form of attack, native applications SHOULD use
external browsers instead of embedding browsers within the
application when requesting end-user authorization. For most newer
browsers, avoidance of iframes can be enforced by the authorization
server using the (non-standard) "x-frame-options" header. This
header can have two values, "deny" and "sameorigin", which will block
any framing, or framing by sites with a different origin,
respectively. For older browsers, JavaScript frame-busting
techniques can be used but may not be effective in all browsers
九. 相关参考
- Bearer Token:RFC6750 - OAuth 2.0 Bearer Token Usage,该协议解决Bearer Token类型定义及使用
- JSON Web Token (JWT):RFC7519 - JSON Web Token (JWT),该协议定义了一种自包含Token类型及完整性校验方法;RFC7797 - JSON Web Signature (JWS) Unencoded Payload Option,该协议对RFC7519的更新
- Token撤销:RFC7009 - OAuth 2.0 Token Revocation,该协议解决RCF6749 - OAuth 2.0 中Access Token和Refresh Token不能撤销的问题
- Token Introspection:RFC7662 - OAuth 2.0 Token Introspection,该协议解决RCF6749 - OAuth 2.0 中对Access Token和Refresh Token有效性检查未给出定义的问题
- OpenID Connect(OIDC):OIDC在OAuth2上构建了一个身份层,是一个基于OAuth2协议的身份认证和授权标准。详情可参考其官网http://openid.net/connect/ 或 OIDC 身份认证授权(核心部分)
- 用OAuth2来认证吗?:基于OAuth2的认证(译自:https://oauth.net/articles/authentication/)