一、JWT 是什么?解决什么问题?

我们先来一张图看一下这个过程:
在这里插入图片描述

JWT(JSON Web Token)是一种把“认证信息(Claims)+ 完整性校验”打包成 自包含 的字符串的规范。
它主要用于
无状态认证
:服务端验证签名即可信任其中的身份与权限,无需每次查库或维护会话(session)。

  • 无状态:后端不存会话;减少分布式共享状态的复杂度。
  • 可扩展:把自定义字段写入 claims(如角色、租户、权限)。
  • 可委托:不同服务/网关只要有验证密钥就能核验并信任。

但请记住:JWT 只保证 完整性(没被篡改),默认不保密(除非用 JWE 加密)。敏感信息不要塞进未加密的 JWT。


二、JWT 的结构与签名流程

JWT 的字符串形如:<header>.<payload>.<signature>

  1. Header(JSON,Base64URL)

    • alg: 签名算法(如 HS256 / RS256 / ES256 / EdDSA
    • typ: 通常为 "JWT"
    • kid(可选):密钥标识,用于密钥轮换
  2. Payload/Claims(JSON,Base64URL)
    常见注册声明:

    • iss(颁发者)、sub(主体,通常是用户ID)、aud(受众)
    • exp(过期时间,秒级时间戳,必须!)、nbf(不早于)、iat(签发时间)
    • jti(唯一ID,用于一次性/黑名单)
      以及你的自定义字段rolestenantIdscope 等。
  3. Signature

    • 计算方式:signature = Sign( base64url(header) + "." + base64url(payload), key, alg )
    • 验证时:使用共享密钥(HMAC)或公钥(RSA/ECDSA/EdDSA)验证。

JWS vs JWE

  • JWS(最常用):签名但不加密;任何人拿到 token 都能看到 payload。
  • JWE:加密(可选),适用于含敏感信息的场景。

三、JWT 使用流程(最小闭环)

  1. 登录:用户名密码校验成功 → 颁发短期 Access Token(JWT)+ 较长期 Refresh Token(不可见给前端或放 HttpOnly Cookie)。
  2. 访问 API:前端将 Authorization: Bearer <jwt> 送给后端。
  3. 后端:验证签名、校验 exp/nbf/aud/iss 等 → 放行。
  4. 刷新:Access Token 过期,用 Refresh Token 换新(做轮换失效控制)。
  5. 登出/撤销(可选):把 jti 或 refresh 的标识加入黑名单,或进行密钥轮换

四、常见安全陷阱(一定要看)

  • ❌ 不设置 exp(永不过期,风险极大)。
  • alg: none(严格禁用)。
  • 密钥混淆:把对称密钥误当作公钥发布;或同一 kid 指向错密钥。
  • HS256 在多服务扩散:一旦泄露,所有服务都可伪造。跨服务建议 RS256/ES256/EdDSA(私钥签、公钥验)。
  • ❌ 不校验 aud/iss:导致“错配 token”被误信任。
  • ❌ 客户端 localStorage 存储 → 易受 XSS 影响。推荐 HttpOnly + Secure + SameSite Cookie
  • ❌ 忽视 CSRF:若用 Cookie 携带 Access Token,要配合 SameSite + CSRF 令牌 或改为 Bearer 头。
  • ❌ 不做 Refresh Token 轮换 与黑名单 → 被盗后长期可用。
  • ❌ 把敏感信息(如身份证、银行卡、密码)塞进未加密 JWT。

五、Java 手写(JJWT)创建与验证

1) 依赖(Maven)

<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId><version>0.11.5</version>
</dependency>
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>0.11.5</version><scope>runtime</scope>
</dependency>
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId><version>0.11.5</version> <!-- for JSON serialization --><scope>runtime</scope>
</dependency>

若用 RSA/EC/EdDSA:还需对应的 jjwt-xxx 或者用 java.security 生成密钥。

2) 生成密钥(示例:RSA 与 Ed25519)

// RSA 2048(推荐生产至少 2048)
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
KeyPair rsaKeyPair = kpg.generateKeyPair();// Ed25519(更轻更快)
KeyPairGenerator ed = KeyPairGenerator.getInstance("Ed25519");
KeyPair edKeyPair = ed.generateKeyPair();

