鉴权大全(cookie、session、token、jwt、单点登录),深入理解和搞懂鉴权

2023-10-28

先从状态说起吧

众所周知http是无状态的协议,也就是说,HTTP 请求方和响应方间无法维护状态,都是一次性的,它不知道前后的请求都发生了什么。(是不是可以单纯的理解为它是一个超级无敌健忘的协议)

但有的场景下,我们需要维护状态。最典型的,一个用户登陆我们CSDN博客,发布、关注、评论,都应是在登录后的用户状态下的。那我每次打开页面你都让我登录?我能不给你差评吗?实在是太繁琐了吧。那怎么办呢?

标记

这个东西就像身份证一样,坐高铁的时候刷一下身份证就能进,坐飞机的时候刷一下身份证也能进,有了身份证你就是一个合法的人,没有它你想出入一些比较正规的场合就是会有问题。

前端存储

说完身份证,我至少要有个口袋装一下身份证吧?要求更高一点,身份证能随便放在口袋里吗?不怕丢吗?所以是不是要找一个合适的地方去放置我的身份证呢?

前端的存储方式有很多。

  • 最低级的,挂到全局变量上,但这是个“体验卡”,一次刷新页面就没了
  • 高端点的,存到 cookie、localStorage 等里,这属于“会员卡”,无论怎么刷新,只要浏览器没清掉或者过期,就一直拿着这个状态。

有地方存了,请求的时候就可以拼到参数里带给接口了。

COOKIE(cookie)

刚刚的方式听起来就感觉很繁琐啊,我(前端)又要存,又要拼到参数里带给接口,有没有简单高效一点的方法?毕竟懒惰是生产力发展的第一要素是不是

答案就是 cookie。

cookie 也是前端存储的一种,但相比于 localStorage 等其他方式,借助 HTTP 头、浏览器能力,cookie 可以做到前端无感知。

一般过程是这样的:

  • 在提供标记的接口,通过 HTTP 返回头的 Set-Cookie 字段,直接“种”到浏览器上
  • 浏览器发起请求时,会自动把 cookie 通过 HTTP 请求头的 Cookie 字段,带给接口

配置:Domain/path

身份证是好身份证,你能拿着身份证在人家America随意出入吗?那不合适了,没这么一个说法。

那cookie也是要限制空间范围的,就是通过Domain、path去设置(这里简单一说,毕竟不是今天重点)

Domain属性指定浏览器发出 HTTP 请求时,哪些域名要附带这个 Cookie。如果没有指定该属性,浏览器会默认将其设为当前 URL 的一级域名,比如 http://www.example.com 会设为 http://example.com,而且以后如果访问http://example.com的任何子域名,HTTP 请求也会带上这个 Cookie。如果服务器在Set-Cookie字段指定的域名,不属于当前域名,浏览器会拒绝这个 Cookie。
Path属性指定浏览器发出 HTTP 请求时,哪些路径要附带这个 Cookie。只要浏览器发现,Path属性是 HTTP 请求路径的开头一部分,就会在头信息里面带上这个 Cookie。比如,PATH属性是/,那么请求/docs路径也会包含该 Cookie。当然,前提是域名必须一致。

配置:Expires/Max-Age

身份证是好身份证,你拿着你三岁拍的照片的身份证,用到八十岁你还用这个身份证能行吗?这不合适了,没有这么一个说法的

那么cookie也要有时间限制,通过Expires/Max-Age去设置

Expires属性指定一个具体的到期时间,到了指定时间以后,浏览器就不再保留这个 Cookie。

Max-Age属性指定从现在开始 Cookie 存在的秒数,比如60 * 60 * 24 * 365(即一年)。过了这个时间以后,浏览器就不再保留这个 Cookie。
 

配置:HttpOnly/Secure

身份证是好身份证,你可不能随意涂改DIY吧,这可真没有这么一个说法,也就是说对于这个的使用方式,您得讲究一下

那么cookie也要讲究一下使用方式了。

Secure属性指定浏览器只有在加密协议 HTTPS 下,才能将这个 Cookie 发送到服务器。

HttpOnly属性指定该 Cookie 无法通过 JavaScript 脚本拿到,主要是Document.cookie属性、XMLHttpRequest对象和 Request API 都拿不到该属性。这样就防止了该 Cookie 被脚本读到,只有浏览器发出 HTTP 请求时,才会带上该 Cookie。

