盘点认证协议 : 普及篇之OAuth , OIDC , CAS

5,931 阅读13分钟

总文档 :文章目录
Github : github.com/black-ant

这一篇来聊一聊老本行 - 身份安全的相关概念 . 主要来说一说一般身份认证中常见的几种协议 这一篇主要说 OAuth , OIDC , CAS . 至于SAML , 我严重怀疑一篇说不完....

一 . 前言

我这里试着把协议分为几大类 :

  • 纯约束型协议 : OAuth , SAML , OIDC , CAS
  • 服务器类协议 : RADIUS , Kerberos , ADFS
  • 认证方式类 : OTP , 生物认证 (人脸 , 声纹 , 指纹)
  • 认证服务器(附带) : AD , LDAP

这一篇我们只对流程进行一个普及 , 后面陆陆续续来分析一下其中的实现方式.

1.1 前置知识点 Token

Token 是认证过程中最常见的一个概念 , 它没有特定的规范 , 它仅仅是一个有着不同协议特征的钥匙 . 通常而言 , 他是有一定规律的无意义的字符串

1.2 前置知识点 JWT

JWT 全称 Json web token , JWT 通常由三部分组成 :

  • JWT 头 : 头部以 JSON 格式表示
    • alg : 签名使用的算法
    • typ : 令牌的类型 ,统一为 JWT
  • 有效载荷
    • iss:Issuer Identifier:必须。提供认证信息者的唯一标识
    • exp:Expiration time : 过期时间,超过此时间的ID Token会作废不再被验证通过
    • sub:Subject Identifier:主题。iss提供的EU的标识,在iss范围内唯一。它会被RP用来标识唯一的用户
    • aud:用户
    • nbf:在此之前不可用
    • iat: Issued At Time :JWT的构建的时间
    • jti:JWT ID用于标识该JWT
    • auth_time : AuthenticationTime : EU完成认证的时间
    • acr : Authentication Context Class Reference:可选。表示一个认证上下文引用值,可以用来标识认证上下文类
    • amr : Authentication Methods References:可选。表示一组认证方法
    • azp = Authorized party:可选。结合aud使用。只有在被认证的一方和受众(aud)不一致时才使用此值,一般情况下很少使用
  • 签名

二 . 纯约束型

纯约束型表示其长得就像个规范 ,而不同的框架去实现这个规范 (其实规范也叫框架 , 是一种狭义的框架)

2.1 OAuth 协议

2.1.1 OAuth 漫谈

通常我们说的 OAuth 是指其 2.0 版本 , 现在OAuth 已经公布了其 2.1 的版本 , 在结构上做了简化 , OAuth 其实不是一个完整的概念 , 它实际上是由许多不同的 rfc 组成的 (RFC 6749 , RFC 6750),它们以不同的方式相互构建并添加特性 , 就如下图所示 :

@ aaronparecki.com/2019/12/12/… oauth-maze.png

在整个 OAuth 协议的发展中 , 他被陆陆续续添加了多个规范 , 并且实现了多种功能.

例如 RFC 6749 中就定义了我们用的最多的四种授权类型: 授权代码、隐式、密码和客户端凭据 , 而 RFC 7636 中则加入了 PKCE (一种无需客户机机密就可以使用授权代码流的方法) , RFC 8252 中将其建议为本机应用程序使用 .

>>> 直到陆陆续续到了 OAuth2.1 , 还剩下以下三种类型 :

  • Authorization code + PKCE
  • Client Credentials
  • Device Grant

这一篇不会涉及到过多的 OAuth2.1 , 主要是我的实现代码还没有写完...

OAuth2.0 支持的类型 :

  • 密码模式(resource owner password credentials)
  • 授权码模式(authorization code)
  • 简化模式(implicit)
  • 客户端模式(client credentials)
  • 新设备 (Device Grant) : 没有浏览器或者没有键盘的设备

OAuthType.png

2.1.2 OAuth 2.0 的流程 :

OAuth 的整个流程大概就长下面那样 :

  • Step 1 : 用一个方式去认证 , 认证成功后返回一个票据AccessToken (这个不限于一步完成)
  • Step 2 : 用返回的票据获取用户信息