3) 颁发 JWT(RS256 或 EdDSA)

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import java.time.Instant;
import java.util.Date;
import java.util.Map;// 使用 RSA 私钥签名(RS256)
String token = Jwts.builder().setHeaderParam("kid", "key-2025-08")     // 便于轮换.setIssuer("https://auth.example.com").setSubject("user-123").setAudience("api.example.com").setIssuedAt(new Date()).setExpiration(Date.from(Instant.now().plusSeconds(900))) // 15 分钟.addClaims(Map.of("roles", new String[]{"ADMIN","USER"},"tenantId", "t-1001")).signWith(rsaKeyPair.getPrivate())        // 默认按密钥类型选择 RS256/ES256/EdDSA.compact();

signWith(PrivateKey):JJWT 会自动选合适 alg;如想强制算法,可用新版签名 API 指定 SignatureAlgorithm.

4) 验证 JWT(公钥验签 + 校验声明)

import io.jsonwebtoken.*;Jws<Claims> jws = Jwts.parserBuilder().requireIssuer("https://auth.example.com").requireAudience("api.example.com").setAllowedClockSkewSeconds(60) // 允许 60s 时钟偏差.setSigningKey(rsaKeyPair.getPublic())    // 或使用 JWKS 拉取的公钥.build().parseClaimsJws(token);Claims claims = jws.getBody();
String userId = claims.getSubject();
String[] roles = claims.get("roles", String[].class);

校验失败会抛异常(如 ExpiredJwtExceptionSignatureException)。
在网关/过滤器中统一捕获 → 返回 401/403。


六、Spring Boot(Resource Server)零胶水校验

最省心的是让 Spring Security 资源服务器替你做解析与校验,它支持 JWK 集合(JWKS) 自动远程拉取公钥。

1) 依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>

2) application.yml(通过 JWKS URL 校验)

server:port: 8080spring:security:oauth2:resourceserver:jwt:jwk-set-uri: https://auth.example.com/.well-known/jwks.jsonissuer-uri: https://auth.example.com   # 建议同时配置,做 iss 校验

你的授权服务器(自建或第三方,如 Auth0/Keycloak/Spring Authorization Server)对外暴露 JWKS。资源服自动缓存和轮询kid 取公钥

3) 安全配置

@Bean
SecurityFilterChain security(HttpSecurity http) throws Exception {http.csrf(csrf -> csrf.disable()) // 如果前端走 Bearer 头,可关;若走 Cookie,需要保留并配置 CSRF.authorizeHttpRequests(reg -> reg.requestMatchers("/public/**").permitAll().requestMatchers("/admin/**").hasRole("ADMIN").anyRequest().authenticated()).oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthConverter())));return http.build();
}// 可选:把自定义 claims 映射为权限
Converter<Jwt, ? extends AbstractAuthenticationToken> jwtAuthConverter() {return jwt -> {Collection<GrantedAuthority> authorities = new ArrayList<>();List<String> roles = jwt.getClaimAsStringList("roles");if (roles != null) {roles.forEach(r -> authorities.add(new SimpleGrantedAuthority("ROLE_" + r)));}return new JwtAuthenticationToken(jwt, authorities, jwt.getSubject());};
}

4) 控制器示例

@RestController
public class DemoController {@GetMapping("/me")public Map<String, Object> me(@AuthenticationPrincipal Jwt jwt) {return Map.of("sub", jwt.getSubject(),"roles", jwt.getClaimAsStringList("roles"),"tenantId", jwt.getClaim("tenantId"));}@GetMapping("/admin/hello")public String admin() { return "hello, admin"; }
}

七、发行端:用 Spring Authorization Server 签发 JWT(概念位)

如果你既要颁发又要验证

  • 引入 spring-authorization-server,配置客户端、用户认证、签名密钥(支持 RSA/ECDSA/EdDSA),开启 /.well-known/jwks.json
  • 认证成功后,框架自动颁发 Access Token(JWT)Refresh Token
  • 资源服务器只需 issuer-uri/jwk-set-uri 即可对接。

好处:密钥管理、轮换、标准化授权流程(OAuth2/OIDC) 都交给框架;你专注业务。


八、Cookie vs Header、CSRF 与前端存储

  • 推荐Authorization: Bearer <jwt> 置于请求头,前端保存在内存(刷新丢失)或安全容器;刷新策略依赖 HttpOnly Refresh Cookie。

  • 若必须把 Access Token 放 Cookie

    • 设置:HttpOnly + Secure + SameSite=Lax/Strict
    • 开启并正确处理 CSRF 防护(基于 Cookie 的双重提交策略或框架自带 CSRF Token)。
  • 不要放 localStorage(XSS 风险大)。