Http的头对cookie的读写

回过头来,HTTP 是如何写入和传递 cookie 及其配置的呢?

HTTP 返回的一个 Set-Cookie 头用于向浏览器写入「一条(且只能是一条)」cookie,格式为 cookie 键值 + 配置键值。例如:

Set-Cookie: username=QZW; domain=QZW.com; path=/blog; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly

前端对cookie的读写

前端可以自己创建 cookie,如果服务端创建的 cookie 没加HttpOnly,那恭喜你也可以修改他给的 cookie。

调用document.cookie可以创建、修改 cookie,和 HTTP 一样,一次document.cookie能且只能操作一个 cookie。

cookie是维持http状态的基石

了解了 cookie 后,我们知道 cookie 是最便捷的维持 HTTP 请求状态的方式,大多数前端鉴权问题都是靠 cookie 解决的。当然也可以选用别的存储方式。

那有了存储工具,接下来怎么做呢?

服务端:session

典型的 session 登陆/验证流程:

 

  • 浏览器登录发送账号密码,服务端查用户库,校验用户
  • 服务端把用户登录状态存为 Session,生成一个 sessionId
  • 通过登录接口返回,把 sessionId set 到 cookie 上
  • 此后浏览器再请求业务接口,sessionId 随 cookie 带上
  • 服务端查 sessionId 校验 session
  • 成功后正常做业务处理,返回结果

session的存储方式

显然,服务端只是给 cookie 一个 sessionId,而 session 的具体内容(可能包含用户信息、session 状态等),要自己存一下。存储的方式有几种:

  • Redis:内存型数据库,以 key-value 的形式存,正合 sessionId-sessionData 的场景;且访问快。
  • 内存:直接放到变量里。一旦服务重启就没了
  • 数据库:普通数据库。性能不高。

session的分布式问题

通常服务端是集群,而用户请求过来会走一次负载均衡,不一定打到哪台机器上。那一旦用户后续接口请求到的机器和他登录请求的机器不一致,或者登录请求的机器宕机了,session 不就失效了吗?

这个问题现在有几种解决方式。

  • 一是从「存储」角度,把 session 集中存储。如果我们用独立的 Redis 或普通数据库,就可以把 session 都存到一个库里。
  • 二是从「分布」角度,让相同 IP 的请求在负载均衡时都打到同一台机器上。以 nginx 为例,可以配置 ip_hash 来实现。

但通常还是采用第一种方式,因为第二种相当于阉割了负载均衡,且仍没有解决「用户请求的机器宕机」的问题。

node.js下对session的处理

npm中有已经封装好的中间件可以直接使用(例如 express-session-npm)。主要实现了这几个功能

  • 封装了对cookie的读写操作,并提供配置项配置字段、加密方式、过期时间等。
  • 封装了对session的存取操作,并提供配置项配置session存储方式(内存/redis)、存储规则等。
  • 给req提供了session属性,控制属性的set/get并响应到cookie和session存取上,并给req.session提供了一些方法。

应用方案:token

session的维护会给服务端造成不小的压力,我们必须找地方存放它,又要考虑分布式的问题,甚至要单独为了它启用一套 Redis 集群。有没有更好的办法呢?

回过头来想想,一个登录场景,也不必往 session 存太多东西,那为什么不直接打包到 cookie 中呢?这样服务端不用存了,每次只要核验 cookie 带的「证件」有效性就可以了,也可以携带一些轻量的信息。

这种方式通常叫做:token

token 的流程是这样的:

  • 用户登录,服务端校验账号密码,获得用户信息
  • 把用户信息、token 配置编码成 token,通过 cookie set 到浏览器
  • 此后用户请求业务接口,通过 cookie 携带 token
  • 接口校验 token 有效性,进行正常业务接口处理

在前面 cookie 说过,cookie 并不是客户端存储凭证的唯一方式。token 因为它的「无状态性」,有效期、使用限制都包在 token 内容里,对 cookie 的管理能力依赖较小,客户端存起来就显得更自由。但 web 应用的主流方式仍是放在 cookie 里,毕竟少操心。

那我们如何控制 token 的有效期呢?很简单,把「过期时间」和数据一起塞进去,验证时判断就好。

 JWT(Json Web Token)

