OAuth2.0 学习笔记
1.简介
OAuth 2.0 是什么?
阮老师的博客 理解OAuth 2.0 里举了个例子:
有一个"云冲印"的网站,可以将用户储存在Google的照片,冲印出来。用户为了使用该服务,必须让"云冲印"读取自己储存在Google上的照片。只有得到用户的授权,Google才会同意"云冲印"读取这些照片。那么,"云冲印"怎样获得用户的授权呢?
一种方法是,用户将自己的Google用户名和密码,告诉"云冲印",后者就可以读取用户的照片了。但是这样的做法有以下几个严重的缺点。
- "云冲印"为了后续的服务,会保存用户的密码,这样很不安全。
- Google不得不部署密码登录,而我们知道,单纯的密码登录并不安全。
- "云冲印"拥有了获取用户储存在Google所有资料的权力,用户没法限制"云冲印"获得授权的范围和有效期。
- 用户只有修改密码,才能收回赋予"云冲印"的权力。但是这样做,会使得其他所有获得用户授权的第三方应用程序全部失效。
- 只要有一个第三方应用程序被破解,就会导致用户密码泄漏,以及所有被密码保护的数据泄漏。
Oauth 2.0 就是为了解决上面这些问题而诞生的,它是一个基于 HTTP 协议的认证框架,使得用户可以安全且灵活地授权给第三方应用来访问自己的数据。(之所以叫 2.0 是因为之前有一个已经过时的 1.0 版本,所以加了一个 2.0 以示区分,下面简写为 Oauth)。
引入了 OAuth 后,上述打印照片的流程就变为了:
-
用户使用云冲印服务,选择打印 Google 云相册里的照片
-
云冲印会引导用户跳转到一个授权页面,大概如下图所示:
-
用户点击 授权 按钮后,提示授权成功。
-
用户就可以在云冲印应用里访问自己的 Google 云相册,从而选择照片进行打印。
另外,这个 介绍视频 也举了一个挺不错的入门例子,值得一看。
看完入门介绍后,接下来就正式开始学习一下OAuth,主要基于官方发布的这个RFC 。
另外官网上也有一些其他更多相关资料,有必要时可以参考查阅一下。
2. 核心流程和关键概念
认证流程:
- Client 去请求 Resource Owner 的授权。
- 用户同意授权,提供一个 Authorization Grant 作为凭证。
- Client 凭借 Authorization Grant,去请求 Authorization Server 获取资源访问的 Access Token。
- Authorization Server 验证请求合法,下发 Access Token。
- Client 凭借 Access Token 去请求 Resource Server 获取资源。
- Resource Server 验证请求合法,下发资源。
为了理解上述流程,需要介绍一下 OAuth 2.0 框架下定义的如下几个角色:
- Client:需要获取授权访问资源的客户端,即上面例子中的 “云冲印” 网站。
- Resource Owner:资源所有者,即照片的主人。
- Authorization Server:认证服务器,专门抽出来用作认证的一层。一般由资源方提供,即Google。
- Resource Server:资源服务器,管理资源的地方,即Google。
以及一些关键概念:
- Authorization Grant : Resource owner 授予的凭证,用于换取资源访问的 access token。
- Access Token : 最终访问资源的凭证。
- Refresh Token:一般会在授予 access token 时同时下发一个较长时效的 Refresh Token,Access Token失效时用 Refresh Token 再换取新的。
- Client Registration:在一切开始之前,需要 Client 提前去 Authorization Server 注册注册成功后会分配一个 ClientId,之后才能进行上述流程。
- Protocol Endpoint:HTTP resources,我的理解为需要提供部署提供 HTTP 接口的地方,有如下三个:
- Authorization Endpoint:接受用户授权的请求,并进行响应,部署在 Authorization Server。
- Redirection Endpoint:授权通过后,会带着用户的 Authorization Grant 重定向到这里来,部署在 client。
- Token Endpoint:接受获取 access token 的请求,并进行相应,部署在 Authorization Server。
- Access Token Scope:控制可以访问的资源范围。
- 注意区分如下两个看起来比较像的概念:
- Authorization:授权,授予访问资源的权限。
- Authentication:身份验证,是可信的客户端或者用户。
我看到这里时,脑海里有这么几个问题:
- 为什么要分 Authorization Grant 和 Access Token 两步获取?直接一步到位拿到最终 Token 可行么?
- 上图中的A、B步骤,概念模型里是 Client 和 Resource Owner打交道,但是最终落实到技术实现里,是哪一端在负责?
- 如何确认 Authorization Request 的确是用户发起的?
- Authorization Grant 具体长什么样?访问范围和时效等管理怎么做?
- Authorization Server 如何检验 Authorization Grant 合法性?
- Access Token 具体长什么样?访问范围和时效等管理怎么做?
- Resource Server 如何检验 Access Token 合法性?
带着这些问题,接下来再一步步探究一下 OAuth 的更多细节。
3. 获取授权
OAuth定义了四种授权方式,下面分别讲一下。
3.1 Authorization Code Grant
首先向用户申请授权,获取到一个 code,再用该 code 去获取 access token。
(A)Client 把用户引导向 Authorization Server,附带上自己的 clientId, scope, state, redirectUrI。 (B)Authorization Server 校验用户身份 (C)假设用户通过了授权,Authorization Server 就会把用户重定向到 redirectUrI,会附带上 authorization code 和 之前带过来的 state。 (D)Client 用 authorization code 去请求 access token。 (E)Authorization Server 验证 Client 身份和 authorization code,同时确认 redirectURI 和步骤 C 里的一致。如果验证都通过了,就下发一个 access token 和一个可选的 refresh token。
接下来仔细看一下获取授权和token的请求和响应体内容。
Authorization Request
Client 请求获取 authorization code,传参:
- response_type :必传,此处固定为 code
- client_id : 必传,之前注册好的值
- redirect_uri :如果无法提前确定重定向URL(没有提前注册好),则必须带上一个该参数以指明要用哪个跳转链接。
- scope:可选,请求的资源范围
- state:可选,Authorization Server 会直接透传回来。用于传递信息,避免 CSRF。
eg:
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
Authorization Response
如果授权通过,Authorization Server 会跳转到指定的链接,并在URL后附带上如下参数:
- code:必传,authorization code。该 code client 只能使用一次,而且必须在较短时间内失效(建议不要超过10分钟)
- state:直接原封不动地传回去。
eg:
HTTP/1.1 302 Found
Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz
Error Response
如果 redirectURI 或者 clientId 不合法,Authorization Server 应该通知 Resource Owner,并且不能重定向到该 redirectURI。 如果 Resource Owner 拒绝授权,或者是 redirectURI 以外的问题,Authorization Server 在重定向URI的后面追加错误信息参数 error:
- error:必传,只能是如下值之一:
- invalid_request: 请求参数不合法
- unauthorized_client : 为授权的client
- access_denied:resource owner 或者 authorization server 拒绝了请求
- unsuported_response_type:authorization server 不支持获取 authorization code
- incalid_scope:scope 非法
- server_error:authorization server自己出了异常。不可以直接用 500 Internal Server Error,是为了能重定向回 Client。
- temporarily_unavailable:服务暂时不可用。不可以直接用 503 Service Unavailable,是为了能重定向回 Client。
- error_description: 可选,错误信息描述
- error_uri:可选,错误信息描述页面。
- state:如果请求里传了这个参数,则需要原封不同传回去。
Access Token Request
Client 凭借 authorization code 请求获取 access token,传参:
- grant_type : 必传,固定为 “authorization_code"
- code : 必传,authorization code 值
- redirect_uri : 如果 authorization request 中传了该值,则这里也必传,而且二者必须完全相同。
- client_id : 视 client authentication 情况而定。。
eg:
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
Access Token Response
成功实例:
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.2 Implicit Grant
Authorization Code Grant 的简化版本,向用户申请授权成功后,直接拿到 access token,不再需要一个中间的 code。
认证流程: (A)Client 把用户引导向 Authorization Server,附带上自己的 clientId, scope, state, redirectUrI。 (B)Authorization Server 校验用户身份,这两步和 authorization grant 方式是一样的。 (C)假设用户通过了授权,Authorization Server 就下发 redirectUrI,会把 access token 放进 URI fragment 里(fragment 即 URI 的 # 后面的内容)。 (D)、(E) 、(F) 这一通操作把我整晕了,不知道在干啥。。 (G) User-agent 把 access token 传给 Client。
Authorization Request
请求参数(和 authorization code 方式的一模一样,只是 response_type 取值不同):
- response_type : 必传固定为 “token"
- client_id : 必传
- redirect_urli: 如果无法提前确定重定向URI(没有提前注册好),则必须带上一个该参数以指明要用哪个跳转链接。
- scope:可选,请求的资源范围
- state:可选,Authorization Server 会直接透传回来。用于传递信息,避免 CSRF。
eg:
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
Access Token Response
如果授权通过,则会重定向到 redirect_uri,同时在 URI 的 fragment 部分追加如下参数:
- access_token : 必传,token。
- token_type : 必传,token 类型。
- expires_in:可选,token 失效时间。
- scope : 可选,资源访问范围。
- state : 如果之前client请求里有带,则必须透传回去。
eg:
HTTP/1.1 302 Found
Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA&state=xyz&token_type=example&expires_in=3600
Error Response
和 authorization code 基本相同,只是 error 信息也是放进 fragment 里的。
3.3 Resource Owner Password Credentials Grant
该情景仅适用于 client 的可信程度够高的情况下,可以直接用 Resource Owner 的密码来换取 access token。
注意这里也是要先换取 access token 再去请求资源的,避免了 client 存储用户密码信息。
但这种授权模式依旧风险较高,存在把用户账号密码泄漏的可能,一般仅用于遗留系统,如果有其他选项,要尽量避免使用。
认证流程: (A)Resource Owner 把自己的密码信息提供给 Client (B)Client 根据用户的密码,去请求 Authorization Server 获取授权 (C)Authorization Server 下发 Access Token(及可选的 Refresh Token)给 Client。
其中步骤A具体是怎么进行的,RFC 里没有规定,但是有一点:Client 在换取 Token 成功后,必须丢弃用户的密码信息。
Access Token Request
参数:
- grant_type : 必传,固定为 “password"
- username : 必传,用户名。
- password : 必传,用户密码。
- scope : 可选,请求访问的资源范围。
eg:
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
Access Token Response
和之前的类型相同。
3.4 Client Credentials Grant
Client 用自己的身份凭证信息去请求访问资源,适用于资源本就从属于 Client 或者 Authorization Server 特别安排的情况。
流程:
(A)Client 把自己的身份凭证信息发给 Authorization Server (B)Authorization Server 进行校验,通过后,下发 Access Token
Access Token Request
传参:
- grant_type : 必传,固定为 “client_credentials "
- scope : 可选,访问的资源范围。
eg:
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
Access Token Response
和之前的类型相同。
4.Refreshing Token
如果 authorization server 下发了 refresh token,则后续 client 可以发起一个 refresh的请求, 参数:
- grant_type : 必传,固定为 “refresh_token"
- refresh_token : 必传,refresh token 值。
- scope : 可选,只能 <= 之前拿到的 scope。这意味着 refresh token 有可能会缩小scope。
eg:
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
如果校验通过,则 authorization server 会下发一个和之前请求 access token 时一样的 response。 看情况也可以下发一个新的 refresh token,把老的失效掉。
5.访问资源
client 根据 access token 去访问 resource server 请求获取资源。 resource server 负责验证 token 合法性,比如:有效期,scope。 具体如何校验 OAuth 框架没有定义,一般都是通过和 authorization server 通信来实现。
Access Token Type 不同的 access token 类型,使用时的方法不太一样,Client 必须在理解这点的基础上才能使用 access token。 这个 RFC 里没有展开,建议用到的开发者去读另外一些资料:
- https://tools.ietf.org/pdf/rfc6750.pdf
- [OAuth-HTTP-MAC] Hammer-Lahav, E., Ed., "HTTP Authentication: MAC Access Authentication", Work in Progress, February 2012.
6.安全性考量
RFC 里罗列了很多,这里先简单记几个我觉得比较关键的。
- Client Authentication
Authorization Server 要注意验证 Client 的身份合法性,一般建议采用比密码更强的校验方式。
如果无法直接校验 Client 身份,Authorization Server 可以采取其他方式,如:要求 Client 提前注册 Redirection URI(虽然无法起到校验身份的作用,但是可以避免后面被重定向到恶意地址),或者找 Resource Owner 来帮助验证。
如果 Client 的身份凭证信息泄漏,则可能会被恶意的 Client 假扮,因此 Authorization Server 要尽可能的多校验 Client 身份。
- Access Tokens
Access Token 必须保持机密性,只能在 Authorization Server,对应资源的 Resource Server,和被授予的 Client 三者之间共享,传输时必须采用 TLS(Transport Layer Security ),不可明文传输。
当时用 Implicit 授权方式时,access token 会被暴露在 URI 的 fragment 里,有可能会被未被授权的其他方获取到,所以不建议在非可信环境下使用该方式。
- Refresh Tokens
和 Access Token 一样要保持机密性,只能在 Authorization Server 和被授予的 Client 之间共享,传输时必须采用 TLS,不可明文传输。
Authorization Server 必须维护 Refresh Token 到它被授予的 Client 之间的映射关系,以便在后续请求时进行校验,避免被其他 Client 假冒。
- Authorization Code
必须给 code 设置一个较短的有效期,并且只能用一次。
只要能对 Client 身份进行校验,则 Authorization Server 必须确保最终授予的,和发起请求的是同一个 Client。
传输时建议采用 TLS,不可明文传输
- Authorization Code Redirection URI Manipulation
Authorization Code 授权模式下,Client 发起授权请求时,会传递一个 “redirect_uri” 参数来指明后续的跳转地址,如果该值被黑客篡改,则存在跳转到恶意网站的风险。
为了防范这样的攻击,Authorization Server 必须确保请求 code 时传的,和请求 token 时传的是同一个 “redirect_uri”。 必须要求公开的 Client 提前注册好 redirect_uri,并且建议对其他所有的 Client 也这都么做,当授权请求发起时,就可以验证传过来的和之前注册好的是不是同一个了。
- Credentials-Guessing Attacks
Authorization Server 必须防止攻击者猜测这些项:access tokens, authorization codes, refresh tokens, resource owner passwords, and client credentials. 猜测出来的概率必须低于 2^(-128),建议低于2^(-160)。
- Cross-Site Request Forgery
一般是通过要求任何请求都必须带一个和用户身份状态有关的一个值(比如 session cookie 的 hash 值),而这个值可以通过 authorization request 的 state 参数传递给 authorization server,再由它来透传回来。
7.总结
最后回答一下开始的几个问题:
- 为什么要分 Authorization Grant 和 Access Token 两步获取?直接一步到位拿到最终 Token 可行么?
- 这个是标准流程也即 authorization code 的流程,如果是 implicit 则无需中间的 grant,直接拿到 token。关于二者的详细区别可以参见第5节。
- 上图中的A、B步骤,概念模型里是和 Resource Owner打交道,但是最终落实到技术实现里,是哪一端在负责?
- 具体实现方式是 client 引导 resource owner 到 authorization server 端进行授权操作。
- 如何确认 Authorization Request 的确是用户发起的?
- authorization server 会对用户身份进行验证(通过密码或者是 session cookie),来确认是用户本人在操作。
- Authorization Grant 具体长什么样?访问范围和时效等管理怎么做?
- 视 grant 类型而异,详见第3节 authorization grant 部分。 访问范围和时效管理 RFC 里并没有管,由 authorization server 自主实现。
- Authorization Server 如何检验 Authorization Grant 合法性?
- 因为 grant 也是通过 Authorization Server 下发的,所以它自己当然有办法验证,具体怎么做 RFC 没有管。
- Access Token 具体长什么样?访问范围和时效等管理怎么做?
- 就是一个普通的字符串。访问范围和时效管理 RFC 里并没有管,由 authorization server 自主实现。
- Resource Server 如何检验 Access Token 合法性?
- RFC 里并没有管,一般需要 Resource Server 发起一次和 Authorization Server 的通信。