jwt 介绍

目录

  • 1,jwt 的出现
    • 问题
  • 2,jwt 介绍
  • 3,jwt 令牌的组成
    • 3.1,header
    • 3.2,payload
    • 3.3,signature
  • 4,验证
  • 5,总结

身份验证相关内容:
浏览器 cookie 的原理(详)
session 原理

1,jwt 的出现

在浏览器 cookie 的原理(详)这篇文章中介绍了身份验证的步骤:

在这里插入图片描述

简单来说,如果身份验证通过 session 完成,那【出入证】就是一个 cookie 信息,内容是 sessionId。但还有其他的问题需要解决:

问题

随着前后端分离的发展,一个产品的终端可能不止是浏览器,还有桌面应用、智能家居等设备。

在这里插入图片描述
通过上图中可以看到:这些设备都会和同一个服务器通信,一般都是 http 协议。

通常不同的产品线会有自己的服务器,产品内部数据一般和自己的服务器交互。但中心服务器仍有存在的必要,因为产品之间总会有数据需要共享。

这个中心服务器至少承担着认证和授权的功能,比如登录:各种设备发送消息到中心服务器,中心服务器响应一个【出入证】(令牌信息)。

问题来了:其他的设备还能使用 cookie 传递令牌信息吗?

虽然 cookie 简单来说就是一个消息头,但浏览器有完善的管理机制:比如自动保存和自动发送,还有相应的安全机制等。但其他设备上的 cookie 机制就需要手动处理了。

jwt 的出现就是为了解决这个问题。

2,jwt 介绍

全称为 json web token,目的:为不同的终端设备提供统一的、安全的令牌格式。

在这里插入图片描述
令牌信息在传输时就是一个字符串而已,而 jwt 是令牌格式。这个字符串可以简单理解为:对一些特殊信息做了编码和加密,来达到身份验证的目的。

所以对这个字符串来说,

  • 在客户端的存储位置没有限制,放到 cookielocalStorage、pc 文件、手机文件中都可以。
  • 传输方式也没有限制。一般来说,会使用消息头来传输它:

比如登录成功后,服务器可以给客户端响应一个 jwt令牌:

POST /api/login HTTP/1.1 200 OK
...
set-cookie: token=jwt令牌
authorization: bearer jwt令牌
...

{..., token:jwt令牌}

它可以出现在响应的任何位置,或是同时出现在多个位置。
以上面的响应为例,就是为了充分利用浏览器的 cookie 机制,同时为了照顾其他设备,所以也出现在了响应头 authorization 和响应体中。

虽然没有明确的要求应该如何附带到请求中,但通常都会如下的格式(OAuth2附带 token 的一种规范格式):

GET /api/resources HTTP/1.1
...
authorization: bearer jwt令牌
...

整体交互流程:

在这里插入图片描述

3,jwt 令牌的组成

为了保证令牌的安全性,jwt 令牌由3个部分组成:

  1. header,头部,记录令牌的类型和签名算法。
  2. payload,负载,记录主体信息,比如用户信息。
  3. signature,签名,按头部的签名算法对整个令牌签名,作用:保证令牌不被伪造和篡改。

完整格式为 header.payload.signature。举例:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJteW5hbWUiLCJpYXQiOiIxNzAzNzc2NjM3In0._UuiFQ-rF8DZdheGE79LA46nfACrn2IiFPckUay7lQI

3.1,header

格式为 json 对象:

{
  "alg":"HS256",
  "typ":"JWT"
}
  1. alg 签名算法,默认是 HMAC SHA256 写成 HS256(对称加密算法);也可以使用 RS256(非对称加密算法)。
  2. typ 令牌类型,固定为 JWT

接着将 json 对象使用 base64 url 编码。

base64 url 不是加密算法,而是一种编码格式,它是在 base64 编码的基础上对 =+/ 这3个字符做特殊处理(=被省略,+替换为 -/ 替换为 _),因为 jwt 可能也会在 url 中传输。
base64 是使用64个可打印字符来表示一个二进制数据。

nodejs 需要借助第三方库实现,比如 base64url:

