浅谈SAML, OAuth和SSO, JWT和Session
nanshan 2024-10-22 12:55 8 浏览 0 评论
私信我或关注猿来如此呀公众号,回复:学习,即有机会免费领取30天视频学习资源包
通常为了弄清楚一个概念,我们需要掌握十个概念。在判断 JWT(JsonWebToken) 是否能代替 session 管理之前,我们要了解什么是 token,以及 access token 和 refresh token 的区别。
了解什么是 OAuth,什么是 SSO, SSO 下不同策略 OAuth 和 SAML 的不同,以及 OAuth 与 OpenID 的不同,更重要的是区分 authorisation和 authentication。
最后我们引出 JSON WEB TOKEN,聊聊 JWT?在 Session 管理方面的优势和劣势,同时尝试解决这些劣势,看看成本和代价有多少。
正文
本文关于 OAuth 授权和 API 调用实例都来自 GoogleAPI。
关于Token
Token 即使是在计算机领域中也有不同的定义,这里我们说的 token,是指 访问资源 的凭据。例如当你调用 GoogleAPI 时,需要带上有效 token 来表明你请求的 合法性。这个 Token是 Google 给你的,这代表 Google 给你的 授权 使得你有能力访问 API 背后的 资源。
请求 API 时携带 token 的方式也有很多种,通过 HTTPHeader 或者 url 参数或者 google 提供的类库都可以:
- HTTP Header
GET /drive/v2/files HTTP/ 1.1 Authorization : Bearer <token> Host : www.googleapis.com/
- URL参数
GET https://www.googleapis.com/drive/v2/files?token=<token>
- Python函数库
from googleapiclient.discovery import build drive = build( 'drive' , 'v2' , credentials=credentials)
更具体的说,上面用于调用 API 的 token,我们称为细分为 access token。通常 access token 是有 有效期限 的,如果 过期 就需要 重新获取。那么如何重新获取?先看看第一次获取 token 的流程是怎样的:
- 首先需要向 GoogleAPI 注册一个应用程序,注册完毕之后就会拿到 认证信息( credentials)包括 ID 和 secret。不是所有的程序类型都有 secret。
- 接下来就要向 Google 请求 access token。这里先忽略一些细节,例如请求参数(当然需要上面申请到的 secret)。重要的是,如果你想访问的是 用户资源,这里就会提醒用户进行 授权。
- 如果 用户授权 完毕。 Google 就会返回 access token。又或者是返回 授权代码( authorization code),再通过代码取得 access token。
- token 获取到之后,就能够带上 token 访问 API了。
流程如下图所示:
注意:在第三步通过 authorization code 兑换 access token 的过程中, Google 并不会仅仅返回 access token,还会返回额外的信息,这其中和之后更新相关的就是 refresh token。 一旦 access token 过期,你就可以通过 refresh token 再次请求 access token。
以上只是大致的流程,并且故意省略了一些额外的概念。比如更新 access token 当然也可以不需要 refresh token,这要根据你的 请求方式 和访问的 资源类型 而定。
这里又会引起另外的两个问题:
- 如果 refesh token 也过期了怎么办?这时就需要用户 重新登陆授权。
- 为什么要区分 refresh token 和 access token?如果合并成一个 token然后把 过期时间调整的 更长,并且每次 失效 之后用户 重新登陆授权 就好了?这个问题会和后面谈的相关概念有关,后面会给予解释说明。
OAuth
从获取 token 到使用 token 访问接口。这其实是标准的 OAuth2.0 机制下访问 API 的流程。这里介绍一下 OAuth 里外相关的概念,更深入的理解 token的作用。
SSO (Single sign-on)
通常公司内部会有非常多的平台供大家使用,比如人力资源,代码管理,日志监控,预算申请等等。如果每一个平台都实现自己的用户体系的话无疑是巨大的浪费,所以公司内部会有一套 公用的用户体系,用户只要登陆之后,就能够 访问所有的系统。这就是 单点登录。
SSO 是一类 解决方案 的统称,而在具体的实施方面,我们有两种策略可供选择:
- SAML 2.0
- OAuth 2.0
接下来我们区别这 两种授权方式 有什么不同。但是在描述 不同的策略 之前,我们先叙述几个 共有的特性,并且相当重要的概念。
Authentication VS Authorisation
- Authentication: 身份鉴别,以下简称 认证;
- Authorisation: 资源访问 授权。
认证 的作用在于 认可 你能够访问系统,用于 鉴别访问者 是否是 合法用户;而 授权 用于决定你有访问 哪些资源的权限。
大多数人不会区分这两者的区别,因为站在用户的立场上。而作为系统的设计者来说,这两者是有差别的,这是不同的两个工作职责。我们可以只需要 认证功能,而不需要 授权功能,甚至不需要自己实现 认证功能。而借助 Google 的认证系统,即用户可以用 Google 的账号进行登陆。
Authorization Server/Identity Provider(IdP)
把负责 认证的服务 称为 AuthorizationServer 或者 IdentityProvider,以下简称 IdP。
Service Provider(SP)/Resource Server
把负责 提供资源( API 调用)的服务称为 ResourceServer 或者 ServiceProvider,以下简称 SP。
SAML 2.0
下图是 SAML2.0 的流程图,看图说话:
- 还 未登陆 的用户 打开浏览器 访问你的网站( SP),网站 提供服务 但是并 不负责用户认证。
- 于是 SP 向 IDP 发送了一个 SAML 认证请求,同时 SP 将 用户浏览器 重定向到 IDP。
- IDP 在验证完来自 SP 的 请求无误 之后,在浏览器中呈现 登陆表单 让用户填写 用户名和 密码 进行登陆。
- 一旦用户登陆成功, IDP 会生成一个包含 用户信息(用户名 或者 密码)的 SAML token( SAML token 又称为 SAMLAssertion,本质上是 XML 节点)。 IDP 向 SP 返回 token, 并且将 用户重定向 到 SP ( token 的返回是在 重定向步骤 中实现的,下面会详细说明)。
- SP 对拿到的 token 进行验证,并从中解析出 用户信息,例如 用户是谁 以及 用户的权限 有哪些。此时就能够根据这些信息允许用户访问我们网站的内容。
当用户在 IDP 登陆成功之后, IDP 需要将用户 再次重定向 到 SP 站点,这一步通常有两个办法:
- HTTP 重定向:这并不推荐,因为 重定向 的 URL 长度 有限制,无法携带更长的信息,比如 SMALToken。
- HTTP POST 请求:这个是更常规的做法,当用户登陆完毕之后渲染出一个表单,用户点击后向 SP 提交 POST 请求。又或者可以使用 Javascript 向 SP 发出一个 POST 请求。
如果你的应用是基于 Web,那么以上的方案没有任何问题。但如果你开发的是一个 iOS 或者 Android 的手机应用,那么问题就来了:
- 用户在 iPhone 上打开应用,此时用户需要通过 IDP 进行认证。
- 应用跳转至 Safari 浏览器,在登陆认证完毕之后,需要通过 HTTP POST的形式将 token返回至 手机应用。
虽然 POST 的 url 可以 拉起应用,但是 手机应用 无法解析 POST 的内容,我们也就无法读取SAMLToken。
当然还是有办法的,比如在 IDP 授权阶段 不跳转至系统的 Safari 浏览器,在 内嵌 的 Webview中解决,在想方设法从 Webview 中提取 token,或者利用 代理服务器。
无论如何, SAML2.0 并 不适用 于当下 跨平台 的场景,这也许与它产生的年代也有关系,它诞生于 2005 年,在那个时刻 HTTP POST 确实是最好的选择方案。
OAuth 2.0
我们先简单了解 SSO 下的 OAuth2.0 的流程。
- 用户通过 客户端(可以是 浏览器 也可以是 手机应用)想要访问 SP 上的资源,但是 SP告诉用户需要进行 认证,将用户 重定向 至 IDP。
- IDP 向 用户 询问 SP 是否可以访问 用户信息。如果用户同意, IDP 向 客户端 返回 authorization code。
- 客户端拿到 authorization code 向 IDP 交换 access token,并拿着 access token 向 SP请求资源。
- SP 接受到请求之后,拿着附带的 token 向 IDP 验证 用户的身份。确认身份无误后, SP 向 客户端 发放相关资源。
那么 OAuth 是如何避免 SAML 流程下 无法解析 POST 内容的信息的呢?
- 一方面是用户从 IDP 返回 客户端 的方式,也是通过 URL 重定向,这里的 URL 允许 自定义 schema,所以即使在 手机 上也能 拉起应用;
- 另一方面因为 IDP 向 客户端 传递的是 authorization code,而不是 XML信息,所以 code 可以很轻易的附着在 重定向 URL 上进行传递。
但以上的 SSO 流程体现不出 OAuth 的本意。 OAuth 的本意是 一个应用 允许 另一个应用 在用户授权 的情况下 访问自己的数据。
OAuth 的设计本意更倾向于 授权而非认证(当然授权用户信息就间接实现了认证), 虽然 Google 的 OAuth2.0API 同时支持 授权 和 认证。所以你在使用 Facebook 或者 Gmail 账号登陆第三方站点时,会出现 授权对话框,告诉你 *第三方站点 可以访问你的哪些信息,需要征得你的同意。
在上面 SSO 的 OAuth 流程中涉及三方角色: SP, IDP 以及 Client。但在实际工作中 Client 可以是不存在的,例如你编写了一个 后端程序 定时的通过 GoogleAPI 从 Youtube 拉取最新的节目数据,那么你的 后端程序 需要得到 Youtube 的 OAuth 授权 即可。
OAuth VS OpenId
如果你有留心的话,你会在某些站点看到允许以 OpenID 的方式登陆,其实也就是以 Facebook账号或者 Google 账号登陆站点:
OpenID 和 OAuth 很像。但本质上来说它们是截然不同的两个东西:
- OpenID: 只用于 身份认证( Authentication),允许你以 同一个账户 在 多个网站登陆。它仅仅是为你的 合法身份 背书,当你以 Facebook 账号登陆某个站点之后,该站点无权访问 你的在 Facebook 上的 数据。
- OAuth: 用于 授权( Authorisation),允许 被授权方 访问 授权方 的 用户数据。
Refresh Token
现在可以回答上面的问题了,为什么我们需要 refresh token?
这样的处理是为了 职责的分离:
- refresh token: 负责 身份认证;
- access token: 负责 请求资源。
虽然 refresh token 和 access token 都由 IDP 发出,但是 access token还要和 SP 进行 数据交换,如果 公用的话 这样就会有 身份泄露 的可能。并且 IDP 和 SP 可能是 完全不同 的 服务提供 的。而在上文,我们之所以没有这样的顾虑是因为 IDP 和 SP 都是 Google。
JWT
初步认识
本质上来说 JWT 也是 token,正如我们在上文提到的,它是 访问资源 的 凭证。
Google 的一些 API 诸如 PredictionAPI 或者 GoogleCloudStorage,是不需要 访问 用户的 个人数据 的。因而不需要经过 用户的授权 这一步骤,应用程序可以直接访问。就像上面 OAuth中没有 Client 没有参与的流程类似。这就要借助 JWT 完成访问了, 具体流程如下:
- 首先需要在 GoogleAPI 上创建一个服务账号( service account)。
- 获取 服务账号 的 认证信息( credential),包括 邮箱地址, client ID,以及一对 公钥/私钥。
- 使用 client ID 和 私钥 创一个 签名 的 JWT,然后将这个 JWT 发送给 Google 交换 access token。
- Google 返回 access token。
- 程序通过 access token 访问 API。
甚至你可以不需要向 Google 索要 access token,而是携带 JWT 作为 HTTP header 里的 bearer token 直接访问 API 也是可以的。这才是 JWT 的最大魅力。
理性认识
JWT 顾名思义,它是 JSON 结构的 token,由三部分组成:
- header
- payload
- signature
header
header 用于描述 元信息,例如产生 signature 的算法:
{ "typ" : "JWT" , "alg" : "HS256" }
其中 alg 关键字就指定了使用哪一种 哈希算法 来创建 signature。
payload
payload 用于携带你希望 向服务端传递 的信息。你既可以往里添加 官方字段,例如: iss(Issuer), sub(Subject), exp(Expirationtime),也可以塞入 自定义的字段,比如 userId:
{ "userId" : "b08f86af-35da-48f2-8fab-cef3904660bd" }
signature
signature 译为 签名,创建签名要分以下几个步骤:
- 从 接口服务端 拿到 密钥,假设为 secret。
- 对 header 进行 base64 编码,假设结果为 headerStr。
- 将 payload 进行 base64 编码,假设结果为 payloadStr。
- 将 headerStr 和 payloadStr 用 . 字符 拼装起来成为字符 data。
- 以 data 和 secret 作为参数,使用 哈希算法 计算出 签名。
如果上述描述还不直观,用 伪代码 表示就是:
// signature algorithm data = base64urlEncode( header ) + “.” + base64urlEncode( payload ) signature = Hash ( data, secret );
假设我们的原始 JSON 结构是这样的:
// Header { "typ" : "JWT" , "alg" : "HS256" } // Payload { "userId" : "b08f86af-35da-48f2-8fab-cef3904660bd" }
如果 密钥 是字符串 secret 的话,那么最终 JWT 的结果就是这样的:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiJiMDhmODZhZi0zNWRhLTQ4ZjItOGZhYi1jZWYzOTA0NjYwYmQifQ.-xN_h82PHVTCMA9vdoHrcZxH-x5mb11y1537t3rGzcM
可以在 jwt.io 上 验证 这个结果。
JWT究竟带来了什么
确保数据完整性
JWT 的目的不是为了 隐藏 或者 保密数据,而是为了确保 数据 确实来自被 授权的人 创建的,以防止 中途篡改。
回想一下,当你拿到 JWT 时候,你完全可以在没有 secret 的情况下解码出 header 和 payload,因为 header 和 payload 只是经过了 base64 编码( encode)而已,编码的目的在于 利于数据结构的传输。
虽然创建 signature 的过程近似于 加密 ( encrypt),但本质其实是一种 签名 ( sign) 的行为,用于保证 数据的完整性,实际上也并且并 没有加密任何数据。
用于接口调用
接下来在 API 调用中就可以附上 JWT (通常是在 HTTPHeader 中)。又因为 SP 会与程序 共享 一个 secret,所以 程序 可以通过 header 提供的相同的 hash 算法来 验证签名 是否正确,从而判断应用是否有权力调用 API。
有状态的对话Session
因为 HTTP 是 无状态 的,所以 客户端 和 服务端 需要解决的问题是,如何让它们之间的对话变得有状态。例如只有是 登陆状态 的 用户 才有权限调用某些接口,那么在 用户登陆 之后,需要记住该用户是 已经登陆 的状态。常见的方法是使用 session 机制。
常见的 session 模型是这样工作的:
- 用户在浏览器 登陆 之后,服务端为用户生成 唯一 的 session id,存储在 服务端 的 存储服务(例如 MySQL, Redis)中。
- 该 session id 也同时 返回给浏览器,以 SESSION_ID 为 KEY 存储在浏览器的 cookie中。
- 如果用户再次访问该网站, cookie 里的 SESSION_ID 会随着 请求 一同发往 服务端。
- 服务端通过判断 SESSION_ID 是否已经在 Redis 中判断用户是否处于 登陆状态。
相信你已经察觉了,理论上来说, JWT 机制可以取代 session 机制。用户不需要提前进行登陆,后端也不需要 Redis 记录用户的登陆信息。客户端的本地保存一份合法的 JWT,当用户需要调用接口时,附带上该合法的 JWT,每一次调用接口,后端都使用请求中附带的 JWT 做一次 合法性的验证。这样也间接达到了 认证用户 的目的。
来源网络,如有侵权请联系删除
相关推荐
- 实战派 | Java项目中玩转Redis6.0客户端缓存
-
铺垫首先介绍一下今天要使用到的工具Lettuce,它是一个可伸缩线程安全的redis客户端。多个线程可以共享同一个RedisConnection,利用nio框架Netty来高效地管理多个连接。放眼望向...
- 轻松掌握redis缓存穿透、击穿、雪崩问题解决方案(20230529版)
-
1、缓存穿透所谓缓存穿透就是非法传输了一个在数据库中不存在的条件,导致查询redis和数据库中都没有,并且有大量的请求进来,就会导致对数据库产生压力,解决这一问题的方法如下:1、使用空缓存解决对查询到...
- Redis与本地缓存联手:多级缓存架构的奥秘
-
多级缓存(如Redis+本地缓存)是一种在系统架构中广泛应用的提高系统性能和响应速度的技术手段,它综合利用了不同类型缓存的优势,以下为你详细介绍:基本概念本地缓存:指的是在应用程序所在的服务器内...
- 腾讯云国际站:腾讯云服务器如何配置Redis缓存?
-
本文由【云老大】TG@yunlaoda360撰写一、安装Redis使用包管理器安装(推荐)在CentOS系统中,可以通过yum包管理器安装Redis:sudoyumupdate-...
- Spring Boot3 整合 Redis 实现数据缓存,你做对了吗?
-
你是否在开发互联网大厂后端项目时,遇到过系统响应速度慢的问题?当高并发请求涌入,数据库压力剧增,响应时间拉长,用户体验直线下降。相信不少后端开发同行都被这个问题困扰过。其实,通过在SpringBo...
- 【Redis】Redis应用问题-缓存穿透缓存击穿、缓存雪崩及解决方案
-
在我们使用redis时,也会存在一些问题,导致请求直接打到数据库上,导致数据库挂掉。下面我们来说说这些问题及解决方案。1、缓存穿透1.1场景一个请求进来后,先去redis进行查找,redis存在,则...
- Spring boot 整合Redis缓存你了解多少
-
在前一篇里面讲到了Redis缓存击穿、缓存穿透、缓存雪崩这三者区别,接下来我们讲解Springboot整合Redis中的一些知识点:之前遇到过,有的了四五年,甚至更长时间的后端Java开发,并且...
- 揭秘!Redis 缓存与数据库一致性问题的终极解决方案
-
在现代软件开发中,Redis作为一款高性能的缓存数据库,被广泛应用于提升系统的响应速度和吞吐量。然而,缓存与数据库之间的数据一致性问题,一直是开发者们面临的一大挑战。本文将深入探讨Redis缓存...
- 高并发下Spring Cache缓存穿透?我用Caffeine+Redis破局
-
一、什么是缓存穿透?缓存穿透是指查询一个根本不存在的数据,导致请求直接穿透缓存层到达数据库,可能压垮数据库的现象。在高并发场景下,这尤其危险。典型场景:恶意攻击:故意查询不存在的ID(如负数或超大数值...
- Redis缓存三剑客:穿透、雪崩、击穿—手把手教你解决
-
缓存穿透菜小弟:我先问问什么是缓存穿透?我听说是缓存查不到,直接去查数据库了。表哥:没错。缓存穿透是指查询一个缓存中不存在且数据库中也不存在的数据,导致每次请求都直接访问数据库的行为。这种行为会让缓存...
- Redis中缓存穿透问题与解决方法
-
缓存穿透问题概述在Redis作为缓存使用时,缓存穿透是常见问题。正常查询流程是先从Redis缓存获取数据,若有则直接使用;若没有则去数据库查询,查到后存入缓存。但当请求的数据在缓存和数据库中都...
- Redis客户端缓存的几种实现方式
-
前言:Redis作为当今最流行的内存数据库和缓存系统,被广泛应用于各类应用场景。然而,即使Redis本身性能卓越,在高并发场景下,应用于Redis服务器之间的网络通信仍可能成为性能瓶颈。所以客户端缓存...
- Nginx合集-常用功能指导
-
1)启动、重启以及停止nginx进入sbin目录之后,输入以下命令#启动nginx./nginx#指定配置文件启动nginx./nginx-c/usr/local/nginx/conf/n...
- 腾讯云国际站:腾讯云怎么提升服务器速度?
-
本文由【云老大】TG@yunlaoda360撰写升级服务器规格选择更高性能的CPU、内存和带宽,以提供更好的处理能力和网络性能。优化网络配置调整网络接口卡(NIC)驱动,优化TCP/IP参数...
- 雷霆一击服务器管理员教程
-
本文转载莱卡云游戏服务器雷霆一击管理员教程(搜索莱卡云面版可搜到)首先你需要给服务器设置管理员密码,默认是空的管理员密码在启动页面进行设置设置完成后你需要重启服务器才可生效加入游戏后,点击键盘左上角E...
你 发表评论:
欢迎- 一周热门
-
-
爱折腾的特斯拉车主必看!手把手教你TESLAMATE的备份和恢复
-
如何在安装前及安装后修改黑群晖的Mac地址和Sn系列号
-
[常用工具] OpenCV_contrib库在windows下编译使用指南
-
WindowsServer2022|配置NTP服务器的命令
-
Ubuntu系统Daphne + Nginx + supervisor部署Django项目
-
WIN11 安装配置 linux 子系统 Ubuntu 图形界面 桌面系统
-
解决Linux终端中“-bash: nano: command not found”问题
-
Linux 中的文件描述符是什么?(linux 打开文件表 文件描述符)
-
NBA 2K25虚拟内存不足/爆内存/内存占用100% 一文速解
-
K3s禁用Service Load Balancer,解决获取浏览器IP不正确问题
-
- 最近发表
-
- 实战派 | Java项目中玩转Redis6.0客户端缓存
- 轻松掌握redis缓存穿透、击穿、雪崩问题解决方案(20230529版)
- Redis与本地缓存联手:多级缓存架构的奥秘
- 腾讯云国际站:腾讯云服务器如何配置Redis缓存?
- Spring Boot3 整合 Redis 实现数据缓存,你做对了吗?
- 【Redis】Redis应用问题-缓存穿透缓存击穿、缓存雪崩及解决方案
- Spring boot 整合Redis缓存你了解多少
- 揭秘!Redis 缓存与数据库一致性问题的终极解决方案
- 高并发下Spring Cache缓存穿透?我用Caffeine+Redis破局
- Redis缓存三剑客:穿透、雪崩、击穿—手把手教你解决
- 标签列表
-
- linux 查询端口号 (58)
- docker映射容器目录到宿主机 (66)
- 杀端口 (60)
- yum更换阿里源 (62)
- internet explorer 增强的安全配置已启用 (65)
- linux自动挂载 (56)
- 禁用selinux (55)
- sysv-rc-conf (69)
- ubuntu防火墙状态查看 (64)
- windows server 2022激活密钥 (56)
- 无法与服务器建立安全连接是什么意思 (74)
- 443/80端口被占用怎么解决 (56)
- ping无法访问目标主机怎么解决 (58)
- fdatasync (59)
- 405 not allowed (56)
- 免备案虚拟主机zxhost (55)
- linux根据pid查看进程 (60)
- dhcp工具 (62)
- mysql 1045 (57)
- 宝塔远程工具 (56)
- ssh服务器拒绝了密码 请再试一次 (56)
- ubuntu卸载docker (56)
- linux查看nginx状态 (63)
- tomcat 乱码 (76)
- 2008r2激活序列号 (65)