什么是JWT?
起源
需要了解一门技术,首先从为什么产生开始说起是最好的。JWT 主要用于用户登录鉴权,所以我们从最传统的 session 认证开始说起。
session认证
众所周知,http 协议本身是无状态的协议,那就意味着当有用户向系统使用账户名称和密码进行用户认证之后,下一次请求还要再一次用户认证才行。因为我们不能通过 http 协议知道是哪个用户发出的请求,所以如果要知道是哪个用户发出的请求,那就需要在服务器保存一份用户信息(保存至 session ),然后在认证成功后返回 cookie 值传递给浏览器,那么用户在下一次请求时就可以带上 cookie 值,服务器就可以识别是哪个用户发送的请求,是否已认证,是否登录过期等等。这就是传统的 session 认证方式。
session 认证的缺点其实很明显,由于 session 是保存在服务器里,所以如果分布式部署应用的话,会出现session不能共享的问题,很难扩展。于是乎为了解决 session 共享的问题,又引入了 redis,接着往下看。
token认证
这种方式跟 session 的方式流程差不多,不同的地方在于保存的是一个 token 值到 redis,token 一般是一串随机的字符(比如UUID),value 一般是用户ID,并且设置一个过期时间。每次请求服务的时候带上 token 在请求头,后端接收到token 则根据 token 查一下 redis 是否存在,如果存在则表示用户已认证,如果 token 不存在则跳到登录界面让用户重新登录,登录成功后返回一个 token 值给客户端。
优点是多台服务器都是使用 redis 来存取 token,不存在不共享的问题,所以容易扩展
缺点是每次请求都需要查一下redis,会造成 redis 的压力,还有增加了请求的耗时,每个已登录的用户都要保存一个 token 在 redis,也会消耗 redis 的存储空间。
有没有更好的方式呢?接着往下看。
什么是JWT
JWT (全称:Json Web Token),它定义了一种紧凑的、自包含的方式,用于作为 JSON 对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。
上面说法比较文绉绉,简单点说就是一种认证机制,让后台知道该请求是来自于受信的客户端。
首先我们先看一个流程图:

