⤴Top⤴

OAuth 2.0 及 SSO 单点登录

博客分类: 后端
修改内容:add SSO cas

OAuth 2.0 及 SSO 单点登录

OAuth 2.0 及 SSO 单点登录

什么是 OAuth

OAuth 即开放授权,是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源,而无需将用户名和密码提供给第三方应用。比如访问某小程序又不想注册时则可以使用微信授权。以下主要介绍 OAuth 2.0 ,它是 OAuth 协议的下一版本,但不向下兼容 OAuth 1.0。

运行流程

     +--------+                               +---------------+
     |        |--(A)- Authorization Request ->|   Resource    |                   A. 用户要访问客户端时,客户端需要用户给予授权。
     |        |                               |     Owner     |
     |        |<-(B)-- Authorization Grant ---|               |                   B. 用户同意给予客户端授权。
     |        |                               +---------------+
     |        |
     |        |                               +---------------+
     |        |--(C)-- Authorization Grant -->| Authorization |                   C. 客户端向认证服务器申请令牌。
     | Client |                               |     Server    |
     |        |<-(D)----- Access Token -------|               |                   D. 认证服务器对客户端进行认证后发放令牌。
     |        |                               +---------------+
     |        |
     |        |                               +---------------+
     |        |--(E)----- Access Token ------>|    Resource   |                   E. 客户端使用令牌,向资源服务器申请获取资源。
     |        |                               |     Server    |
     |        |<-(F)--- Protected Resource ---|               |                   F. 资源服务器确认令牌无误,同意向客户端开放资源。
     +--------+                               +---------------+

授权模式

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

授权码模式

授权码模式(authorization code)是功能最完整、流程最严密的授权模式。它的特点就是通过客户端的后台服务器,与”服务提供商”的认证服务器进行互动:

     +----------+
     | Resource |
     |   Owner  |
     |          |
     +----------+
          ^
          |
         (B)
     +----|-----+          Client Identifier      +---------------+
     |         -+----(A)-- & Redirection URI ---->|               |                   A. 用户访问客户端,后者将前者导向认证服务器。
     |  User-   |                                 | Authorization |
     |  Agent  -+----(B)-- User authenticates --->|     Server    |                   B. 用户选择是否给予客户端授权。
     |          |                                 |               |
     |         -+----(C)-- Authorization Code ---<|               |                   C. 认证服务器将用户导向客户端事先指定的重定向地址,同时附上一个授权码 code。
     +-|----|---+                                 +---------------+
       |    |                                         ^      v
      (A)  (C)                                        |      |
       |    |                                         |      |
       ^    v                                         |      |
     +---------+                                      |      |
     |         |>---(D)-- Authorization Code ---------'      |                        D. 客户端收到授权码,向认证服务器申请令牌。
     |  Client |          & Redirection URI                  |
     |         |                                             |
     |         |<---(E)----- Access Token -------------------'                        E. 认证服务器核对了授权码和重定向 URI,确认无误后,向客户端发送访问令牌和更新令牌。
     +---------+       (w/ Optional Refresh Token)

A 步骤中,客户端申请认证的 URI,包含以下参数:

/authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb

<!-- 简书登录跳微信认证的页面 -->
https://open.weixin.qq.com/connect/qrconnect?appid=wxe9199d568fe57fdd&client_id=wxe9199d568fe57fdd&redirect_uri=http%3A%2F%2Fwww.jianshu.com%2Fusers%2Fauth%2Fwechat%2Fcallback&response_type=code&scope=snsapi_login&state=%257B%257D#wechat_redirect

C 步骤中,服务器回应客户端的 URI,包含以下参数:

https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz

D 步骤中,客户端向认证服务器申请令牌的 HTTP 请求,包含以下参数:

grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb

E 步骤中,认证服务器发送的 HTTP 回复,包含以下参数:

{
  "access_token": "2YotnFZFEjr1zCsicMWpAA",
  "token_type": "example",
  "expires_in": 3600,
  "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA",
  "example_parameter": "example_value"
}

简化模式