OAuthCommon.jpg

OAuth2.0 的流程网络上已经说的太多了 ,自认为不会被前辈们画的更好 , 这里也就直接引用了:

总结性归类 :

这里细说一下三者的区别 :

Code 方式 : Code 方式一般是企业最常用的一种方式 , 因为它很灵活 , 安全性也高 , 它和 implicit 以及 password 模式的区别主要是多了一个获取 Code 的过程 :

Step 1: Authoriza - > code : 发起请求返回code
Step 2: Code -> Token : 传递Code 换取Toekn
Step 3: Token -> UserInfo : 传递Token 换取用户信息

implicit 方式 : implicit 方式是相对于 Code 的简化版 , 它由 Step1 Authoriza 直接来到了Token 步骤

password 方式 : password 方式就变化较大了 ,它省去了跳转登录页认证的步骤 , 直接获取Token

OAuth Code 方式

OAuthCode.jpg

OAuth implicit 方式

OAuthImplict.jpg

OAuth Password 方式

OAuthPassword.jpg

OAuth 协议的接口请求一览

// Authoriza Code 模式
* 第一步 : http://localhost:8080/oauth/authorize?response_type=code&client_id=pair&redirect_uri=http://baidu.com

> Response_type -> 返回类型
> Client_id-> 对应的client id 
> redirect_uri->重定向的地址 

* 第二步 :
http://localhost:8080/oauth/token?grant_type=authorization_code&code=o4YrCS&client_id=pair&client_secret=secret&redirect_uri=http://baidu.com 

> grant_type
> code    
> client_id   
> client_secret   
> redirect_uri 

* 第三步 :
通过Token 换取信息 ... 略

----------------------------
// implicit 模式 (略 , 第一步直接返回Code)


----------------------------
// Password 模式 (直接传入密码)
http://localhost:8080/oauth/accessToken?grant_type=password&client_id=b7a8cc2a-5dec-4a64&username=admin&password=123456


2.1.3 OAuth FAQ

问题一 : state 的作用 问题 : 当被攻击人(平民 A )登录时 ,让 A 认为登录的是自己的账号 ,但是 ,实际上 ,登录的是 攻击者 (狼人B)事先准备的账号 ,这就导致 A 在其上做的操作 ,B均可见 。

  • B 事先准备好攻击账号 ,进入第一步临时授权 ,获取到Code , 此时 ,基础验证已经完成 ,剩下访问 授权服务器 获取 Access Token.
  • B 此时强行停止 自身验证流程 ,骗取 A 进行点击 ,让 A 通过Code 完成后续登录 ,此时 A 以为登录的自身账号 ,并且 以为 正确进入系统

这就是通常说的中间人攻击 , 而有了 state , 开发者可以用这个参数验证请求有效性,也可以记录用户请求授权页前的位置 , 当然 ,也可以预防 CSRF

问题二 : implicit 和 Password 存在的场景 ?

Password 从请求上就可以看出有一定的安全漏洞 ,如果没有 SSL + 明文密码 , 这简直是把密码告诉别人了 , 而在我的使用中, 部分应用是发起存后台请求 , 不期望进行跳转 , 这个时候 , password 就能派上用场

问题三 : 待完善 TODO ~~~~~

2.2 OIDC 协议

讲了 OAuth 当然要来说一下 OIDC 这个小兄弟啦 , OIDC 其实很简单 , 就是在 OAuth 的基础上加入了 OpenID 的概念 , 你如果为了方便 , 复用 OAuth 的代码都没问题的. 即在 OAuth 的基础上额外携带一个 JWT 传递用户信息

2.2.1 OIDC 简介

OpenID Connetction : OIDC= (Identity, Authentication) + OAuth 2.0 , 它是一个基于 OAuth 2 的身份认证标准协议 , 通过 OAuth 2.0 构建了一个身份层 .

OIDC 提供了ID Token 来解决第三方客户端标识用户身份 的问题 ,在Oauth2 的授权流程中 ,一并提供用户的身份认证信息给第三方客户端 ,ID token 使用JWT 格式进行包装 (得益于 JWT 的包容性 紧凑性 和 防篡改机制 ,并且提供 UserInfo ,可以回看一下开头的 JWT 扩展哦 )

