为什么需要 oAuth2?
假设有一个“云笔记”产品,并提供了“云笔记服务”和“云相册服务”,此时用户需要在不同的设备(PC、Android、iPhone、TV、Watch)上去访问这些“资源”(笔记,图片)
那么用户如何才能访问属于自己的那部分资源呢?此时传统的做法就是提供自己的账号和密码给我们的“云笔记”,登录成功后就可以获取资源了。但这样的做法会有以下几个问题:
- “云笔记服务”和“云相册服务”会分别部署,难道我们要分别登录吗?
- 如果有第三方应用程序想要接入我们的“云笔记”,难道需要用户提供账号和密码给第三方应用程序,让他记录后再访问我们的资源吗?
- 用户如何限制第三方应用程序在我们“云笔记”的授权范围和使用期限?难道把所有资料都永久暴露给它吗?
- 如果用户修改了密码收回了权限,那么所有第三方应用程序会全部失效.
- 只要有一个接入的第三方应用程序遭到破解,那么用户的密码就会泄露,后果不堪设想。
为了解决类似以上问题 oAuth2 应用而生
oAuth2 是什么?
OAuth(开放授权)是一个开放标准,允许用户授权第三方移动应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方移动应用或分享他们数据的所有内容,OAuth2.0 是 OAuth 协议的延续版本,但不向后兼容 OAuth 1.0 即完全废止了 OAuth1.0。
OAuth 2.0 的标准是 RFC 6749 文件。该文件先解释了 OAuth 是引入了一个授权层,用来分离两种不同的角色:客户端和资源所有者。…… 资源所有者同意以后,资源服务器可以向客户端颁发令牌。客户端通过令牌,去请求数据。OAuth 的核心就是向第三方应用颁发令牌。
oAuth 的 2 规则中的名词解释
- 第三方应用程序(Third-party application):又称之为客户端(client),比如上节中提到的设备(PC、Android、iPhone、TV、Watch),我们会在这些设备中安装我们自己研发的 APP。又比如我们的产品想要使用 QQ、微信等第三方登录。对我们的产品来说,QQ、微信登录是第三方登录系统。我们又需要第三方登录系统的资源(头像、昵称等)。对于 QQ、微信等系统我们又是第三方应用程序。
- HTTP 服务提供商(HTTP service):我们的云笔记产品以及 QQ、微信等都可以称之为“服务提供商”。
- 资源所有者(Resource Owner):又称之为用户(user)。
- 用户代理(User Agent):比如浏览器,代替用户去访问这些资源。
- 认证服务器(Authorization server):即服务提供商专门用来处理认证的服务器,简单点说就是登录功能(验证用户的账号密码是否正确以及分配相应的权限)
- 资源服务器(Resource server):即服务提供商存放用户生成的资源的服务器。它与认证服务器,可以是同一台服务器,也可以是不同的服务器。简单点说就是资源的访问入口,比如上节中提到的“云笔记服务”和“云相册服务”都可以称之为资源服务器。
oAuth2 规则的运行
(A)用户打开客户端以后,客户端要求用户给予授权。
(B)用户同意给予客户端授权。
(C)客户端使用上一步获得的授权,向认证服务器申请令牌。
(D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌。
(E)客户端使用令牌,向资源服务器申请获取资源。
(F)资源服务器确认令牌无误,同意向客户端开放资源。
oAuth2 规则的客户端授权方式
- 授权码模式(authorization code)
(A)用户访问客户端,后者将前者导向认证服务器。
(B)用户选择是否给予客户端授权。
(C)假设用户给予授权,认证服务器将用户导向客户端事先指定的 ” 重定向 URI”(redirection URI),同时附上一个授权码。
(D)客户端收到授权码,附上早先的 ” 重定向 URI”,向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。
(E)认证服务器核对了授权码和重定向 URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。
- 简化模式(implicit)
(A)客户端将用户导向认证服务器。
(B)用户决定是否给于客户端授权。
(C)假设用户给予授权,认证服务器将用户导向客户端指定的 ” 重定向 URI”,并在 URI 的 Hash 部分包含了访问令牌。
(D)浏览器向资源服务器发出请求,其中不包括上一步收到的 Hash 值。
(E)资源服务器返回一个网页,其中包含的代码可以获取 Hash 值中的令牌。
(F)浏览器执行上一步获得的脚本,提取出令牌。
(G)浏览器将令牌发给客户端。
- 密码模式(resource owner password credentials)
(A)用户向客户端提供用户名和密码。
(B)客户端将用户名和密码发给认证服务器,向后者请求令牌。
(C)认证服务器确认无误后,向客户端提供访问令牌。
- 客户端模式(client credentials)
(A)客户端向认证服务器进行身份认证,并要求一个访问令牌。
(B)认证服务器确认无误后,向客户端提供访问令牌。
oAuth2 客户端的授权码模式详细步骤
授权码模式(authorization code)是功能最完整、流程最严密的授权模式。它的特点就是通过客户端的后台服务器,与 ” 服务提供商 ” 的认证服务器进行互动。
(A) 用户访问客户端,后者将前者导向认证服务器; 客户端申请认证的 URI,包含以下参数:
response_type:表示授权类型,必选项,此处的值固定为 ”code”
client_id:表示客户端的 ID,必选项
redirect_uri:表示重定向 URI,可选项
scope:表示申请的权限范围,可选项
state:表示客户端的当前状态,任意字符串,认证服务器会原封不动地返回这个值,也是用来防止跨站点请求伪造攻击,必选项
eg:
GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
(B) 用户选择是否给予客户端授权。
(C) 假设用户给予授权,认证服务器将用户导向客户端事先指定的 ” 重定向 URI”(redirection URI),同时附上一个授权码;
服务器回应客户端的 URI,包含以下参数:
code:表示授权码,必选项。该码的有效期应该很短,通常设为 10 分钟,客户端只能使用该码一次,否则会被授权服务器拒绝。该码与客户端 ID 和重定向 URI,是一一对应关系。
state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数
eg:Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz
(D) 客户端收到授权码,附上早先的 ” 重定向 URI”,向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见; 客户端向认证服务器申请令牌的 HTTP 请求,包含以下参数:
grant_type:表示使用的授权模式,必选项,此处的值固定为 ”authorization_code”。
code:表示上一步获得的授权码,必选项。
redirect_uri:表示重定向 URI 应用程序中的 URL,用于在授权后发送用户, 必选项,且必须与 A 步骤中的该参数值保持一致。
client_id:表示应用程序的客户端 ID,必选项
client_secrect:表示应用程序的客户机密也即是访问的资源权限,必选项
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
(E) 认证服务器核对了授权码和重定向 URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token);认证服务器发送的 HTTP 回复,包含以下参数:
access_token:表示访问令牌,必选项。
token_type:表示令牌类型,该值大小写不敏感,必选项,可以是 bearer 类型或 mac 类型。
expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。
refresh_token:表示更新令牌,用来获取下一次的访问令牌,可选项。
scope:表示权限范围,如果与客户端申请的范围一致,此项可省略。
eg:
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"
}
流程图如下
更新令牌
令牌的有效期到了,如果让用户重新走一遍上面的流程,再申请一个新的令牌,很可能体验不好,而且也没有必要。OAuth 2.0 允许用户自动更新令牌。
具体方法是,资源服务器颁发令牌的时候,一次性颁发两个令牌,一个用于获取数据,另一个用于获取新的令牌(refresh token 字段)。令牌到期前,用户使用 refresh token 发一个请求,去更新令牌。
https://b.com/oauth/token?
grant_type=refresh_token&
client_id=CLIENT_ID&
client_secret=CLIENT_SECRET&
refresh_token=REFRESH_TOKEN
URL 中,grant_
type 参数为 refresh_
token 表示要求更新令牌,client_
id 参数和 client_
secret 参数用于确认身份,refresh_
token 参数就是用于更新令牌的令牌。资源服务器验证通过以后,就会颁发新的令牌。
oAuth2 四种方式比较
密码模式(resource owner password credentials)
- 这种模式是最不推荐的,因为 client 可能存了用户密码
- 这种模式主要用来做遗留项目升级为 oauth2 的适配方案
- 当然如果 client 是自家的应用,也是可以
- 支持 refresh token
授权码模式(authorization code)
- 这种模式算是正宗的 oauth2 的授权模式
- 设计了 auth code,通过这个 code 再获取 token
- 支持 refresh token
简化模式(implicit)
- 这种模式比授权码模式少了 code 环节,回调 url 直接携带 token
- 这种模式的使用场景是基于浏览器的应用
- 这种模式基于安全性考虑,建议把 token 时效设置短一些
- 不支持 refresh token
客户端模式(client credentials)
- 这种模式直接根据 client 的 id 和密钥即可获取 token,无需用户参与
- 这种模式比较合适消费 api 的后端服务,比如拉取一组用户信息等
- 不支持 refresh token,主要是没有必要
refresh token 的初衷主要是为了用户体验不想用户重复输入账号密码来换取新 token,因而设计了 refresh token 用于换取新 token
这种模式由于没有用户参与,而且也不需要用户账号密码,仅仅根据自己的 id 和密钥就可以换取新 token,因而没必要 refresh token
安全问题
- redirect_uri
问:为什么 OAuth 2.0 要求申请
client_id
的时候必须输入一个域名,并且要求redirect_uri
必须是此域名下的地址?答:如果别人知道了我们的
client_id
,然后在第二部生成授权连接的时候,把redirect_uri
替换成自己与域名下的,就会能获取code
并存放在自己服务器上 - code
如果
第三反方服务
不支持 https,就会有被劫持的风险。如果没有code
,而是直接返回access_token
在redirect_uri
中,中间人就可以直接使用access_token
- app_secret
如果
code
被中间人获取,在没有app_secret
的情况下,就可以直接用code
换取access_token
。因为在整个获取code
的过程中都是暴露在外的 -
state
state
参数如果利用起来,当作CSRF Token
,就能避免此事的发生:- 攻击者依旧获取 code 并打算骗受害者点击
- 受害者点击链接,但因服务器(比如 chrisyue.com)分配给受害者的设备的 state 值和链接里面的 state 值不一样,服务器(chrisyue.com)直接返回验证 state 失败
`state` 或者说 `CSRF Token` 这种跟设备绑定的随机字符串,只要稍微复杂一点,攻击者根本就不可能猜得出来,而设置一个让攻击者猜不到的,跟设备或者说浏览器绑定的 state(CSRF token)值,就是解决 CSRF 攻击的关键。
oAuth2 实现过程小结
关于 Auth2 的的实现机制,就是不同的客户端需要向资源服务器通过请求令牌(access_
token)来获取不同的资源, 服务器需要去验证访问的客户端是否是在该资源服务器注册过的,以及该客户端被分配到的资源服务器的可访问的权限是什么,所以需要用户首先被客户端重定向到认证服务器来验证它的身份,验证通过,返回一个用来获取 token 的只能使用一次的一个 code 码作为请求 token 的参数,客户端请求 token 的时候还必须传递一个需要获取资源权限的必选参数 client_
secrect, 以及参数 client_
id(客户端的身份 ), 验证通过,则客户端获取到资源并展示给用户。
参考资料:
https://www.jianshu.com/p/a7d…
http://www.ruanyifeng.com/blo…
http://www.ruanyifeng.com/blo…
http://www.ruanyifeng.com/blo…
https://developer.github.com/…
https://www.jianshu.com/p/7b1…