理解 OAuth2.0
文章转载于阮一峰老师的博客:http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html
参考文章:https://learnku.com/articles/20082
一、应用场景
为了理解 OAuth 的适用场景,阮老师在这里举了一个例子说明,如下:
有一个 "云冲印" 的网站,可以将用户存储在 Google 的照片,冲印出来。这里需要说明一下"云冲印"相对于 Google 是一个第三方。
那么如果要想让"云冲印"能够打印用户存储在 Google 上的照片,就必须要能让"云冲印"能读取用户存储在 Google 上的照片。
问题来了,Google 肯定不会让一个未经用户授权的第三方去读取用户存储在 Google 上的照片,那么"云冲印"怎么获取用户的授权?
传统的方法就是用户将自己 Google 的帐号密码告诉"云冲印",后者就可以拿着帐号密码登录 Google 去读取用户的照片了,但是这时
大家肯定会想到这很不安全,都有哪些风险呢?
1 如果"云冲印"保存了用户的帐号密码,那这样很不安全
2 "云冲印"有了用户的帐号密码后,就拥有了和用户一样的权限可以读取用户所有的资料,用户没办法限制"云冲印"获得授权的范围和
有效期
3 用户只有修改密码,才能收回"云冲印"的权限,但是这样做,会使得其他所有获得用户授权的第三方应用程序失效
4 如果其中一个第三方应用程序被破解,那么用户的帐号密码泄露
OAuth 就是为了解决上面的问题诞生的
二、名词定义
1 Third-party application:第三方应用程序,本文中又称"客户端(client)",即上面例子中的"云冲印"
2 HTTP service:HTTP 服务提供商,本文中简称"服务提供商",即上面例子中的"Google"
3 Resource Owner:资源拥有者,本文中指的是"用户"
4 User Agent:用户代理,本文中就是浏览器
5 Authorization server: 认证服务器,服务提供商专门用来解决认证处理的服务器
6 Resource server:资源服务器,服务提供商存放用户所拥有资源的服务器,它与认证服务器可以是一台服务器也可以不是一台服务器
了解了上面这些名词,可以知道,OAuth 其实就是"客户端"安全可控的获取"用户授权",与"服务提供商"交互
三、OAuth 的思路
OAuth 在客户端和服务提供商之间,设置了一个"授权层",客户端不能直接登录服务提供商,只能登录授权层,以此将用户和客户端区分
开来,客户端登录授权层所用的令牌(token),与用户的密码不同。用户在登录的时候可以指定授权层令牌的权限范围和有效期
客户端登录授权层之后,服务提供商根据令牌的权限范围和有效期,向客户端开放用户存储的资料
四、运行流程
OAuth2.0 的运行流程如下图:
A: 用户打开客户端以后,客户端需要用户给与权限
B:用户同意给与客户端授权
C:客户端使用上一步获得的授权,向认证服务器申请令牌
D:认证服务器对客户端进行认证以后,确认无误,同意发放令牌
E:客户端使用令牌,向资源服务器申请获得资源
F:资源服务器确认令牌无误后,同意向客户端开放资源
上面六步中,B 是关键的一步,用户怎么才能客户端授权,客户端被授权后访问认证服务器申请令牌,然后凭借有效的令牌访问资源服务器,
下面看一下客户端获取授权的四种模式
五、客户端的授权模式
客户端必须获得用户授权之后,才能拿到令牌(access token),OAuth2.0 提供了四种授权方式,阮一峰老师的博客里面详解每一种,在这里我
只介绍下常用的两种模式
授权码模式(authorization code)
简化模式(implicit)
六、授权码模式
授权码模式是目前功能最完善,流程最严密的授权模式,它的特点就是通过客户端的后台服务器和服务提供商的认证服务器进行互动,如下图:
A:用户访问客户端,客户端将用户导向认证服务器
B:此时会弹出相关页面让用户选择是否同意授权给客户端
C:假设用户同意给与授权,认证服务器将用户导向事先指定好的"重定向URI",同时附上一个授权码(authorization code)
D:客户端收到授权码之后,附上之前的重定向URI,向认证服务器申请令牌(access token),这一步是在客户端的服务器上完成的
E:认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送令牌(access token)和刷新令牌(refresh token)
下面是上面这些步骤所需要的参数
A 步骤中,客户端申请认证的URI:
response_type:表示授权类型,必选项,此处为固定值 "code"
client_id:表示客户端的ID,必选项
redirect_uri:表示重定向 URI,可选项
scope:表示申请的权限范围,可选项
state:表示客户端当前状态,可以试任意值,认证服务器会原封不动的返回这个值
如下示例:
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
C 步骤中,认证服务器返回给客户端的参数:
code:授权码,授权码的有效期比较短,并且只能使用一次
state:如果客户端中的请求中包含这个参数,则认证服务器必须原封不动的回应这样一个参数
如下示例:
HTTP/1.1 302 Found Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA &state=xyz
D 步骤中,客户端向认证服务器申请令牌的HTTP请求的参数:
grant_type:表示使用的授权模式,此处为固定值:authorization code
code:表示上一步获取的授权码,必填
redirect_uri:表示重定向URI,如果授权请求中包含 redirect_uri 则返回时必填
client_id:客户端 ID
如下示例:
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 步骤认证服务器向客户端发送的请求回复参数:
access_token:表示令牌
token_type:表示令牌类型,该值大小写不敏感,必选项,可以是bearer类型或mac类型
expires_in:表示过期时间,单位为秒
refresh_token:表示更新令牌,用来获取下一次令牌
scope:表示权限范围,如果与客户端申请的范围一致,可以省略
如下示例:
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:用户访问客户端,客户端将用户导向认证服务器
B:用户决定是否给与客户端授权
C:假设用户同意授权,认证服务器将用户导向客户端指定的"重定向URI",并在 URI的 HASH 部分包含了访问令牌
D:浏览器向资源服务器发出请求,其中不包括上一步收到的 HASH 值
E:资源服务器返回一个网页,其中包含的代码可以获取 HASH 值中的令牌
F:浏览器执行上一步获取得脚本,提取出令牌
G:浏览器将令牌发给客户端
下面是上面步骤中所需要的参数:
A 步骤客户端发出的 HTTP 请求参数:
response_type:表示授权类型,此处的值为固定值"token"
client_id:表示客户端的 ID
redirect_uri:表示重定向 URI
scope:表示授权范围
state:表示客户端状态,可以指定任意值,认证服务器会原封不动的返回这个值
如下示例:
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
C步骤中,认证服务器回应客户端的URI:
access_token:表示访问令牌
token_type:表示令牌类型
expires_in:表示过期时间
scope:表示权限范围
state:如果客户端的请求中包含这个参数,则认证服务器的回应中也需要包含一个一模一样的参数
如下示例:
HTTP/1.1 302 Found Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA &state=xyz&token_type=example&expires_in=3600
在上面的例子中,认证服务器用 HTTP 头信息的 Location 栏,指定浏览器重定向的网址,注意,这个网址的 HASH 部分包含了令牌,
根据上面的步骤,下一步浏览器会访问Location指定的网址,但是 HASH 部分不会被发送,然后资源服务器发送过来的代码,会提取出HASH
中的令牌
-----------------------------------------2019/03/29----------------------------------------------
关于 OAuth2.0 中的 state 参数
在看完阮一峰老师的文章关于使用授权码模式实现授权登录后关于 state 这个参数就有一点疑问,其中阮一峰老师在文章中讲到,如果客
户端的传递参数中包含 state 这个参数,则服务端必须要原封不动传递回来,阮老师也没有具体说到这个参数的真正作用,后来看了相关文章后,
这个参数可以说是必传的参数,如果少了这个参数可能会造成 CSRF 攻击
我们通过一个常见场景说一下,参考文章:https://blog.csdn.net/gjb724332682/article/details/54428808
1. 用户 A 登录一个第三方站点,现在用户想要把这个第三方站点的帐号和自己的微博帐号进行绑定,此时用户到了绑定页面,但是还没有
绑定,在绑定页面提功力一个绑定按钮:"绑定微博" (地址a:http://aaa.com/index.php?m=user_3rd_bind_sina)
2.用户点击绑定按钮,第三方站点会将用户导向微博的认证服务器(该过程用户无感知),然后认证服务器会弹出一个页面询问用户是否同意
授权,用户同意授权之后认证服务器会将用户导向第三方站点请求参数里面的回跳地址,并返回授权码
该过程中涉及到的一些请求参数,第三方站点将用户导向认证服务器时的请求:https://api.weibo.com/oauth2/authorize?client_id=&redirect
_url=【http://aaa.com/index.php?m=user_3rd_bind_sina】&response_type=code
认证服务器返回给第三方站点的地址:http://aaa.com/index.php?m=user_3rd_bind_sina&code=【授权码】
从上面认证服务返回给第三方站点的地址来看其实是和当前登录用户一点关系都没有,这个地址只能证明微博用户信息,没有一个标识能证明
第三方站点的用户信息,这样就出现了漏洞。
假设现在有甲和乙两个用户同时发起绑定请求,都在认证服务器返回授权码这一步停下,然后甲和乙互换地址,会出现什么样的结果?
因为返回授权码的地址没有任何标识能有效证明第三方站点当前登录用户信息,所以会出现甲绑定乙的微博帐号,乙绑定甲的微博帐号,而攻击
者的目标就是获取自己账号的地址,然后诱骗已登录第三方站点的用户点击,从而改变绑定关系,将用户的微博帐号绑定到攻击者的帐号
为了应对这种情况,OAuth2.0加入了 state 这个参数,很多第三方开放平台都会有这个参数,比如新浪微博的(https://open.weibo.com/wiki/Oauth
2/authorize),我们可以使用这个参数去验证请求的有效性,第三方向认证服务器发送请求时带上这个参数,认证服务器在返回的时候也需要带上这个参数,
每个用户的state参数都是唯一的,如果检验传递过去的和返回的不一样则认为是不合法的,这样就可以有效的避免CSRF(跨站请求伪造)攻击
关于如何生成state参数,不同的开发者有自己的实现方式,一般情况下都是一个随机字符串将其保存在cookie或session中,回调时检查cookie或ses
sion中该参数的有效性即可