JSON Web Token (JWT) 是一个开放标准,定义了一种传递 JSON 信息的方式。这些信息通过数字签名确保可信。

它是一种成熟的 token 字符串生成方案,包含了我们前面提到的数据、签名。不如直接看一下一个 JWT token 长什么样:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyaWQiOiJhIiwiaWF0IjoxNTUxOTUxOTk4fQ.2jf3kl_uKWRkwjOP6uQRJFqMlwSABcgqqcJofFH5XCo

这串东西看起来好乱,那它是怎么生成的呢?

refresh token

token,作为权限守护者,最重要的就是「安全」。

业务接口用来鉴权的 token,我们称之为 access token。越是权限敏感的业务,我们越希望 access token 有效期足够短,以避免被盗用。但过短的有效期会造成 access token 经常过期,过期后怎么办呢?

一种办法是,让用户重新登录获取新 token,显然不够友好,要知道有的 access token 过期时间可能只有几分钟。

另外一种办法是,再来一个 token,一个专门生成 access token 的 token,我们称为 refresh token。

  • access token 用来访问业务接口,由于有效期足够短,盗用风险小,也可以使请求方式更宽松灵活
  • refresh token 用来获取 access token,有效期可以长一些,通过独立服务和严格的请求方式增加安全性;由于不常验证,也可以如前面的 session 一样处理

有了 refresh token 后,几种情况的请求流程变成这样:

session 和 token

session 和 token 都是边界很模糊的概念,就像前面说的,refresh token 也可能以 session 的形式组织维护。

狭义上,我们通常认为 session 是「种在 cookie 上、数据存在服务端」的认证方案,token 是「客户端存哪都行、数据存在 token 里」的认证方案。对 session 和 token 的对比本质上是「客户端存 cookie / 存别地儿」、「服务端存数据 / 不存数据」的对比。

客户端存 cookie / 存别地儿

存 cookie 固然方便不操心,但问题也很明显:

  • 在浏览器端,可以用 cookie(实际上 token 就常用 cookie),但出了浏览器端,没有 cookie 怎么办?
  • cookie 是浏览器在域下自动携带的,这就容易引发 CSRF 攻击

存别的地方,可以解决没有 cookie 的场景;通过参数等方式手动带,可以避免 CSRF 攻击。

服务端存数据 / 不存数据

  • 存数据:请求只需携带 id,可以大幅缩短认证字符串长度,减小请求体积
  • 不存数据:不需要服务端整套的解决方案和分布式处理,降低硬件成本;避免查库带来的验证延迟

单点登录

ps:最后一部分啦!全篇历经快三天的这一篇终于要结束了(昨天发烧了。哎~)

前面我们已经知道了,在同域下的客户端/服务端认证系统中,通过客户端携带凭证,维持一段时间内的登录状态。

但当我们业务线越来越多,就会有更多业务系统分散到不同域名下,就需要「一次登录,全线通用」的能力,叫做「单点登录」。

“虚假”的单点登录(一级域名相同)

简单的,如果业务系统都在同一一级域名下,比如wenku.baidu.com tieba.baidu.com,就好办了。可以直接把 cookie domain 设置为一级域名 baidu.com,百度也就是这么干的。

“真实”的单点登录(一级域名不同)

比如滴滴这么潮的公司,同时拥有didichuxing.com     xiaojukeji.com     didiglobal.com等域名,种 cookie 是完全绕不开的。

这要能实现「一次登录,全线通用」,才是真正的单点登录。

这种场景下,我们需要独立的认证服务,通常被称为 SSO。

一次「从 A 系统引发登录,到 B 系统不用登录」的完整流程

  • 用户进入 A 系统,没有登录凭证(ticket),A 系统给他跳到 SSO
  • SSO 没登录过,也就没有 sso 系统下没有凭证(注意这个和前面 A ticket 是两回事),输入账号密码登录
  • SSO 账号密码验证成功,通过接口返回做两件事:一是种下 sso 系统下凭证(记录用户在 SSO 登录状态);二是下发一个 ticket
  • 客户端拿到 ticket,保存起来,带着请求系统 A 接口
  • 系统 A 校验 ticket,成功后正常处理业务请求
  • 此时用户第一次进入系统 B,没有登录凭证(ticket),B 系统给他跳到 SSO
  • SSO 登录过,系统下有凭证,不用再次登录,只需要下发 ticket
  • 客户端拿到 ticket,保存起来,带着请求系统 B 接口

