当前位置: 首页 > news >正文

JWT详解:从登录认证到令牌验证

一、引入

回想一下传统的Web应用是怎么做登录认证的:

用户输入用户名密码登录成功,服务器在session里记下“用户已登录”,然后把sessionId通过cookie返回给浏览器。浏览器下次请求时带上这个cookie,服务器就能识别出用户身份。

这套方案在单机时代没问题,但到了分布式时代就尴尬了:

  • 用户登录了A服务器,下次请求被负载均衡到B服务器,B服务器不认识这个session

  • 解决方法是session共享,要么存数据库,要么用Redis,但多了一次网络开销

  • 如果做移动端App,cookie用起来更别扭

那有没有一种办法,让服务器不保存登录状态,用户自己把身份信息带过来,服务器验证一下就行?这就是JWT(JSON Web Token)的核心理念。

二、JWT是什么?

JWT全称是JSON Web Token,它本质上是一个字符串,由三部分组成,用点(.)连接:

这个字符串就是用户的“身份证”。用户登录成功后,服务器生成这个令牌返回给客户端。客户端以后每次请求都带着它,服务器只要验证令牌合法,就知道用户是谁。

整个过程中,服务器不需要保存任何session数据,所以特别适合分布式系统和微服务架构。

三、JWT的三段结构

把JWT拆开看,每一段都有自己的作用:

3.1 Header(头部)

Header通常由两部分组成:

  • typ:令牌类型,固定是"JWT"

  • alg:签名算法,比如HS256、RS256

json

{ "alg": "HS256", "typ": "JWT" }

这个JSON对象经过Base64Url编码,就成了JWT的第一段。

打个比方,Header就像是身份证上的“中华人民共和国”这几个字,告诉别人这是个什么证件。

3.2 Payload(载荷)

Payload里放的是实际需要传递的数据,比如用户ID、用户名、过期时间等。

json

{ "sub": "1234567890", "name": "张三", "exp": 1516239022, "iss": "auth-server" }

这里有几个预定义的字段(但都不是强制的):

  • sub:主题(通常是用户ID)

  • exp:过期时间

  • iat:签发时间

  • iss:签发者

  • aud:接收方

你还可以加任意自定义字段,比如roleavatar等。

这个JSON同样经过Base64Url编码,成了JWT的第二段。

注意:Payload只是Base64编码,不是加密!里面的内容任何人只要解码就能看到。所以千万不要放密码、手机号等敏感信息

3.3 Signature(签名)

第三段是签名,它的作用是防止数据被篡改

签名的生成过程:

  1. 把编码后的Header和Payload用点连接:header.payload

  2. 用Header里声明的算法(比如HS256),加上一个只有服务器知道的密钥,对上面的字符串进行加密

  3. 得到的结果就是签名

HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret )

服务器收到JWT后,会重新计算一遍签名。如果计算出来的签名和JWT自带的签名一致,说明数据没有被篡改;如果不一致,说明这个令牌被改过了,直接拒绝。

四、JWT的工作流程

用一个具体的例子走一遍流程:

4.1 用户登录