OIDC 构成 信息

- core : 定义 OIDC 的核心功能 ,在OAuth 2 之上 构建身份认证 ,以及使用 Claims 来传递用户信息 
- Discovery :  发现服务  ,  用于客户端动态的获取OIDC 服务相关的元数据描述信息
- Dynamic Registration : 动态注册服务 , 使客户端可以动态的注册到OIDC 的 OP 
- OAuth 2.0 Multiple Response Types :可选。针对OAuth2的扩展,提供几个新的response_type。
- OAuth 2.0 Form Post Response Mode:可选。针对OAuth2的扩展,OAuth2回传信息给客户端是通过URL的querystring和fragment这两种方式,这个扩展标准提供了一基于form表单的形式把数据post给客户端的机制。
- Session Management :可选。Session管理,用于规范OIDC服务如何管理Session信息
- Front-Channel Logout:可选。基于前端的注销机制,使得RP(这个缩写后面会解释)可以不使用OP的iframe来退出
- Back-Channel Logout:可选。基于后端的注销机制,定义了RP和OP直接如何通信来完成注销

2.2.2 OIDC 请求流程

  1. RP 向 OP 申请 授权 ,OP 返回授权Access Token 以及 ID Token , 使用Access Token 向 User Info EndPoint 请求 信息
  2. AuthN 请求虽然是复用OAuth 2 的 Authorization 请求 ,但是用途不一样 ,OIDC 的 authN scope 参数 必须要有一个openid 的参数
The RP (Client) sends a request to the OpenID Provider (OP).
The OP authenticates the End-User and obtains authorization.
The OP responds with an ID Token and usually an Access Token.
The RP can send a request with the Access Token to the UserInfo Endpoint.
The UserInfo Endpoint returns Claims about the End-User. 
  
+--------+                                   +--------+
|        |                                   |        |
|        |---------(1) AuthN Request-------->|        |
|        |                                   |        |
|        |  +--------+                       |        |
|        |  |        |                       |        |
|        |  |  End-  |<--(2) AuthN & AuthZ-->|        |
|        |  |  User  |                       |        |
|   RP   |  |        |                       |   OP   |
|        |  +--------+                       |        |
|        |                                   |        |
|        |<--------(3) AuthN Response--------|        |
|        |                                   |        |
|        |---------(4) UserInfo Request----->|        |
|        |                                   |        |
|        |<--------(5) UserInfo Response-----|        |
|        |                                   |        |
+--------+                                   +--------+

// 详细步骤 : 
》 OIDC 单点登录流程
  > 1 . 用户点击登录 ,触发对OIDC-SERVER 的认证请求
     |-> request : 包含参数URL , 指向登录成功后的跳转地址
     |-> response : 返回 302 ,Location 指向 OIDC-SERVER ,Set-Cookie 设置了 nonce的cookie
  > 2 . 向 OIDC-SERVER 发起 authc 请求
     |-> client_id=implicit-client :发起认证请求的客户端的唯一标识
     |-> reponse_mode=form_post :使用form表单的形式返回数据
     |-> response_type=id_token :返回包含类型 id_token
     |-> scope=openid profile :返回包含有openid这一项
     |-> state :等同于OAuth2 state ,用于保证客户端一致性
     |-> nonce : 写入的cookie 值
     |-> redirect_uri : 认证成功后的回调地址
  > 3 . OIDC-SERVER 验证 authc 请求
     |-> client_id是否有效,redircet_uri是否合法 等一系列验证
  > 4 . 引导用户登录 ,以及用户登录 
     |-> resumeURL 
     |-> username + password     
  > 5 . 返回一个自动提交form 表单的页面
     |-> id_token:id_token即为认证的信息,OIDC的核心部分,采用JWT格式包装的一个字符串
     |-> scope:用户允许访问的scope信息
     |-> state : 类似
     |-> session_state :会话状态    
  > 6 . 验证数据有效性,构造自身登录状态
     |-> 客户端验证id_token的有效性 ,保证客户端得到的id_token是oidc-sercer.dev颁发的

