koa如何实现Oauth2(一)

单点登陆和受权 -- SSO & OAUTH2html

二者区别:oauth2解决的是服务提供方(如微信等)给第三方应用受权的问题,而SSO解决的是大型系统中各个子系统如何共享登录状态的问题。前端

SSOjava

什么是SSO?git

wiki上是这么解释的:SSO(Single Sign-On), is a property of access control of multiple related, but independent software systems. With this property a user logs with a single ID and password to gain access to connected system or systems without using different usernames or passwords, or in some configurations seamlessly sign on at each system. ( 单点登陆是一种控制多个相关但彼此独立的系统的访问权限, 拥有这一权限的用户可使用单一的ID和密码访问某个或多个系统从而避免使用不一样的用户名或密码,或者经过某种配置无缝地登陆每一个系统 ).github

为何须要SSO
解决了用户只须要登陆一次就能够访问全部相互信任的应用系统,而不用重复登陆。web

SSO几种场景
(一)同域 SSOjson

1. 用户访问产品 a,向 后台服务器发送登陆请求。
2. 登陆认证成功,服务器把用户的登陆信息写入 session。
3. 服务器为该用户生成一个 cookie,并加入到 response header 中,随着请求返回而写入浏览器。该 cookie 的域设定为 dxy.cn。
4. 下一次,当用户访问同域名的产品 b 时,因为 a 和 b 在同一域名下,也是 dxy.cn,浏览器会自动带上以前的 cookie。此时后台服务器就能够经过该 cookie 来验证登陆状态了。api

(二)同父域 SSO跨域

同父域 SSO 是同域 SSO 的简单升级,惟一的不一样在于,服务器在返回 cookie 的时候,要把cookie 的 domain 设置为其父域。
好比两个产品的地址分别为 a.dxy.cn 和 b.dxy.cn,那么 cookie 的域设置为 dxy.cn 便可。在访问 a 和 b 时,这个 cookie 都能发送到服务器,本质上和同域 SSO 没有区别。浏览器

(三)跨域SSO

当两个产品不一样域时,cookie 没法共享,因此咱们必须设置独立的 SSO 服务器了。下面咱们就来详细介绍一下CAS:

SSO仅仅是一种架构,一种设计,而 CAS 则是实现 SSO 的一种手段。二者是抽象与具体的关系。固然,除了 CAS 以外,实现 SSO 还有其余手段,好比简单的 cookie。

CAS (Central Authentication Service)中心受权服务,自己是一个开源协议,分为 1.0 版本和 2.0 版本。1.0 称为基础模式,2.0称为代理模式,适用于存在非 Web 应用之间的单点登陆。本文只涉及 CAS 1.0。

(1)CAS定义了一组票据

TGT:Ticket Grangting Ticket。TGT 是 CAS 为用户签发的登陆票据,拥有了 TGT,用户就能够证实本身在 CAS 成功登陆过。TGT 封装了 Cookie 值以及此 Cookie 值对应的用户信息。当 HTTP 请求到来时,CAS 以此 Cookie 值(TGC)为 key 查询缓存中有无 TGT ,若是有的话,则相信用户已登陆过。

TGC:Ticket Granting Cookie。CAS Server 生成TGT放入本身的 Session 中,而 TGC 就是这个 Session 的惟一标识(SessionId),以 Cookie 形式放到浏览器端,是 CAS Server 用来明确用户身份的凭证。

ST:Service Ticket。ST 是 CAS 为用户签发的访问某一 service 的票据。用户访问 service 时,service 发现用户没有 ST,则要求用户去 CAS 获取 ST。用户向 CAS 发出获取 ST 的请求,CAS 发现用户有 TGT,则签发一个 ST,返回给用户。用户拿着 ST 去访问 service,service 拿 ST 去 CAS 验证,验证经过后,容许用户访问资源。

(2)CAS 详细步骤

1. 用户访问产品 a,域名是 www.a.cn。

2. 因为用户没有携带在 a 服务器上登陆的 a cookie,因此 a 服务器返回 http 重定向,重定向的 url 是 SSO 服务器的地址,同时 url 的 query 中经过参数指明登陆成功后,回跳到 a 页面。重定向的url 形如 sso.dxy.cn/login?service=https%3A%2F%2Fwww.a.cn。

3. 因为用户没有携带在 SSO 服务器上登陆的 TGC(看上面,票据之一),因此 SSO 服务器判断用户未登陆,给用户显示统一登陆界面。用户在 SSO 的页面上进行登陆操做。