@PostMapping("/login") public Result login(@RequestBody LoginRequest request) { // 1. 验证用户名密码 User user = userService.login(request.getUsername(), request.getPassword()); // 2. 生成JWT String jwt = JwtUtil.createToken(user.getId(), user.getUsername()); // 3. 返回给客户端 return Result.success(jwt); }

4.2 服务器生成JWT

服务器生成JWT的步骤:

public class JwtUtil { // 密钥(应该配置在配置文件里,不要写死在代码里) private static final String SECRET = "mySecretKey"; // 过期时间(1小时) private static final long EXPIRE_TIME = 60 * 60 * 1000; public static String createToken(Long userId, String username) { // 1. 设置过期时间 Date expireDate = new Date(System.currentTimeMillis() + EXPIRE_TIME); // 2. 创建JWT Builder JwtBuilder builder = Jwts.builder() // 设置Header(算法) .setHeaderParam("alg", "HS256") .setHeaderParam("typ", "JWT") // 设置Payload .setSubject(String.valueOf(userId)) .claim("username", username) .setIssuedAt(new Date()) .setExpiration(expireDate) // 签名 .signWith(SignatureAlgorithm.HS256, SECRET); // 3. 生成token return builder.compact(); } }

生成的JWT大概是这个样子:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwidXNlcm5hbWUiOiLlvKDkuIkiLCJleHAiOjE2MTUyMzkwMjIsImlhdCI6MTYxNTIzNTQyMn0.Lw1qQ_RqFbPJ4H5X7Y8Z9a0b1c2d3e4f5g6h7i8j9k

4.3 客户端存储JWT

客户端收到JWT后,需要保存起来。不同端有不同的存法:

  • Web端:存在localStorage或sessionStorage

  • App端:存在SharedPreferences或Keychain

  • 小程序:存在storage

以后每次请求,客户端都要在HTTP头里带上JWT:

axios.defaults.headers.common['Authorization'] = 'Bearer ' + token;

4.4 服务器验证JWT

服务器收到请求后,从Header里取出JWT,验证合法性:

@Component public class JwtInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 1. 从Header获取token String token = request.getHeader("Authorization"); if (token == null || !token.startsWith("Bearer ")) { throw new RuntimeException("未登录"); } token = token.substring(7); // 去掉"Bearer " // 2. 验证token try { Claims claims = JwtUtil.parseToken(token); // 把用户信息存入请求,后续方法可以直接取用 request.setAttribute("userId", claims.getSubject()); request.setAttribute("username", claims.get("username")); return true; } catch (Exception e) { throw new RuntimeException("token无效或已过期"); } } }

验证的具体实现:

public static Claims parseToken(String token) { return Jwts.parser() .setSigningKey(SECRET) // 用相同的密钥验证签名 .parseClaimsJws(token) .getBody(); }

验证过程做了三件事:

  1. 签名验证:用密钥重新计算签名,对比是否一致

  2. 过期验证:检查exp字段是否晚于当前时间

  3. 完整性验证:解码出来的数据是否完整

如果验证通过,说明这个JWT确实是我们签发的,而且没有被篡改过。

4.5 后续处理

Controller里可以直接取用JWT里的用户信息:

@GetMapping("/user/info") public Result getUserInfo(HttpServletRequest request) { // 从拦截器设置进去的属性里拿 Long userId = (Long) request.getAttribute("userId"); String username = (String) request.getAttribute("username"); // 或者直接注入 return Result.success(userService.findById(userId)); }

五、JWT的核心优势

对比传统session方案,JWT的优势很明显:

维度Session方案JWT方案
存储位置服务器内存/Redis客户端自己保存
扩展性需要session共享,麻烦天生支持分布式
跨域需要配置CORS直接支持
移动端cookie用起来别扭Header统一
性能每次都要查session只需计算签名

六、JWT的注意事项

6.1 安全性

JWT的Payload是明文的(只是Base64编码)。打开浏览器的开发者工具就能看到:

// 在控制台输入 atob("eyJzdWIiOiIxMjM0NTY3ODkwIiwidXNlcm5hbWUiOiLlvKDkuIkiLCJleHAiOjE2MTUyMzkwMjJ9")

所以绝对不能放密码、身份证号、手机号等敏感信息。如果有需要,可以再对Payload加密,但这会增加复杂度。

6.2 长度

JWT比sessionId长得多。一个普通的JWT可能有两三百个字符,如果放在URL里可能太长(浏览器URL长度有限制),所以一般放Header里。

6.3 登出

这是JWT最大的痛点:因为服务器不保存状态,所以无法主动让一个token失效。你点退出登录,服务器只能把客户端的token删掉,但如果别人拿着之前的token来访问,服务器依然认为是有效的。

解决方案:

  • 设置较短的过期时间,配合refresh token机制

  • 维护一个黑名单(但又回到共享状态的问题了)

6.4 续签

token过期了怎么办?不能让用户每过一小时就重新登录一次。

常见做法是refresh token机制:同时返回两个token

  • access token:有效期短(比如1小时),用于正常访问

  • refresh token:有效期长(比如7天),专门用来换取新的access token

客户端发现access token过期了,就拿refresh token去换新的,用户体验不受影响。

七、总结

JWT用一句话总结就是:把用户信息加密成一段字符串,交给客户端保存,服务器通过验证签名来确认身份

它的核心三要素:

  • Header:声明类型和算法

  • Payload:存放实际数据(不能放敏感信息)

  • Signature:防篡改的签名

整个流程:

  1. 用户登录成功,服务器生成JWT返回

  2. 客户端保存JWT,每次请求都带上

  3. 服务器验证JWT,通过就放行,不通过就拒绝

相比传统session方案,JWT更适合分布式系统、移动端App、前后端分离的项目。虽然它也有一些局限(如无法主动登出、长度较大),但只要合理设计过期时间,配合refresh token机制,足以应对大多数场景。


以上是我对JWT的学习理解,如果有不准确的地方,欢迎指正。

http://www.jsqmd.com/news/465960/

相关文章:

  • 大厂集体“捞虾”:腾讯派出了它的先遣队
  • STM32开发板
  • 机器学习做材料性能的回归预测氧离子电导率模型需要按材料成分分组划分训练测试集吗?
  • 2026探寻常德市淘发源生物科技,从信息看其口碑和专业性 - 工业设备
  • Parse error 语法错误:10种常见原因 + 修复方法
  • Python基于flask+uniapp微信小程序的校园学生宿舍报修管理系统
  • 面试别再只说“我会写用例”:测试黑话升级,薪资翻倍秘籍
  • 2026年3月新疆门窗维修服务团队综合评测与选购指南 - 2026年企业推荐榜
  • [特殊字符] 从零搭建网页项目:从创建到预览全流程
  • ByteBuddy系列文章目录
  • ASP.NET Core面试精讲系列八
  • 访问后台路径(admin.php)时,提示“403 Forbidden”,无法进入后台,前台可正常访问原因分析
  • Python基于flask+uniapp微信小程序的校园学生社团签到系统 可视化
  • Python开发英语记忆单词软件
  • Python基于flask+uniapp微信小程序的校园智能垃圾分类回收预约平台
  • 氯代烷烃源头厂家盘点:2026年谁主性价比之巅? - 2026年企业推荐榜
  • 工业智能网关+物联网云平台的一体化解决方案
  • 2026年毕业季降AI工具怎么选?亲测比话降AI效果实录
  • Python基于Django的汉语文本阅读难度分级系统(附源码,文档说明)
  • Ppt设计资源合集
  • Spring Framework 中文官方文档
  • 《AI原生应用自然语言理解:突破局限,创造无限可能》
  • Python基于flask+uniapp微信小程序的校园服务闲置物品交易失物招领系统平台
  • 研发工程师晋升背后的逻辑:一些背后的思考与行动指南
  • areal异步技术分析
  • 2026年DeepSeek写的论文AI率98%怎么办?实测从98%降到8%的全过程
  • 2026上海雅思培训机构推荐:科学选择备考机构指南 - 品牌排行榜
  • 2026年降AI工具性价比排行:花最少的钱降到最低
  • 排序查找,简单模板
  • 基于vue的校园兼职系统的设计与实现Python django flask