OIDC 接口演示 ------------> 真不记得是哪位大佬的案例了....


// OIDC 直观流程 请求地址  :
// Step 1 : 发起 Authorize 请求 
https://${yourDomain}/oauth2/default/v1/authorize?response_type=code
&client_id=12345
&redirect_uri=https://proxy.example.com:3080/v1/webapi/oidc/callback
&scope=openid,email
&state=syl

// Step 2 : 认证成功后重定向返回
https://proxy.example.com:3080/v1/webapi/oidc/callback?code=pkzdZumQi1&state=syl

// Step 3 : 申请 Token
POST https://${yourOktaDomain}/oauth2/default/v1/token
?grant_type=authorization_code& 
code=pkzdZumQi1& 
redirect_uri=https://proxy.example.com:3080/v1/webapi/oidc/callback
client_id=12345& 
client_secret=gravitational
    
// Step 4 : AccessToken 返回
{
"id_token":"FW6AlBeyalZtDIRXxA0u5XBbZkLzjYzKUQBloxQXSSGPFmRS8eSfDu0A4nS4GF1aQP9PRxQk7gIh9bjaX99
aa4vDSzP1E2ajsgIomlNGhNxBqEDV5Exp0xISE9bZ4HUzM91pbzPPj7Bq5ZQUWcSuSVD0NAfkAoG6qDpbQfxPjWRyfthz3p
UEXwZe8Cz4eOXOM45UKB4Q0VnVSChVF84MWkeBFKzhrRNXd2dFv0HTlkQr6vXGlYsocMxR06wo38HvGiKjkUmL2YUyPOjZa
oUN4ovfwlwdGdjNR2GVcRsXzjxCPszJ9dTXztoL5wo2ycEpuxkkNp57BuZ9YRexoNnRHahFKH76XrFsTvdvAYk3fBVUqrO5
vvyxHAFrAIKpV0FvaMiBwKNfaE84oRC6aBXnzS3q4uVyGcHveHQMJB1temgB599rfVH3pBqurUmQCd0tVexRZj4PUkrDocf
8Z0QKkCD0eonH0Q1bRpQPY5vATiLkpF8RArU7wyB2FxhB3egtQBvwDgsVjyix7u8Cx4P9oy3IJje6SZfc6Lz61uEQttpVhy
qfzgFYUqVoQacw6rocCn3u61dM0moB"
"access_token":"IEZKr6ePPtxZBEd",
"token_type":"bearer",
"scope":"read:org",
"expires_in":3600
}

// Step 5 : 对 id_token 进行解码
{
    "sub":"virag",
    "iss":"https://${yourOktaDomain}/oauth2/",
    "aud":"client_12345",
    "iat":"1595977376",
    "exp":"1595980976",
    "email":"virag@goteleport.com",
    "email_verified":"true"
}

// Step 6 : 对资源进行请求

2.2.3 OIDC 扩展文档

https://www.ubisecure.com/education/differences-between-saml-oauth-openid-connect/

https://www.okta.com/identity-101/whats-the-difference-between-oauth-openid-connect-and-saml/

https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-protocols

https://yangsa.azurewebsites.net/index.php/2019/08/08/brief-summary-of-differences-between-oauth2-and-oidc/

https://www.c-sharpcorner.com/article/oauth2-0-and-openid-connect-oidc-core-concepts-what-why-how/

2.2.4 OIDC 的额外知识点

OIDC Discovery 规范

定义了一个服务发现的规范,它定义了一个api( /.well-known/openid-configuration ),这个api返回一个json数据结构,其中包含了一些OIDC中提供的服务以及其支持情况的描述信息,这样可以使得oidc服务的RP可以不再硬编码OIDC服务接口信息

会话管理

  • Session Management :可选。Session管理,用于规范OIDC服务如何管理Session信息。
  • Front-Channel Logout:可选。基于前端的注销机制。
  • Back-Channel Logout:可选。基于后端的注销机制。