简化模式(implicit grant type)不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了”授权码”这个步骤。所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证:

     +----------+
     | Resource |
     |  Owner   |
     |          |
     +----------+
          ^
          |
         (B)
     +----|-----+          Client Identifier     +---------------+
     |         -+----(A)-- & Redirection URI --->|               |                    A. 客户端将用户导向认证服务器。
     |  User-   |                                | Authorization |
     |  Agent  -|----(B)-- User authenticates -->|     Server    |                    B. 用户选择是否给于客户端授权。
     |          |                                |               |
     |          |<---(C)--- Redirection URI ----<|               |                    C. 认证服务器将用户导向客户端事先指定的重定向地址,并在 URI 的 Hash 部分包含了访问令牌。
     |          |          with Access Token     +---------------+
     |          |            in Fragment
     |          |                                +---------------+
     |          |----(D)--- Redirection URI ---->|   Web-Hosted  |                    D. 浏览器向资源服务器发出请求,其中不包括上一步收到的 Hash 值。
     |          |          without Fragment      |     Client    |
     |          |                                |    Resource   |
     |     (F)  |<---(E)------- Script ---------<|               |                    E. 资源服务器返回一个网页,其中包含的代码可以获取 Hash 值中的令牌。
     |          |                                +---------------+                    F. 浏览器执行上一步获得的脚本,提取出令牌。
     +-|--------+
       |    |
      (A)  (G) Access Token                                                           G. 浏览器将令牌发给客户端。
       |    |
       ^    v
     +---------+
     |         |
     |  Client |
     |         |
     +---------+

密码模式

密码模式(Resource Owner Password Credentials Grant)中,用户向客户端提供自己的用户名和密码。客户端使用这些信息,向”服务商提供商”索要授权。在这种模式中,用户必须把自己的密码给客户端,但是客户端不得储存密码。这通常用在用户对客户端高度信任的情况下,比如客户端是操作系统的一部分,或者由一个著名公司出品。而认证服务器只有在其他授权模式无法执行的情况下,才能考虑使用这种模式:

     +----------+
     | Resource |
     |  Owner   |
     |          |
     +----------+
          v
          |    Resource Owner
         (A) Password Credentials                                                     A. 用户向客户端提供用户名和密码。
          |
          v
     +---------+                                  +---------------+
     |         |>--(B)---- Resource Owner ------->|               |                   B. 客户端将用户名和密码发给认证服务器,向后者请求令牌。
     |         |         Password Credentials     | Authorization |
     | Client  |                                  |     Server    |
     |         |<--(C)---- Access Token ---------<|               |                   C. 认证服务器确认无误后,向客户端提供访问令牌。
     |         |    (w/ Optional Refresh Token)   |               |
     +---------+                                  +---------------+

客户端模式

客户端模式(Client Credentials Grant)指客户端以自己的名义,而不是以用户的名义,向”服务提供商”进行认证。严格地说,客户端模式并不属于 OAuth 框架所要解决的问题。在这种模式中,用户直接向客户端注册,客户端以自己的名义要求”服务提供商”提供服务,其实不存在授权问题:

     +---------+                                  +---------------+
     |         |                                  |               |
     |         |>--(A)- Client Authentication --->| Authorization |                   A. 客户端向认证服务器进行身份认证,并要求一个访问令牌。
     | Client  |                                  |     Server    |
     |         |<--(B)---- Access Token ---------<|               |                   B. 认证服务器确认无误后,向客户端提供访问令牌。
     |         |                                  |               |
     +---------+                                  +---------------+

更新令牌

如果用户访问的时候,客户端的”访问令牌”已经过期,则需要使用”更新令牌”申请一个新的访问令牌:

  +--------+                                           +---------------+
  |        |--(A)------- Authorization Grant --------->|               |
  |        |                                           |               |
  |        |<-(B)----------- Access Token -------------|               |
  |        |               & Refresh Token             |               |
  |        |                                           |               |
  |        |                            +----------+   |               |
  |        |--(C)---- Access Token ---->|          |   |               |
  |        |                            |          |   |               |
  |        |<-(D)- Protected Resource --| Resource |   | Authorization |
  | Client |                            |  Server  |   |     Server    |
  |        |--(E)---- Access Token ---->|          |   |               |
  |        |                            |          |   |               |
  |        |<-(F)- Invalid Token Error -|          |   |               |
  |        |                            +----------+   |               |
  |        |                                           |               |
  |        |--(G)----------- Refresh Token ----------->|               |
  |        |                                           |               |
  |        |<-(H)----------- Access Token -------------|               |
  +--------+           & Optional Refresh Token        +---------------+

部署

推荐阅读这篇文章 👈

什么是单点登录