4. 登陆成功后,SSO 服务器构建用户在 SSO 登陆的 TGT(又一个票据),同时返回一个 http 重定向。这里注意:
4.1 重定向地址为以前写在 query 里的 a 页面。</br>
4.2 重定向地址的 query 中包含 sso 服务器派发的 ST。</br>
4.3 重定向的 http response 中包含写 cookie 的 header。这个 cookie 表明用户在 SSO 中的登陆状态,它的值就是 TGC。

5. 浏览器重定向到产品 a。此时重定向的 url 中携带着 SSO 服务器生成的 ST。

6. 根据 ST,a 服务器向 SSO 服务器发送请求,SSO 服务器验证票据的有效性。验证成功后,a 服务器知道用户已经在 sso 登陆了,因而 a 服务器构建用户登陆 session,记为 a session。并将 cookie 写入浏览器。注意,此处的 cookie 和 session 保存的是用户在 a 服务器的登陆状态,和 CAS 无关。

7. 以后用户访问产品 b,域名是 www.b.cn。

8. 因为用户没有携带在 b 服务器上登陆的 b cookie,因此 b 服务器返回 http 重定向,重定向的 url 是 SSO 服务器的地址,去询问用户在 SSO 中的登陆状态。

9. 浏览器重定向到 SSO。注意,第 4 步中已经向浏览器写入了携带 TGC 的cookie,因此此时 SSO 服务器能够拿到,根据 TGC 去查找 TGT,若是找到,就判断用户已经在 sso 登陆过了。

10. SSO 服务器返回一个重定向,重定向携带 ST。注意,这里的 ST 和第4步中的 ST 是不同的,事实上,每次生成的 ST 都是不同的。

11. 浏览器带 ST 重定向到 b 服务器,和第 5 步同样。

12. b 服务器根据票据向 SSO 服务器发送请求,票据验证经过后,b 服务器知道用户已经在 sso 登陆了,因而生成 b session,向浏览器写入 b cookie


(3)CAS安全性

对于一个CAS用户来讲,最重要是要保护它的TGC,若是TGC不慎被CAS Server之外的实体得到,Hacker可以找到该TGC,而后冒充CAS用户访问全部受权资源。从基础模式能够看出,TGC是CAS Server经过SSL方式发送给终端用户,所以,要截取 TGC 难度很是大,从而确保 CAS 的安全性。

 OAUTH
OAuth是一个关于受权(authorization)的开放网络标准,在全世界获得普遍应用,目前的版本是2.0版。

应用场景和认证流程
应用场景: 假如我有一个网站,你是我网站上的访客,看了文章想留言,留言时发现有这个网站的账号才可以留言,此时给了你两个选择:一个是在个人网站上注册拥有一个新帐户,而后用注册的用户名来留言;一个是使用 github 账号登陆,使用你的 github 用户名来留言。前者你以为过于繁琐,因而惯性地点击了 github 登陆按钮,此时 OAuth 认证流程就开始了。

(一) 网站和 Github 之间的协商

Github 会对用户的权限作分类,好比读取仓库信息的权限、写入仓库的权限、读取用户信息的权限、修改用户信息的权限等等。若是我想获取用户的信息,Github 会要求我,先在它的平台上注册一个应用,在申请的时候标明须要获取用户信息的哪些权限,用多少就申请多少,而且在申请的时候填写你的网站域名,Github 只容许在这个域名中获取用户信息。
此时个人网站已经和 Github 之间达成了共识,Github 也给我发了两张门票,一张门票叫作 Client Id,另外一张门票叫作 Client Secret。


(二)用户和 Github 之间的协商

用户进入个人网站,点击 github 登陆按钮的时候,个人网站会把上面拿到的 Client Id 交给用户,让他进入到 Github 的受权页面,Github 看到了用户手中的门票,就知道这是个人网站让他过来的,因而它就把个人网站想要获取的权限摆出来,并询问用户是否容许我获取这些权限。

// 用户登陆 github,协商
GET https://github.com/login/oauth/authorize
// 协商凭证
params = {
client_id: "xxxx",
redirect_uri: "http://my-website.com"
}

 

若是用户以为个人网站要的权限太多,或者压根就不想我知道他这些信息,选择了拒绝的话,整个 OAuth 2.0 的认证就结束了,认证也以失败了结。若是用户以为 OK,在受权页面点击了确认受权后,页面会跳转到我预先设定的 `redirect_uri` 并附带一个盖了章的门票 code

// 协商成功后带着盖了章的 code 受权码
Location: http://my-website.com?code=xxx