OIDC 的好处

  • OIDC使得身份认证可以作为一个服务存在。
  • OIDC可以很方便的实现SSO(跨顶级域)。
  • OIDC兼容OAuth2,可以使用Access Token控制受保护的API资源。
  • OIDC可以兼容众多的IDP作为OIDC的OP来使用。
  • OIDC的一些敏感接口均强制要求TLS,除此之外,得益于JWT,JWS,JWE家族的安全机制,使得一些敏感信息可以进行数字签名、加密和验证,进一步确保整个认证过程中的安全保障。

2.3 CAS

CAS 我可太熟了 , 这还不随便和大家扯淡~~~~

2.3.1 CAS 术语

CAS分为两部分,CAS Server和CAS Client

  • CAS Server用来负责用户的认证工作,就像是把第一次登录用户的一个标识存在这里
  • CAS Client就是我们自己开发的应用程序,需要接入CAS Server端

CAS 的三个术语

  • Ticket Granting ticket (TGT) :可以认为是CAS Server根据用户名密码生成的一张票,存在Server端
    • 缓存后会配合TGC 生成ST (因为TGT 可以标识用户已经登陆过)
  • Ticket-granting cookie (TGC) :其实就是一个Cookie,存放用户身份信息,由Server发给Client端
  • Service ticket (ST) :由TGT生成的一次性票据,用于验证,只能用一次

2.3.2 CAS 处理流程

  • 用户第一次访问网站,重定向到CAS Client , 发现没有cookie(TGC或者没有ST) ,重定向到 CAS Server端的登录页面
  • 在登陆页面输入用户名密码认证(Web 和 Server 交互),认证成功后cas-server生成TGT,再用TGT生成一个ST
  • 然后再第三次重定向并返回ST和cookie(TGC)到浏览器
  • 浏览器带着ST再访问想要访问的地址
  • 浏览器的服务器收到ST后再去cas-server验证一下是否为自己签发的
  • 再登陆另一个接入CAS的网站,重定向到CAS Server,server判断是第一次来(但是此时有TGC,也就是cookie,所以不用去登陆页面了)
  • 但此时没有ST,去cas-server申请一个于是重定向到cas-server
  • cas-server 通过TGT + TGC 生成了ST
  • 浏览器的服务器收到ST后再去cas-server验证一下是否为自己签发的

我知道一般人懒得看 ,我也是 ,所以我画了一张图!!!

cas.jpg

2.3.3 CAS FAQ

CAS 与 OAuth 最大的几个区别 :

CAS 和 OAuth 都是一种认证结构/协议 , 而 Token/ST则是属于一种票据的方式 , 并没有特定的归属

  • 1 资源和用户信息 :
    • 首先要明确这两者不是同一种东西 !
    • 资源是受保护的 , 是认证成功后才可以访问的 , 通常情况下 , CAS 的资源在客户端 , OAuth 方式的资源存在于服务端
    • 用户信息在认证系统里面是登录时 ,和认证成功后返回的 , 其显式或者隐式存在于服务端
  • 2 申请流程的细微区别:
    • CAS 完成二次认证时 (已经登陆过), 可以直接拿着ST 去进行认证 , 这样做的原因一个是因为其认session 和 Token 中
    • OAuth 进行认证的时候 , 除了需要一个临时码(Code) , 另外需要一个ClientSecret , 用来判断客户端是否合法
  • 3 客户端 :
    • 基于上一点 , CAS 看来 , 客户端基本上是一致的 , 不论何种 ,将TGC 放入Cookie 就行
    • OAuth 的客户端虽然业务上是同一类东西 , 但是人为的对客户端进行了身份处理

CAS 认证的票据 :

CAS 通过将 TGC 写入 Cookie , 当下次认证是从 Cookie 中取出 TGC 认证 ,所以要想做跨浏览器登录 , 可以在这里做文章哦 !

总结

今天先这样了 , 又要到转钟了 , 这篇文章既是盘点 , 也是对自身知识图谱的完善 , 认证协议多种多样 , 常规的应用通常只需要选择其中最合适的一种去实现自己的业务即可.

参考与感谢

讲道理 , 挺多的 ,但是记得时候又没有把地址记下来 , 懒得找了 , 直接祝大家身体健康吧