完整版本:考虑浏览器的场景

上面的过程看起来没问题,但在有些时候还是会出现问题的

比如说

 

对浏览器来说,SSO 域下返回的数据要怎么存,才能在访问 A 的时候带上?浏览器对跨域有严格限制,cookie、localStorage 等方式都是有域限制的。

这就需要也只能由 A 提供 A 域下存储凭证的能力。一般我们是这么做的:

 

  • 在 SSO 域下,SSO 不是通过接口把 ticket 直接返回,而是通过一个带 code 的 URL 重定向到系统 A 的接口上,这个接口通常在 A 向 SSO 注册时约定
  • 浏览器被重定向到 A 域下,带着 code 访问了 A 的 callback 接口,callback 接口通过 code 换取 ticket
  • 这个 code 不同于 ticket,code 是一次性的,暴露在 URL 中,只为了传一下换 ticket,换完就失效
  • callback 接口拿到 ticket 后,在自己的域下 set cookie 成功
  • 在后续请求中,只需要把 cookie 中的 ticket 解析出来,去 SSO 验证就好
  • 访问 B 系统也是一样

总结一下叭

时间总是过得那么快,又到了说再见的时候,但愿下次见到你,我还有勇气说出我爱你

  • HTTP 是无状态的,为了维持前后请求,需要前端存储标记
  • cookie 是一种完善的标记方式,通过 HTTP 头或 js 操作,有对应的安全策略,是大多数状态管理方案的基石
  • session 是一种状态管理方案,前端通过 cookie 存储 id,后端存储数据,但后端要处理分布式问题
  • token 是另一种状态管理方案,相比于 session 不需要后端存储,数据全部存在前端,解放后端,释放灵活性
  • token 的编码技术,通常基于 base64,或增加加密算法防篡改,jwt 是一种成熟的编码方案
  • 在复杂系统中,token 可通过 service token、refresh token 的分权,同时满足安全性和用户体验
  • session 和 token 的对比就是「用不用cookie」和「后端存不存」的对比
  • 单点登录要求不同域下的系统「一次登录,全线通用」,通常由独立的 SSO 系统记录登录状态、下发 ticket,各业务系统配合存储和认证 ticket
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

鉴权大全(cookie、session、token、jwt、单点登录),深入理解和搞懂鉴权 的相关文章