这个时候,用户和 Github 之间的协商就已经完成,Github 也会在本身的系统中记录此次协商,表示该用户已经容许在个人网站访问上直接操做和使用他的部分资源。

(三)告诉 Github 个人网站要来拜访了

第二步中,咱们已经拿到了盖过章的门票 code,但这个 code 只能代表,用户容许个人网站从 github 上获取该用户的数据,若是我直接拿这个 code 去 github 访问数据必定会被拒绝,由于任何人均可以持有 code,github 并不知道 code 持有方就是我本人。

还记得以前申请应用的时候 github 给个人两张门票么,Client Id 在上一步中已经用过了,接下来轮到另外一张门票 Client Secret。

// 网站和 github 之间的协商
POST https://github.com/login/oauth/access_token
// 协商凭证包括 github 给用户盖的章和 github 发给个人门票
params = {
code: "xxx",
client_id: "xxx",
client_secret: "xxx",
redirect_uri: "http://my-website.com"
}

 

拿着用户盖过章的 code 和可以标识我的身份的 client_id、client_secret 去拜访 github,拿到最后的绿卡 access_token。

// 拿到最后的绿卡
response = {
access_token: "e72e16c7e42f292c6912e7710c838347ae178b4a"
scope: "user,gist"
token_type: "bearer",
refresh_token: "xxxx"
}

 

(四)用户开始使用 github 账号在个人页面上留言

// 访问用户数据
GET https://api.github.com/user
?access_token=e72e16c7e42f292c6912e7710c838347ae178b4a

 

上一步 github 已经把最后的绿卡 access_token 给我了,经过 github 提供的 API 加绿卡就可以访问用户的信息了,能获取用户的哪些权限在 response 中也给了明确的说明,scope 为 user 和 gist,也就是只能获取 user 组和 gist 组两个小组的权限,user 组中就包含了用户的名字和邮箱等信息了。

// 告诉我用户的名字和邮箱
response = {
username: "barretlee",
email: "barret.china@gmail.com"
}

 

根据上面的分析, 能够总结OAuth 2.0流程为

(A)用户打开客户端之后,客户端要求用户给予受权。
(B)用户赞成给予客户端受权。
(C)客户端使用上一步得到的受权,向认证服务器申请令牌。
(D)认证服务器对客户端进行认证之后,确认无误,赞成发放令牌。
(E)客户端使用令牌,向资源服务器申请获取资源。
(F)资源服务器确认令牌无误,赞成向客户端开放资源。

客户端的受权模式
客户端必须获得用户的受权(authorization grant),才能得到令牌(access token)。OAuth 2.0定义了四种受权方式。

* 受权码模式(authorization code)
* 简化模式(implicit)
* 密码模式(resource owner password credentials)
* 客户端模式(client credentials)

咱们目前系统采用的是是受权码模式,受权码模式(authorization code)是功能最完整、流程最严密的受权模式。它的特色就是经过客户端的后台服务器,与"服务提供商"的认证服务器进行互动。

(A)用户访问客户端,后者将前者导向认证服务器。
(B)用户选择是否给予客户端受权。
(C)假设用户给予受权,认证服务器将用户导向客户端事先指定的"重定向URI"(redirection URI),同时附上一个受权码。
(D)客户端收到受权码,附上早先的"重定向URI",向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。
(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步骤中,服务器回应客户端的URI,包含如下参数:
* code:表示受权码,必选项。该码的有效期应该很短,一般设为10分钟,客户端只能使用该码一次,不然会被受权服务器拒绝。该码与客户端ID和重定向URI,是一一对应关系。
* 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,必选项,且必须与A步骤中的该参数值保持一致。
* 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步骤中,认证服务器发送的HTTP回复,包含如下参数:
* 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"
}

 

若是用户访问的时候,客户端的"访问令牌"已通过期,则须要使用"更新令牌"申请一个新的访问令牌。
客户端发出更新令牌的HTTP请求,包含如下参数:
* granttype:表示使用的受权模式,此处的值固定为"refreshtoken",必选项。
* refresh_token:表示早前收到的更新令牌,必选项。
* scope:表示申请的受权范围,不能够超出上一次申请的范围,若是省略该参数,则表示与上一次一致。

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

 

关于更多内容请查看
参考连接

理解OAuth 2.0
OAuth 受权的工做原理是怎样的?足够安全吗?
OAuth的改变
CAS实现单点登陆SSO执行原理探究
Oauth的access token 安全么?
OAuth 2.0攻击方法及案例总结
前端须要了解的 SSO 与 CAS 知识

相关文章
相关标签/搜索