const base64url = require("base64url");

const a = base64url.encode(
  JSON.stringify({
    alg: "HS256",
    typ: "JWT",
  })
);
console.log(a); // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

const b = base64url.decode("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9");
console.log(b);

3.2,payload

jwt 的主体信息,也是一个 json 对象,包含以下内容:

{
  "ss""发行者", // 可以是公司名字,也可以是服务名称
  "iat""发布时间",
  "exp""到期时间",
  "sub""主题", // 该 jwt 的作用
  "aud""受众", // 发放给哪个终端的,可以是终端类型,也可以是用户名称
  "nbf""在此之前不可用", // 一个时间点,在该时间点到达之前,这个令牌是不可用的
  "jti""JWT ID" // jwt的唯一编号,主要是为了防止重放攻击(在某些场景下,用户使用之前的令牌发送到服务器,被服务器正确的识别,从而导致不可预期的行为发生)
}

以上内容只是一个规范,都是可选的。设置了也需要之后验证 jwt 令牌时手动处理才能发挥作用。

而我们可以把需要的信息加进去,比如用户 id 等等。比如:

{
  "foo": "myname", // 自定义信息
  "iat": "1703776637" // 规范的信息
}

同样也需要使用 base64url 编码:

// base64url 编码
const base64url = require("base64url");
const a = base64url.encode(
  JSON.stringify({
    foo: "myname",
    iat: "1703776637",
  })
);
// eyJmb28iOiJteW5hbWUiLCJpYXQiOiIxNzAzNzc2NjM3In0

注意,浏览器提供的 window.btoa 函数只是 base64 编码,并不是base64 url 编码!不会对 =+/ 这3个字符做特殊处理。

window.btoa(JSON.stringify({
  "foo":"myname",
  "iat":"1703776637"
}))
// 'eyJmb28iOiJteW5hbWUiLCJpYXQiOiIxNzAzNzc2NjM3In0='

// 但都可被正常解码,下面2个结果相同
window.atob('eyJmb28iOiJteW5hbWUiLCJpYXQiOiIxNzAzNzc2NjM3In0=')
window.atob('eyJmb28iOiJteW5hbWUiLCJpYXQiOiIxNzAzNzc2NjM3In0')

header 和 payload 都算是明文传输的。所以不要将敏感信息放到 payload 中

3.3,signature

这部分保证了 jwt 不会被篡改或伪造。

生成步骤:将 header 和 payload 的编码结果,使用 header 中指定的加密算法进行加密。

// 上面 header 的编码结果 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
// 上面 payload 的编码结果 eyJmb28iOiJteW5hbWUiLCJpYXQiOiIxNzAzNzc2NjM3In0
const crypto = require("crypto");

function HS256(header, playload) {
  const hmac = crypto.createHmac("sha256", "mykey"); // 创建加密对象,且指定秘钥为 mykey
  hmac.update(`${header}.${playload}`); // 将数据放入加密对象
  return hmac.digest("base64url"); // 编码为 base64url 
}

HS256("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9", "eyJmb28iOiJteW5hbWUiLCJpYXQiOiIxNzAzNzc2NjM3In0")
// _UuiFQ-rF8DZdheGE79LA46nfACrn2IiFPckUay7lQI

最终将这3部分拼接在一起,得到完整的 jwt:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJteW5hbWUiLCJpYXQiOiIxNzAzNzc2NjM3In0._UuiFQ-rF8DZdheGE79LA46nfACrn2IiFPckUay7lQI

node-hmac.digest 参考

4,验证

因为签名使用的秘钥会保存在服务器,所以客户端无法伪造签名来篡改 jwt。

服务器拿到客户端回传的 jwt 之后,除了验证相同之外(比如payload信息被篡改),还需要验证是否过期,受众是否还满足要求等。

最终整体验证流程:

在这里插入图片描述

5,总结

  • jwt本质上是一种令牌格式。它和终端设备无关,同样和服务器无关,甚至与如何传输无关,它只是规范了令牌的格式而已。
  • jwt由三部分组成:header、payload、signature,主体信息在payload。
  • jwt难以被篡改和伪造。这是因为有第三部分的签名存在。所以在秘钥不泄露的前提下,一个验证通过的 jwt token 是值得被信任的。