九、刷新与撤销(实战策略)

  • 短期 Access Token(5–15 分钟) + 长期 Refresh Token(7–30 天)

  • Refresh Token 轮换:每次刷新都颁发新 refresh,并使旧的失效(存库并维护 revoked 标记或版本号)。

  • 黑名单/撤销

    • 记录 Access Token 的 jti(可选)用于紧急撤销;
    • 更常用的是缩短 Access Token寿命 + 轮换 Refresh;
    • 密钥轮换:更换私钥(新 kid),强制旧 token 逐步失效(需兼容一段时间,等旧 token 过期)。

十、微服务与网关

  • 首选:网关或每个服务自行校验 JWT(拿到 JWKS 公钥本地验);不要把解析结果当作“可信 JSON”直接传递。
  • aud/iss:为不同受众(微服务)使用不同 aud,防止“错用 token”。
  • 性能:缓存 JWKS、公钥对象;JWT 验证成本很低,通常不是瓶颈。

十一、完整示例:无授权服务器时的“轻量颁发 + 校验”

1) 颁发端(登录成功后)

// 假设你用 Spring Security 自己做用户名/密码认证
@PostMapping("/auth/login")
public Map<String, String> login(@RequestBody LoginReq req) {// 1. 校验用户名密码(略)// 2. 颁发 TokenInstant now = Instant.now();String access = Jwts.builder().setHeaderParam("kid", "key-2025-08").setIssuer("https://auth.example.com").setSubject("user-" + req.username()).setAudience("api.example.com").setIssuedAt(Date.from(now)).setExpiration(Date.from(now.plusSeconds(900))).claim("roles", List.of("USER")).signWith(rsaPrivateKey) // 你的私钥.compact();String refreshId = UUID.randomUUID().toString(); // 存入数据库,标记有效String refresh = Jwts.builder().setIssuer("https://auth.example.com").setSubject("user-" + req.username()).setId(refreshId).setIssuedAt(Date.from(now)).setExpiration(Date.from(now.plusSeconds(30 * 24 * 3600))) // 30 天.signWith(rsaPrivateKey).compact();// refresh 建议放 HttpOnly Cookie 返回return Map.of("access_token", access, "token_type", "Bearer");
}

2) 刷新端点

@PostMapping("/auth/refresh")
public Map<String, String> refresh(@CookieValue("refresh_token") String refreshToken) {// 1. 验证 refreshToken 签名与过期Jws<Claims> jws = Jwts.parserBuilder().setSigningKey(rsaPublicKey).build().parseClaimsJws(refreshToken);String jti = jws.getBody().getId();// 2. 校验 jti 是否未吊销,且未被使用(轮换)// 3. 颁发新 access(并轮换 refresh:生成新 refresh,旧的置为 revoked)String newAccess = ...;// Set-Cookie: refresh_token=<new>; HttpOnly; Secure; SameSite=Strictreturn Map.of("access_token", newAccess, "token_type", "Bearer");
}

3) 资源服务(校验端,若不用 Resource Server Starter)

自定义过滤器(不建议重复造轮子,演示用):

@Component
public class JwtAuthFilter extends OncePerRequestFilter {private final PublicKey publicKey;public JwtAuthFilter(PublicKey publicKey) { this.publicKey = publicKey; }@Overrideprotected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)throws ServletException, IOException {String auth = req.getHeader("Authorization");if (auth != null && auth.startsWith("Bearer ")) {String token = auth.substring(7);try {Jws<Claims> jws = Jwts.parserBuilder().requireIssuer("https://auth.example.com").requireAudience("api.example.com").setAllowedClockSkewSeconds(60).setSigningKey(publicKey).build().parseClaimsJws(token);Claims c = jws.getBody();List<GrantedAuthority> auths = new ArrayList<>();List<String> roles = c.get("roles", List.class);if (roles != null) roles.forEach(r -> auths.add(new SimpleGrantedAuthority("ROLE_" + r)));UsernamePasswordAuthenticationToken authentication =new UsernamePasswordAuthenticationToken(c.getSubject(), null, auths);SecurityContextHolder.getContext().setAuthentication(authentication);} catch (JwtException e) {res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);return;}}chain.doFilter(req, res);}
}