单点登录(Single Sign On,即 SSO)是多域名企业站点流行的登录方式,指用户只需输入一次账密,在一处完成登录,之后可以直接进入所有业务系统。想要完成单点登录的效果,必须有一个唯一身份源,其他业务系统必须配合完成改造和对接。目前主流的 SSO 技术有 CAS、OAuth2、SAML、xxl-sso 等。

同域下的单点登录实现

普通的登陆认证机制可以基于 cookie 和 session 去实现,具体可参考这篇

一个企业一般情况下只有一个域名,通过二级域名区分不同的系统。比如我们有个域名叫做:a.com,同时有两个业务系统分别为:app1.a.com 和 app2.a.com。我们要做单点登录,需要一个登录系统,叫做:sso.a.com。

我们只要在 sso.a.com 登录,app1.a.com 和 app2.a.com 就也登录了。通过上面的登陆认证机制,我们可以知道,在 sso.a.com 中登录了,其实是在 sso.a.com 的服务端的 session 中记录了登录状态,同时在浏览器端的 sso.a.com 下写入了 Cookie。那么我们怎么才能让 app1.a.com 和 app2.a.com 登录呢?这里有两个问题:

  1. Cookie 是不能跨域携带的,我们 Cookie 的 domain 属性是 sso.a.com,在给 app1.a.com 和 app2.a.com 发送请求是带不上的。
  2. sso、app1 和 app2 是不同的应用,它们的 session 存在自己的应用内,是不共享的。

解决方案:

  1. 可以将 Cookie 的域设置为顶域,即.a.com,这样所有子域的系统都可以访问到顶域的 Cookie
  2. 共享 Session 的解决方案有很多,例如:Spring-Session

sso-cookie-session

跨域下的单点登录 CAS 实现

同域下的单点登录是巧用了 Cookie 顶域的特性。跨域的话可以使用 CAS 流程,这个流程是单点登录的标准流程。CAS(Central Authentication Service) 即中心授权服务,是耶鲁大学发起的一个开源项目,旨在为 Web 应用系统提供一种可靠的单点登录方法:

  1. 用户访问 app.example.com 网页,但用户未登录。
  2. 跳转到 CAS server,即 SSO 登录系统。SSO server 找不到用户信息,弹出用户登录页。
  3. 用户填写用户名、密码,SSO server 进行认证后,将登录状态写入 SSO 的 session(key 为 TGT,即 Ticket Granting Ticket),浏览器中写入 SSO 域下的 Cookie(名称为 CASTGC)。
  4. SSO server 登录完成后会生成一个 ST(Service Ticket),然后跳转到网页,同时将 ST 作为参数传递给网页。
  5. 网页拿到 ST 后,向 SSO 发送请求,验证 ST 是否有效。
  6. 验证通过后,网页将更新 session 并设置客户端的 Cookie(默认名为 jSESSIONID)。
  7. 用户再次访问该网页时,带上 cookie 即可认证。如果访问的是网页 2,由于 SSO 能够检测到用户,所以也不需要重新登录了,分配新的 ST 即可

sso-cas

至于为啥中间要多一步 ST 认证环节?用户在给 SSO 服务器提供了用户名密码后,作为业务系统并不知道这件事。SSO 随便给业务系统一个 ST,那么业务系统是不能确定这个 ST 是用户伪造的,还是真的有效,所以要拿着这个 ST 去 SSO 服务器再问一下,这个用户给我的 ST 是否有效,是有效的我才能让这个用户访问。

总体而言。从开发集成难易程度方面考虑,依次为 CAS、OAuth2 稍复杂,SAML 最复杂。OAuth2 是目前互联网最流行的单点登录技术,比如微信平台、QQ 平台、钉钉平台等,但在企业应用方面,OAuth2 使用远没有 CAS 多,尤其是企业存在大量的存量系统,有的是前后端分离架构,基于 token 认证鉴权,有的是传统 SOA 架构,基于中间件 session 会话认证鉴权,所以在企业内部改造 OAuth2 的成本比较高。SAML 是协议最复杂的一种 SSO,安全性最好,仅仅适用于 web,开发集成难度高,一般企业内部的应用系统不推荐使用。

参考链接

  1. The OAuth 2.0 Authorization Framework - RFC 6749
  2. 理解 OAuth 2.0 By 阮一峰
  3. OAuth 2.0 认证的原理与实践 By Way Lau
  4. OAuth 2.0: Bearer Token Usage By 熊猫猛男
  5. 单点登录(SSO)看这一篇就够了