以上。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/282481.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

微服务实战系列之Dubbo(下)

前言 眼看着2023即将走远,心里想着似乎还有啥,需要再跟各位盆友叨叨。这不说曹操,曹操就来了。趁着上一篇Dubbo博文的余温尚在,博主兴匆匆地“赶制”了Dubbo的下集,以飨读者。 上一篇博主依然从Dubbo的内核出发&#…

Linux基础知识学习3

vim编辑器 其分为四种模式 1.普通(命令)模式 2.编辑模式 3.底栏模式 4.可视化模式 vim编辑器被称为编辑器之神,而Emacs更是神之编辑器 普通模式: 1.光标移动 ^ 移动到行首 w 跳到下一个单词的开头…

软件开发新手用哪个IDE比较好?软件开发最好的IDE都在这!

目录 IDES 的优点 最佳编程 IDE 列表 Java 开发的流行集成开发环境 JetBrains 的 IntelliJ IDEA NetBeans 适用于 C/ C、C# 编程语言的最佳 IDE Visual Studio 和 Visual Studio 代码 Eclipse PHP 开发的最佳 IDE PHPStorm Sublime Text Atom JavaScript 的顶级 I…

Windows10系统的音频不可用,使用疑难解答后提示【 一个或多个音频服务未运行】

一、问题描述 打开电脑,发现电脑右下角的音频图标显示为X(即不可用,无法播放声音),使用音频自带的【声音问题疑难解答】(选中音频图标,点击鼠标右键,然后选择“声音问题疑难解答(T)”…

procise纯PL流程点灯记录

procise纯PL流程点灯记录 一、概述 此篇记录使用procise工具构造JFMQL15T 纯PL工程,显示PL_LED闪烁; 硬件说明如下: 时钟引脚 Pl_CLK: U2 ,IO_L14P_T2_SRCC_34 PL_LED1 : E2, IO_L17P_T2_AD5P_35 PL_LED2: D6, IO_L2N_T0_AD8N_35 PL_LED3 :…

网易有道词典不能截屏翻译,不能联网解决办法

对应版本: win10系统,联想拯救者笔记本,网易有道词典8.10.2.0。 网易有道词典免费下载链接:https://download.csdn.net/download/qq_42755734/88684985 修改代理: youdao.com 0 取消勾选---不更新 效果&#xff1a…

CentOS 7 lvm 裸盘的扩容和缩容减盘 —— 筑梦之路

背景介绍 之前写过比较多的关于lvm的文章: CentOS 7 lvm 更换坏盘操作步骤小记 —— 筑梦之路_centos更换硬盘操作-CSDN博客 xfs ext4 结合lvm 扩容、缩容 —— 筑梦之路_ext4扩盘-CSDN博客 LVM逻辑卷元数据丢失恢复案例 —— 筑梦之路_pve lvm数据恢复-CSDN博客…

【MMdetection】MMdetection从入门到进阶

基础环境安装 步骤 0. 从官方网站下载并安装 Miniconda。 步骤 1. 创建并激活一个 conda 环境。 conda create --name openmmlab python3.8 -y conda activate openmmlab步骤 2. 基于 PyTorch 官方说明安装 PyTorch。 pip install torch2.0.1 torchvision0.15.2 torchaudio…

SpringBoot+拦截器(Interceptor)

记录一下SpringBoot的拦截器(Interceptor)使用 拦截器(Interceptor)是AOP面向切面编程的思想来实现的,对于只写代码的来说,具体如何实现不需要多关心,只需要关心如何去使用,会用在那些地方。 当http请求进入Springboot应用程序后…

UE蓝图 RPG动作游戏(一) day15

角色状态制作 制作角色动画混合空间 创建一个动混合空间 添加动作在混合空间 动画蓝图 创建一个动画蓝图 先使用混合空间进行移动,后续优化后再使用状态机 编写垂直水平速度逻辑初始化,获取到此动画的角色组件 获取Horizontal与Vertical的速度逻辑 …

股票价格预测 | Python实现Autoformer, FEDformer和PatchTST等模型用于股价预测

文章目录 效果一览文章概述环境描述源码设计效果一览 文章概述 Autoformer、FEDformer和PatchTST是一些用于时间序列预测,包括股价预测的模型。它们都是在Transformer模型的基础上进行了改进和扩展,以更好地适应时间序列数据的特点。 Autoformer:Autoformer是一种自适应Tran…

软件测试/测试开发丨Python 面向对象编程思想

面向对象是什么 Python 是一门面向对象的语言面向对象编程(OOP):Object Oriented Programming 所谓的面向对象,就是在编程的时候尽可能的去模拟真实的现实世界,按照现实世界中的逻辑去处理问题,分析问题中…

继续声明 | 连声明都抄,谁抄袭谁,一目了然,现在竟然恬不知耻的反咬一口。

继续声明 | 连声明都抄,谁抄袭谁,一目了然,现在竟然恬不知耻的反咬一口。 一、本账号为《机器学习之心》博主CSDN唯一官方账号,唯一联系方式见文章底部。 二、《机器学习之心》博主未授权任何第三方账号进行模型合作、程序设计、…

【Java进阶篇】什么是UUID,能不能保证唯一?

什么是UUID,能不能保证唯一? ✔️典型解析✔️优缺点 ✔️各个版本实现✔️V1.基于时间戳的UUID✔️V2.DCE(Distributed Computing Environment)安全的UUID✔️V3.基于名称空间的UUID(MD5)✔️V4.基于随机数的UUID✔️V5.基于名称空间的UUID(SHA1)✔️各个版本总结…

我在Vscode学OpenCV 图像处理四(轮廓查找 cv2.findContours() cv2.drawContours())-- 待补充

图像处理四(轮廓查找) 一、前言1.1 边缘检测和轮廓查找的区别是什么1.1.1 边缘检测:1.1.2 轮廓查找: 1.2 边缘检测和轮廓查找在图像处理中的关系和流程 二、查找并绘制轮廓2.1 cv2.findContours():2.1.1 详细介绍&…

爬虫工作量由小到大的思维转变---<第三十章 Scrapy Redis 第一步(配置同步redis)>

前言: 要迈向scrapy-redis进行编写了;首要的一步是,如何让他们互通?也就是让多台电脑连一个任务(这后面会讲); 现在来做一个准备工作,配置好redis的同步!! 针对的是windows版本的redis同步,实现主服务和从服务共享一个redis库; 正文: 正常的redis for windows 的安装这里就…

C#,入门教程(03)——Visual Studio 2022编写彩色Hello World与动画效果

C#,入门教程(01)—— Visual Studio 2022 免费安装的详细图文与动画教程https://blog.csdn.net/beijinghorn/article/details/123350910 C#,入门教程(02)—— Visual Studio 2022开发环境搭建图文教程https://blog.csdn.net/beijinghorn/article/detail…

Hampel滤波器是一种基于中位数的离群值检测方法【异常值检测方法】

Hampel滤波器是一种基于中位数的离群值检测方法,也是一种线性滤波器,由德国数学家和统计学家John Hampel在1974年提出。它主要用于去除信号中的脉冲噪声,具有很强的抗干扰能力,因此被广泛应用于信号处理、通信系统等领域。 1.基本…

SpringBoot定时监听RocketMQ的NameServer

问题分析 自己在测试环境部署了RocketMQ,发现namesrv很容易挂掉,于是就想着监控,挂了就发邮件通知。查看了rocketmq-dashboard项目,发现只能监控Broker,遂放弃这一路径。于是就从报错的日志入手,发现最终可…

【Redis-08】Redis主从复制的实现原理

在Redis中,可以通过slaveof命令或者设置slaveof选项实现两台Redis服务器的主从复制,比如我们有两个Redis机器,地址分别是 127.0.0.1:6379 和 127.0.0.1:6380,现在我们在前者上面执行: 127.0.0.1:6379 > SLAVEOF 12…