十二、测试要点清单(上线前自查)

  • exp/nbf/iat/iss/aud 均有严格校验;允许小量 clock skew
  • ✅ 禁止 alg: none,不允许客户端指定算法。
  • ✅ 使用 非对称算法(RS/ES/EdDSA) 做跨服务验证;对称密钥仅限单体/网关内部。
  • ✅ 开启并演练 密钥轮换kid + JWKS),旧公钥保留到所有 token 过期。
  • ✅ 访问控制基于 最小权限(角色/权限来源可在 claims 或 DB)。
  • ✅ 选择合适的 存储与传输方式(Bearer 头 或 HttpOnly Cookie + CSRF 防护)。
  • 短期 Access + 轮换 Refresh;可选黑名单(jti)应急撤销。
  • ✅ 日志中绝不打印完整 token(最多打前后各 6 位用于排错)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/95014.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/95014.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/95014.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

大数据在UI前端的应用深化研究:用户行为数据的跨平台关联分析

大数据在UI前端的应用深化研究&#xff1a;用户行为数据的跨平台关联分析每天&#xff0c;你在手机 App 里点了一个按钮、在网页上滑了两屏、又在小程序里停留了 3 秒&#xff0c;这些看似零散的动作&#xff0c;其实都在被悄悄记录。过去&#xff0c;这些数据只能各自躺在自己…

C++11基础——— 右值引用和移动语义

1. C11的发展历史 C11是C的第⼆个主要版本&#xff0c;并且是从C98起的最重要更新。它引入了大量更改&#xff0c;标准化了既有实践&#xff0c;并改进了对C程序员可用的抽象。在它最终由ISO在2011年8月12日采纳前&#xff0c;人们曾使用名称“C0x”&#xff0c;因为它曾被期待…

【一】Django框架版本介绍

【一】Django框架版本介绍 【一】Django框架版本 ● Django 是一个高级的Python Web框架&#xff0c;由荷兰人Armin Ronacher创建。 ● 随着版本的迭代和功能的不断优化&#xff0c;Django在处理异步请求方面也有了显著的进步。 【1】Django1.x ● 默认不支持异步 ● Django 1.…

git 大文件上传不了的 问题

你 还是在 cmd 里执行&#xff0c;Select-String 是 PowerShell 的命令&#xff0c;cmd 不认识。 请务必按下面的步骤 切换到 PowerShell 再运行。✅ 1. 打开 PowerShell&#xff08;不要再用 cmd&#xff09;最简单&#xff1a; 在资源管理器里进入 D:\linShiWenjian\my-react…

【FIX】go运行报错“missing go.sum entry for module providing package”解决方案

&#x1f527; ​核心解决方案​**运行 go mod tidy**​ ​作用​&#xff1a;自动同步 go.mod和 go.sum文件&#xff0c;添加缺失依赖并移除无用条目。 go mod tidy​适用场景​&#xff1a;90% 的校验和缺失问题可通过此命令解决。 ​注意​&#xff1a;若项目含私有仓库&…

【实操教学】ArcGIS 如何进行定义坐标系

一、坐标系定义的方式创建数据时可直接完成坐标系定义&#xff1b;针对已创建的数据集&#xff08;涵盖要素类、要素数据集及栅格数据集&#xff09;&#xff0c;则可通过以下这种方式定义&#xff1a;工具箱工具调用&#xff1a;使用 ArcGIS 工具箱中的 “定义投影&#xff08…

如何使用Windows自带的PnPUtil命令来禁用/停用和启用硬件设备

我来详细讲解一下如何使用 Windows 自带的 PnPUtil 命令来禁用&#xff08;停用&#xff09; 和启用硬件设备。 PnPUtil (即插即用实用工具) 是一个功能强大的命令行工具&#xff0c;主要用于安装、卸载、枚举和修改驱动程序包。对于硬件的启用和禁用&#xff0c;它通过操作设…

鸿蒙Next媒体展示组件实战:Video与动态布局全解析

今天我们来深入探讨HarmonyOS Next中几种核心媒体展示组件的使用方法&#xff0c;通过实际代码示例展示如何打造丰富的多媒体体验。HarmonyOS Next为开发者提供了一套强大而灵活的媒体展示组件&#xff0c;使开发者能够轻松实现视频播放、动态布局适应、全屏切换等常见多媒体功…

复现RoboDK机器人校准功能(以Staubli TX2‑90L / TX200机械臂为测试对象,实测精度接近原厂)

本算法复现了 RoboDK 的机器人校准功能&#xff1a;在训练集的理论校准后精度与 RoboDK 一致&#xff0c;在测试集的实测精度接近 Staubli 原厂。 参考&#xff1a;RoboDK 机器人校准功能&#xff08;https://robodk.com.cn/cn/robot-calibration&#xff09; 特性 支持 SDH 参…

Vue常用指令和生命周期