流程描述一下:
- 用户使用账号、密码登录应用,登录的请求发送到 Authentication Server。
- Authentication Server 进行用户验证,然后创建 JWT 字符串返回给客户端。
- 客户端请求接口时,在请求头带上 JWT。
- Application Server 验证 JWT 合法性,如果合法则继续调用应用接口返回结果。
可以看出与token方式有一些不同的地方,就是不需要依赖 redis,用户信息存储在客户端。所以关键在于生成 JWT 和解析 JWT 这两个地方。
JWT的数据结构
JWT 一般是这样一个字符串,分为三个部分,以 “.” 隔开:
xxxxx.yyyyy.zzzzz
Header
JWT 第一部分是头部分,它是一个描述 JWT 元数据的 Json 对象,通常如下所示。
{"alg": "HS256","typ": "JWT"
}
alg 属性表示签名使用的算法,默认为 HMAC SHA256(写为HS256)
typ 属性表示令牌的类型,JWT 令牌统一写为JWT。
最后,使用 Base64 URL 算法将上述 JSON 对象转换为字符串保存。
Payload
JWT 第二部分是 Payload,也是一个 Json 对象,除了包含需要传递的数据,还有七个默认的字段供选择。
-
iss (issuer):签发人/发行人
-
sub (subject):主题
-
aud (audience):用户
-
exp (expiration time):过期时间
-
nbf (Not Before):生效时间,在此之前是无效的
-
iat (Issued At):签发时间
-
jti (JWT ID):用于标识该 JWT
如果自定义字段,可以这样定义:
{//默认字段"sub":"主题123",//自定义字段"name":"java技术爱好者","isAdmin":"true","loginTime":"2021-12-05 12:00:03"
}
需要注意的是,默认情况下 JWT 是未加密的,任何人都可以解读其内容,因此一些敏感信息不要存放于此,以防信息泄露。
JSON 对象也使用 Base64 URL 算法转换为字符串后保存,是可以反向反编码回原样的,这也是为什么不要在 JWT 中放敏感数据的原因。
Signature
header (base64URL 加密后的)
payload (base64URL 加密后的)
secret
JWT 第三部分是签名。是这样生成的,首先需要指定一个 secret,该 secret 仅仅保存在服务器中,保证不能让其他用户知道。这个部分需要 base64URL 加密后的 header 和 base64URL 加密后的 payload 使用 . 连接组成的字符串,然后通过header 中声明的加密算法 进行加盐secret组合加密,然后就得出一个签名哈希,也就是Signature,且无法反向解密。
那么 Application Server 如何进行验证呢?可以利用 JWT 前两段,用同一套哈希算法和同一个 secret 计算一个签名值,然后把计算出来的签名值和收到的 JWT 第三段比较,如果相同则认证通过。
JWT的优点
- json格式的通用性,所以JWT可以跨语言支持,比如Java、JavaScript、PHP、Node等等。
- 可以利用Payload存储一些非敏感的信息。
- 便于传输,JWT结构简单,字节占用小。
- 不需要在服务端保存会话信息,易于应用的扩展。
怎么使用JWT
首先引入Maven依赖。
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version>
</dependency>
创建工具类,用于创建(生成) jwt 字符串和解析 jwt。
@Component
public class JwtUtil {@Value("${jwt.secretKey}")private String secretKey;public String createJWT(String id, String subject, long ttlMillis, Map<String, Object> map) throws Exception {JwtBuilder builder = Jwts.builder().setId(id).setSubject(subject) // 发行者.setIssuedAt(new Date()) // 发行时间.signWith(SignatureAlgorithm.HS256, secretKey) // 签名类型 与 密钥.compressWith(CompressionCodecs.DEFLATE);// 对载荷进行压缩if (!CollectionUtils.isEmpty(map)) {builder.setClaims(map);}if (ttlMillis > 0) {builder.setExpiration(new Date(System.currentTimeMillis() + ttlMillis));}return builder.compact();}public Claims parseJWT(String jwtString) {return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwtString).getBody();}
}
接着在application.yml配置文件配置jwt.secretKey
## 用户生成jwt字符串的secretKey
jwt:secretKey: ak47
接着创建一个响应体。
public class BaseResponse {private String code;private String msg;public static BaseResponse success() {return new BaseResponse("0", "成功");}public static BaseResponse fail() {return new BaseResponse("1", "失败");}//构造器、getter、setter方法
}public class JwtResponse extends BaseResponse {private String jwtData;public static JwtResponse success(String jwtData) {BaseResponse success = BaseResponse.success();return new JwtResponse(success.getCode(), success.getMsg(), jwtData);}public static JwtResponse fail(String jwtData) {BaseResponse fail = BaseResponse.fail();return new JwtResponse(fail.getCode(), fail.getMsg(), jwtData);}//构造器、getter、setter方法
}
接着创建一个UserController:
@RestController
@RequestMapping("/user")
public class UserController {@Resourceprivate UserService userService;@RequestMapping(value = "/login", method = RequestMethod.POST)public JwtResponse login(@RequestParam(name = "userName") String userName,@RequestParam(name = "passWord") String passWord){String jwt = "";try {jwt = userService.login(userName, passWord);return JwtResponse.success(jwt);} catch (Exception e) {e.printStackTrace();return JwtResponse.fail(jwt);}}
}
还有UserService:
@Service
public class UserServiceImpl implements UserService {@Resourceprivate JwtUtil jwtUtil;@Resourceprivate UserMapper userMapper;@Overridepublic String login(String userName, String passWord) throws Exception {//登录验证User user = userMapper.findByUserNameAndPassword(userName, passWord);if (user == null) {return null;}//如果能查出,则表示账号密码正确,生成jwt返回String uuid = UUID.randomUUID().toString().replace("-", "");HashMap<String, Object> map = new HashMap<>();map.put("name", user.getName());map.put("age", user.getAge());return jwtUtil.createJWT(uuid, "login subject", 0L, map);}
}
还有UserMapper.xml:
@Mapper
public interface UserMapper {User findByUserNameAndPassword(@Param("userName") String userName, @Param("passWord") String passWord);}<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="io.github.yehongzhi.jwtdemo.mapper.UserMapper"><select id="findByUserNameAndPassword" resultType="io.github.yehongzhi.jwtdemo.model.User">select * from user where user_name = #{userName} and pass_word = #{passWord}</select>
</mapper>
user 表结构如下:

启动项目,然后用 postman 请求 login 接口。 
返回的 jwt 字符串如下:
eyJhbGciOiJIUzI1NiIsInppcCI6IkRFRiJ9.eNqqVspLzE1VslJ6OnHFsxnzX67coKSjlJgOFDEzqAUAAAD__w.qib2DrjRKcFnY77Cuh_b1zSzXfISOpCA-g8PlAZCWoU
接着我们写一个接口接收这个 jwt,并做验证。
@RestController
@RequestMapping("/jwt")
public class TestController {@Resourceprivate JwtUtil jwtUtil;@RequestMapping("/test")public Map<String, Object> test(@RequestParam("jwt") String jwt) {//这个步骤可以使用自定义注解+AOP编程做解析jwt的逻辑,这里为了简便就直接写在controller里Claims claims = jwtUtil.parseJWT(jwt);String name = claims.get("name", String.class);String age = claims.get("age", String.class);HashMap<String, Object> map = new HashMap<>();map.put("name", name);map.put("age", age);map.put("code", "0");map.put("msg", "请求成功");return map;}
}

像这样能正常解析成功的话,就表示该用户登录未过期,并且已认证成功,所以可以正常调用服务。那么有人会问了,这个 jwt 字符串能不能被伪造呢?
除非你知道 secretKey,否则是不能伪造的。比如客户端随便猜一个 secretKey 的值,然后伪造一个jwt:
eyJhbGciOiJIUzI1NiIsInppcCI6IkRFRiJ9.eNqqVspLzE1VslJ6OnHFsxnzX67coKSjlJgOFDEzqAUAAAD__w.bHr9p3-t2qR4R50vifRVyaYYImm2viZqiTlDdZHmF5Y
然后传进去解析,会报以下错误:

还记得原理吧,是根据前面两部分(Header、Payload)加上 secretKey 使用 Header 指定的哈希算法计算出第三部分(Signature),所以可以看出最关键就是 secretKey。secretKey只有服务端自己知道,所以客户端不知道 secretKey 的值是伪造不了jwt字符串的。
总结
最后讲讲 JWT 的缺点,因为任何技术都不是完美的,所以我们得用辩证思维去看待任何一项技术。
- 安全性没法保证,所以 jwt 里不能存储敏感数据。因为 jwt 的 payload 并没有加密,只是用 Base64 编码而已。
- 无法中途废弃。因为一旦签发了一个 jwt,在到期之前始终都是有效的,如果用户信息发生更新了,只能等旧的 jwt 过期后重新签发新的 jwt。
- 续签问题。当签发的 jwt 保存在客户端,客户端一直在操作页面,按道理应该一直为客户端续长有效时间,否则当 jwt有效期到了就会导致用户需要重新登录。那么怎么为 jwt 续签呢?最简单粗暴就是每次签发新的 jwt,但是由于过于暴力,会影响性能。如果要优雅一点,又要引入 Redis 解决,但是这又把无状态的 jw t硬生生变成了有状态的,违背了初衷。
的,所以我们得用辩证思维去看待任何一项技术。
- 安全性没法保证,所以 jwt 里不能存储敏感数据。因为 jwt 的 payload 并没有加密,只是用 Base64 编码而已。
- 无法中途废弃。因为一旦签发了一个 jwt,在到期之前始终都是有效的,如果用户信息发生更新了,只能等旧的 jwt 过期后重新签发新的 jwt。
- 续签问题。当签发的 jwt 保存在客户端,客户端一直在操作页面,按道理应该一直为客户端续长有效时间,否则当 jwt有效期到了就会导致用户需要重新登录。那么怎么为 jwt 续签呢?最简单粗暴就是每次签发新的 jwt,但是由于过于暴力,会影响性能。如果要优雅一点,又要引入 Redis 解决,但是这又把无状态的 jw t硬生生变成了有状态的,违背了初衷。
所以印证了那句话,没有最好的技术,只有适合的技术。
转自:java技术爱好者
链接:https://www.zhihu.com/question/485758060/answer/2257869896
来源:知乎
相关文章:
什么是JWT?
起源 需要了解一门技术,首先从为什么产生开始说起是最好的。JWT 主要用于用户登录鉴权,所以我们从最传统的 session 认证开始说起。 session认证 众所周知,http 协议本身是无状态的协议,那就意味着当有用户向系统使用账户名称和…...
STM32—0.96寸OLED液晶显示
本文主要介绍基于STM32F103的0.96寸的OLED液晶显示,详细关于0.96寸OLED液晶屏幕的介绍可参考这篇博客:https://blog.csdn.net/u011816009/article/details/130119426 一、简介 OLED被称为有机激光二极管,也被称为有机激光显示,O…...
Mysql的简介和选择
文章目录 前言一、为什么要使用数据库 数据库的概念为什么要使用数据库二、程序员为什么要学习数据库三、数据库的选择 主流数据库简介使用MySQL的优势版本选择四、Windows 平台下安装与配置MySQL 启动MySQL 服务控制台登录MySQL命令五、Linux 平台下安装与配置MySQL总结 前言…...
3D视觉之深度相机方案
随着机器视觉,自动驾驶等颠覆性的技术逐步发展,采用 3D 相机进行物体识别,行为识别,场景 建模的相关应用越来越多,可以说 3D 相机就是终端和机器人的眼睛。 3D 相机 3D 相机又称之为深度相机,顾名思义&…...
Mysql列的完整性约束详解(主键约束)
文章目录 前言一、设置表字段的主键约束(PRIMARY KEY,PK) 1.单字段主键2.多字段主键总结 前言 完整性约束条件是对字段进行限制,要求用户对该属性进行的操作符合特定的要求。如果不满足完整性约束条件,数据库系统将不再…...
母婴市场竞争激烈,如何通过软文营销脱颖而出
如今,随着宝宝数量增加以及人们对孩子的重视程度的增加,母婴市场愈发火爆。然而,母婴行业的竞争也越来越激烈,企业需要不断开拓新市场才能生存。在这样的情况下,软文营销成为了母婴企业拓展市场的一种有效方式。 首先&…...
java--线程池
目录 1.线程池概 2 为什么要使用线程池 1创建线程问题 2解决上面两个问题思路: 3线程池的好处 4线程池适合应用场景 3 线程池的构造函数参数 1.corePoolSize int 线程池核心线程大小 2.maximumPoolSize int 线程池最大线程数量 3.keepAliveTime long 空闲…...
asp.net765数码手机配件租赁系统
员工部分功能 1.员工登录,员工通过自己的账号和密码登录到系统中来,对租赁信息进行管理 2.配件查询,员工可以查询系统内的配件信息 3.客户信息管理,员工可以管理和店内有业务往来的客户信息 4.配件租赁,员工可以操作用…...
有关态势感知(SA)的卷积思考
卷积是一种数学运算,其本质是将两个函数进行操作,其中一个函数是被称为卷积核或滤波器的小型矩阵,它在另一个函数上滑动并产生新的输出。在计算机视觉中,卷积通常用于图像处理和特征提取,它可以通过滤波器对输入图像进…...
Docker快速部署springboot项目
有很多开发者在项目部署过程中都会遇到一些繁琐的问题,比如打包、上传、部署等。而使用Docker可以非常方便地解决这些问题。在本文中,将详细讲解如何使用IDEA中的docker打包插件,将代码打包并直接发布到服务器上。这样,我们就可以…...
Linux命令rsync增量同步目录下的文件
业务场景描述 最近遇到一个问题,需要编写相应的Linux命令,增量同步/var/mysql里的所有文件到另外一个目录/opt/mysql,但是里面相关的日志文件xx.log是不同步的,这个场景,可以使用rsync来实现 什么是rsync命令&#x…...
项目管理---(1)项目管理一般知识
一、项目管理一般知识 1.1 项目的一般知识 1.1.1 项目的定义: 项目是为创造独特的产品、服务或成果而进行的临时性工作。 1.1.2 项目的目标: 项目的目标包括成果性目标和约束性目标。 成果性目标:指通过项目开发出满足客户要求的产品、系…...
超过50多个热门的免费可用 API 分享
今天吃什么:随机返回一顿美味食物,解决你今天吃什么的难题。万年历:获取公历日期对应的农历、农历节日节气、天干地支纪年纪月纪日、生肖属相、宜忌、星座等信息。支持查询未来15天。笑话大全:各种最新、最及时的幽默、搞笑段子&a…...
记一次死锁问题
最近在做一个需求,碰到了死锁的问题,记录下解决问题的过程 背景 这个需求要改动一个接口,我这边称为A接口,原先的逻辑是A接口内部会调用c方法,c方法是一个dubbo方法, 现在需要再A接口里添加调用B方法&…...
Bean 作⽤域和⽣命周期
目录 1.lombok 1.1 1.添加依赖:(pom.xml) 1.2 在实体类上使用lombok提供的注解 1.3 安装插件 2. Bean 的 6 种作⽤域(Scope) 2.1 singleton(默认模式) 2.2 prototype(原型模式…...
SVN通过备份、过滤、再导入的方式彻底删除废弃目录
文章目录 前言简要步骤操作示例总结 前言 SVN占用的空间随着项目版本迭代越来越大,因为保存了历史记录中的各个版本,所以即使本地把废弃的目录删掉提交,也不会释放出多余的空间,大概率因为操作删除增加了一个版本号,使…...
golang支持优雅关闭和core错误记录
#经过测试,不能使用 ENTRYPOINT ["/modapi/modapi", "1>> /dev/null","2>> ./logs/stderr.log"],原因是虽然这种方案可以 #保证modapi命令为1号程序,能够接收到os的signal信号。但是如果程序core了…...
Basics of Container Isolation 容器隔离的实现原理
目录 容器隔离的实现原理 1. 使用cgroups实现资源隔离 自定义一个cgroup 设置进程的内存使用 启动一个docker 容器,观察cgroup的创建情况 2. 使用Namespaces进行资源分区 namespace继承关系引发的问题 3. 结合来使用Namespaces 和chroot 4. 结论 参考文档…...
EBS R12.1 注册客户化应用的步骤
创建客户化应用目录 登录成 applxxx 用户 -- applxxx 改成所需用户名 # 以标准INV模块作为客户化应用目录的模板 cd $APPL_TOP mkdir -p cust cp -r inv cust/template cd cust # 删除template 目录下的文件,只保留目录结构 cd $APPL_TOP/cust for rm_list in …...
算法记录 | Day38 动态规划
对于动态规划问题,将拆解为如下五步曲 确定dp数组(dp table)以及下标的含义确定递推公式dp数组如何初始化确定遍历顺序举例推导dp数组 509.斐波那契数 思路: 确定dp数组(dp table)以及下标的含义&#x…...
GPT-SoVITS语音克隆镜像评测:5秒样本实现高质量声音复刻
GPT-SoVITS语音克隆镜像评测:5秒样本实现高质量声音复刻 1. 引言:声音克隆技术的新突破 在虚拟助手、有声读物和数字人应用爆发的今天,语音克隆技术正变得越来越重要。传统语音合成系统往往需要数小时的录音样本才能训练出可用的声音模型&a…...
WarcraftHelper:让经典《魔兽争霸III》适配现代设备的开源解决方案
WarcraftHelper:让经典《魔兽争霸III》适配现代设备的开源解决方案 【免费下载链接】WarcraftHelper Warcraft III Helper , support 1.20e, 1.24e, 1.26a, 1.27a, 1.27b 项目地址: https://gitcode.com/gh_mirrors/wa/WarcraftHelper 当你在高清显示器上启动…...
别再硬用Search API了!Qdrant纯Payload查询的正确姿势:Scroll API实战与性能调优
别再硬用Search API了!Qdrant纯Payload查询的正确姿势:Scroll API实战与性能调优 最近在重构一个电商后台系统时,我发现团队里不少工程师都在用Qdrant的Search API做纯Payload字段查询——比如按订单状态筛选数据、根据商品标签过滤结果集。这…...
2025届最火的六大AI科研平台推荐
Ai论文网站排名(开题报告、文献综述、降aigc率、降重综合对比) TOP1. 千笔AI TOP2. aipasspaper TOP3. 清北论文 TOP4. 豆包 TOP5. kimi TOP6. deepseek 在学术写作这个领域当中 ,那论文AI网站正一步一步地变成研究者的重要辅助工具。这…...
Windows下MySQL服务报错1067别急着重装!一个my.ini参数拯救你的数据库
Windows下MySQL服务报错1067的深度修复指南 当你在Windows服务器上突然遭遇MySQL服务罢工,事件查看器里赫然显示着"错误1067:进程意外终止"的红色警告,那种焦虑感足以让任何运维人员心跳加速。但别急着掏出重装系统的终极武器——本…...
Qwen3-ASR-0.6B真实效果:直播间弹幕语音→实时字幕滚动+敏感词过滤联动
Qwen3-ASR-0.6B真实效果:直播间弹幕语音→实时字幕滚动敏感词过滤联动 1. 开篇:直播间语音转文字的痛点 做直播的朋友都知道,实时字幕是个让人又爱又恨的功能。爱的是它能提升观众体验,让不方便开声音的人也能看懂内容ÿ…...
利用快马平台快速生成基于jdk17的spring boot应用原型
最近在尝试用JDK17搭建一个Spring Boot项目原型时,发现从环境配置到基础代码编写要花不少时间。正好试用了InsCode(快马)平台,发现它能快速生成可运行的项目骨架,特别适合需要快速验证想法的场景。这里记录下具体操作和体验: 项目…...
Honey Select 2本地化增强补丁:技术实现与模块化架构深度解析
Honey Select 2本地化增强补丁:技术实现与模块化架构深度解析 【免费下载链接】HS2-HF_Patch Automatically translate, uncensor and update HoneySelect2! 项目地址: https://gitcode.com/gh_mirrors/hs/HS2-HF_Patch HS2-HF Patch作为Honey Select 2游戏社…...
Hunyuan-OCR-WEBUI效果实测:复杂表格识别与字段抽取案例展示
Hunyuan-OCR-WEBUI效果实测:复杂表格识别与字段抽取案例展示 1. 引言 在数字化转型浪潮中,纸质文档的电子化处理一直是企业办公自动化的关键环节。传统OCR技术虽然能够完成基本的文字识别,但在面对复杂表格、多语言混合、低质量扫描件等实际…...
[特殊字符]OpenClaw 优化系列(三):基于WSL的OpenClaw备份恢复与文件交互
告别环境崩塌恐惧症,一文掌握WSL下OpenClaw的数据安全与高效操作 Windows下WSL环境准备已经在前面讲了:🦞 OpenClaw 部署环境准备:Windows下WSL安装及配置全攻略。至于OpenClaw部署,教程已经很多很多了,只…...