随机推荐

  • 11款常用的安全测试工具

    1 AppScan 一款安全漏洞扫描工具 支持Web和移动 现在安全测试做漏洞扫描非常适用 它相当于是 探索 和 测试 的过程 最终生成很直观的测试报告 有助于研发人员分析和修复通常安全测试工具用这个 扫描一些安全漏洞 用起来比较方便 网上
  • php request instance,TP5学习笔记 请求(request)变量

    可以通过Request对象完成全局输入变量的检测 获取和安全过滤 支持包括 GET POST REQUEST SERVER SESSION COOKIE ENV等系统变量 以及文件上传信息 检测变量是否设置 可以使用has方法来检测一个变量
  • linux 挂载磁盘 普通用户读写 --chatGPT

    问 mount 挂载共享磁盘 指定用户fly可以读写 gpt 要将共享磁盘挂载并授予用户 fly 读写权限 您可以使用 mount 命令结合合适的挂载选项 首先 您需要确保磁盘已经连接到系统上 然后 按照以下步骤进行操作 1 创建一个目标目
  • CPU 与 GPU 之间数据转换 cudaMemcpy

    显存上分配空间 CUDA SAFE CALL cudaMalloc void Dst d sizeof float3 totalPNum 显存上传输数据 CUDA SAFE CALL cudaMemcpy Dst d Srcdata0 h
  • HAProxy--理论--02--配置文件

    HAProxy 理论 02 配置文件 1 配置文件组成 1 global 设置全局配置参数 2 defaults 设置的默认参数 3 frontend 接收请求的前端虚拟节点 Frontend可以直接指定具体使用后端的backend 4 b
  • anaconda 激活环境 并 安装相关包 torch 安装matplotlib

    1 管理员身份 运行 anaconda prompt 2 激活环境 查看python版本 activate 环境名称 3 查看已经安装的包 4 安装所需的包 方法1 pip install matplotlib 如果太慢建议更换国内镜像源
  • vue中给dom元素绑定js原生onclick事件并获取data中定义的数据和调用函数

    onclick 执行的是 window 环境中的方法 所以 将 this 中的方法或data中定义的字段关联到 window 上即可 vue代码 mounted window customMethods this customMethods
  • MariaDB+SpringBoot 报错 Caused by: java.sql.SQLException: GSS-API authentication exception

    最近在搭建springboot mybatis mariadb 项目的时候遇到了如题的问题 搜索了一圈没找到合适的解决方法 因此记录下我的解决方案 希望对后来者有帮助 先说结论 重装MariaDB即可 1 环境和配置信息 开发环境 win1
  • 排序基础算法(一)—— 堆排序

    堆排序 前言 1 完全二叉树 2 大根堆和小根堆 3 堆排序原理 一 调整堆 二 建初堆 三 堆排序算法的实现 Java代码 前言 堆排序是一种树形选择排序 在排序过程中 将待排序的记录r 1 n 看成是一棵完全二叉树的顺序存储结构 利用完
  • ffmpeg基础(五) I帧B帧P帧

    一 I帧 关键帧 属于帧内压缩 你可以理解为这一画面的完整保留 解码的时候只需要本帧的数据就可以完成 因为它包含的是完整信息 特点 1 它是一个全帧压缩编码帧 将全帧图像进行JPEG压缩以及传输 2 解码的时候只需要通过I帧的数据就可以得到
  • C++primer Plus 第三章复习题

    1 为什么C 有多种整型 有多种整型 可以根据输出结果选择最合适的类型 比如年龄可以使用short 存储容量用long 2 声明与下述描述相符的变量 a short整型 值为80 short num 80 b unsigned int 整型
  • python时间相减_python 计算时间差,时间加减运算代码

    1 方便的计算两个时间的差 如两个时间相差几天 几小时 import datetime d1 datetime datetime 2009 3 23 d2 datetime datetime 2009 10 7 dayCount d1 d2
  • MsSQL数据备份与恢复---完全备份与增量备份

    文章目录 1 数据备份 1 1 数据备份的重要性 1 2 数据备份的分类 1 2 1 从物理与逻辑的角度分类 1 2 2 从数据库的备份策略角度分类 1 2 3 备份方式的比较 1 3 常见的备份方法 2 MySQL完全备份与恢复 2 1
  • 扩容 磁盘、pv、lv

    因虚似机上的磁盘空间不够了 试了下偏门的扩展方法 root hdss7 21 df h Filesystem Size Used Avail Use Mounted on devtmpfs 1 9G 0 1 9G 0 dev tmpfs 1
  • Datawhale&Git-Model:假设检验2-多元数值向量的检验

    作业 为研究东 中 西部各省市规模以上的企业发展状况 我们收集了各城市企业的主要经济指标 包括 总资产贡献率 资产负债率 流动资产周转次数 工业成本费用利润率 产品销售率 我们用变量 类别 定义了各类城市 其中1为东部城市 2为中部城市 3
  • openwrt单网口进不了登录界面

    图一 图二 图一是刷完固件后 图二是网线直连电脑的IP配置 进不了登录界面 也ping 不通 请教是什么原因
  • 使用命令恢复postgres数据库报错psql: FATAL: role "root" does not exist 解决方案

    因为是从其他地方备份出来的sql文件 如果直接用pgAdmin4因为格式问题报错input file appears to be a text format dump Please use psql 所以换用命令行执行 但是出现psql F
  • 如何使用vant库的loading组件

    第一步 main js文件引入下载的loading 全局引入ui组件 import Vant from vant import vant lib index css Vue use Vant 第二步 在vuex中定义全局变量loading
  • Java Utils工具类大全

    源码和jar见 https github com evil0ps utils Java Utils 封装了一些常用Java操作方法 便于重复开发利用 另外希望身为Java牛牛的你们一起测试和完善 欢迎入群263641914 一起封装和完成常
  • 鉴权大全(cookie、session、token、jwt、单点登录),深入理解和搞懂鉴权

    先从状态说起吧 众所周知http是无状态的协议 也就是说 HTTP 请求方和响应方间无法维护状态 都是一次性的 它不知道前后的请求都发生了什么 是不是可以单纯的理解为它是一个超级无敌健忘的协议 但有的场景下 我们需要维护状态 最典型的 一个