Vue 是基于 MVVM模型的前端 JavaScript 框架。Vue 核心是数据驱动视图&#xff0c;通过响应式数据实现视图自动更新。<template><div>{{ message }}</div><button click"changeMsg">修改内容</button> </template><script se…

深度学习周报(8.25~8.31)

目录 摘要 Abstract 1 RNN学习意义 2 RNN基础知识 2.1 核心思想 2.2 传播 2.3 优缺点 2.4 变体结构与应用场景 3 RNN结构代码示例 4 总结 摘要 本周主要学习了循环神经网络的学习意义与基础知识&#xff0c;重点了解了RNN循环连接的核心思想、前向传播与反向传播过程…

借助 LAMBDA 公式,实现单元格区域高效转换

新特性介绍 “转换单元格&#xff08;Transform&#xff09;” 功能允许用户将自定义的单参数 LAMBDA 公式应用于选中的单元格区域。用户可选择公式参数的作用域 —— 按单元格、按行、按列或按整个区域。 转换完成后&#xff0c;源单元格区域会被清空&#xff0c;转换后的区…

LeetCode 01背包 494. 目标和

494. 目标和给你一个非负整数数组 nums 和一个整数 target 。 向数组中的每个整数前添加 ‘’ 或 ‘-’ &#xff0c;然后串联起所有整数&#xff0c;可以构造一个 表达式 &#xff1a; 例如&#xff0c;nums [2, 1] &#xff0c;可以在 2 之前添加 ‘’ &#xff0c;在 1 之前…

Dify 1.8.0 全网首发,预告发布

距离Dify 1.7.2过去两周了 Dify 1.8.0 又跟大伙见面了&#xff01; 1.8.0&#xff0c;属于主版本号不变、但第二位数字更新的“阶段性大更”&#xff0c;意味着功能上的显著优化和体验上的重要升级。 根据官方的Github日志&#xff0c;这一版本将继续聚焦三大核心方向&#x…

基于LangChain框架搭建AI问答系统(附源码)

AI问答系统1. 背景知识2. 问答系统流程3. 知识问答系统相关组件3.1 文档加载器3.2 文档切割器3.3 嵌入模型包装器3.4 向量存储库3.5 模型包装器3.6 链组件4. 问答系统演示4.1 问答程序4.2 演示大模型回答效果5.问答系统代码1. 背景知识 在人工智能技术飞速发展的今天&#xff…

【Python】QT(PySide2、PyQt5):Qt Designer,VS Code使用designer,可能的报错

Qt designer&#xff1a;可直接在designer界面&#xff0c;使用拖拽的方式设计需要的界面&#xff0c;可设定部分属性。安装Pyside2后&#xff0c;designer默认在python安装目录的Lib/sit_packages/PySide2文件夹中。designer使用&#xff1a;① 双击打开designer.exe&#xff…

前端常见安全问题 + 防御方法 + 面试回答

目录 XSS&#xff08;跨站脚本攻击&#xff09;CSRF&#xff08;跨站请求伪造&#xff09;SQL 注入文件上传漏洞其他前端常见安全问题面试常见问答 1. XSS&#xff08;跨站脚本攻击&#xff09; 定义 XSS&#xff08;Cross-Site Scripting&#xff09;是一种 通过注入恶意脚…

jxWebUI--下拉选择框

下拉选择框提供了预先定义好的选项&#xff0c;用户只能在这些选项中选择输入。 combobox 定义格式 combobox 控件名 属性列表 ;属性 bind 类型&#xff1a;string 缺省值&#xff1a; 输入控件所绑定的变量名。当给输入控件bind了一个变量名后【bindbind_var_name】&#xff0…

大模型时代:用Redis构建百亿级向量数据库方

大模型时代&#xff1a;用Redis构建百亿级向量数据库方案第一章&#xff1a;大模型时代的向量数据库挑战1.1 大模型时代的特征与需求1.2 向量数据库的核心价值1.3 百亿级向量的技术挑战第二章&#xff1a;Redis作为向量数据库的优势2.1 Redis的核心优势2.2 Redis向量搜索模块&a…

jsqlparser(六):TablesNamesFinder 深度解析与 SQL 格式化实现

在数据库应用开发中&#xff0c;SQL语句的解析和处理是一项常见而重要的任务。本文将深入探讨 JSQLParser 中的 TablesNamesFinder 类&#xff0c;分析其核心原理、与 AST 访问接口&#xff08;CCJSqlParserVisitor &#xff09;的关系、使用场景&#xff0c;并通过实际代码示例…