SpringBoot--JWT
一、JWT 的简单了解
1. 什么是 JWT?
JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在 各方之间安全地传输信息。它基于 JSON 格式,信息通过 数字签名 的方式保证不可篡改,常用于 身份认证和信息交换。
2. JWT 的结构
JWT 由三部分组成,每部分用
.
分隔:Header.Payload.Signature
(1) Header(头部)
描述 类型 和 签名算法。例如:
{"alg": "HS256","typ": "JWT" }
Base64Url 编码后得到第一部分。
(2) Payload(载荷)
存放 声明(Claims) 的地方。
声明分三类:
注册声明(Registered Claims):建议但非必须使用的标准字段
iss
:签发者 (issuer)
sub
:主题 (subject)
aud
:接收者 (audience)
exp
:过期时间 (expiration time)
nbf
:生效时间 (not before)
iat
:签发时间 (issued at)
jti
:JWT ID,用于防止重放攻击公共声明(Public Claims):大家约定好的字段
私有声明(Private Claims):系统内部定义的字段,比如
userId
、role
。例如:
{"sub": "user123","name": "Alice","role": "admin","exp": 1692345600 }
Base64Url 编码后得到第二部分。
(3) Signature(签名)
用于防篡改。
计算方法:HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload),secret )
即:把前两部分拼接,用密钥和算法加密生成签名。
3. JWT 的认证流程
用户登录:客户端提交用户名、密码。
服务端验证:验证通过后,生成 JWT(带用户信息、过期时间等),返回给客户端。
客户端存储:通常存储在 LocalStorage 或 Cookie 中。
后续请求:客户端请求时在 Header 中带上
Authorization: Bearer <token>
。服务端验证:服务端用密钥验证 JWT 是否有效、是否过期、是否被篡改。
验证通过:允许访问资源。
4. JWT 的优点
无状态:服务端不存储 Session,支持分布式扩展。
跨语言:JSON 格式,兼容性强。
高性能:减少数据库查询,认证速度快。
信息完整:载荷可携带用户信息,减少额外查询。
5. JWT 的缺点
不可撤销:一旦签发,在过期前无法主动让其失效(除非维护黑名单)。
体积较大:比 Session ID 更大(因为要携带签名和用户信息)。
安全风险:如果私钥泄露,所有 token 都会失效。
5.1 为什么说不可撤销呢?在前端删除jwt(前端销毁)不就好了?
用户自己点击 “退出登录”,前端删除 token,下次再访问接口时就没有 token 了 → 确实相当于退出了。
在大多数 正常用户行为 下,这种方式足够。
5.1.1 但是会有特殊情况:
(1)多端登录 / 被动下线🔹如果一个账号在两台设备登录,管理员希望强制其中一台下线。🔹单靠前端删 token,另一台设备的 token 依然有效。(2)token 泄露🔹假如 token 被别人拷贝了(比如被窃取、抓包、XSS 攻击),那个人依然可以继续用。🔹你本地删掉 token 没用,因为“坏人”还有副本。(3)黑名单/封禁需求🔹如果公司后台管理员封禁了某个账号,这个用户的 token 在过期前仍然能请求接口。🔹只能靠服务端来控制 token 的立即失效。
5.2 解决办法:
具体实现本文不做演示,着重介绍JWT
前端销毁:保证用户主动退出后,浏览器不再携带 token。
后端控制:保证遇到异常场景(黑名单、泄露、多端冲突)时,能强制失效。
方式一:Redis 黑名单
方式二:Redis 白名单(只保留最新 token)
方式三:短 token + refresh token
6. 使用场景
用户认证(替代传统 session)。
API 鉴权(前后端分离、微服务)。
信息安全传输(声明不可篡改)。
二、Spring Boot 使用 JWT 的完整示例
这里我们用 jjwt 库(io.jsonwebtoken:jjwt-api)来实现。
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> <!-- 支持 json 序列化 --><version>0.11.5</version><scope>runtime</scope> </dependency>
2. 工具类
JwtUtil
import io.jsonwebtoken.*; import io.jsonwebtoken.security.Keys;import java.security.Key; import java.util.Date;public class JwtUtil {// 私钥(实际项目中放到配置文件里)private static final Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);// 生成 tokenpublic static String generateToken(String userId, String role) {long nowMillis = System.currentTimeMillis();long expMillis = nowMillis + 3600000; // 1小时过期Date exp = new Date(expMillis);return Jwts.builder().setSubject(userId) // sub.claim("role", role) // 自定义字段.setIssuedAt(new Date(nowMillis)) // iat.setExpiration(exp) // exp.signWith(key) // 签名.compact();}// 验证 tokenpublic static boolean validateToken(String token) {try {Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);return true;} catch (JwtException e) {return false;}}// 获取用户IDpublic static String getUserId(String token) {Claims claims = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();return claims.getSubject();}// 获取角色public static String getUserRole(String token) {Claims claims = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();return claims.get("role", String.class);} }
3. Controller 示例
import org.springframework.web.bind.annotation.*;@RestController @RequestMapping("/auth") public class AuthController {// 模拟登录接口@PostMapping("/login")public String login(@RequestParam String username, @RequestParam String password) {// 假设用户名=admin, 密码=123456if ("admin".equals(username) && "123456".equals(password)) {// 登录成功,生成 JWTreturn JwtUtil.generateToken("1001", "admin");}return "用户名或密码错误";}// 需要鉴权的接口@GetMapping("/check")public String check(@RequestHeader("Authorization") String token) {token = token.replace("Bearer ", "");if (JwtUtil.validateToken(token)) {return "用户ID:" + JwtUtil.getUserId(token) +",角色:" + JwtUtil.getUserRole(token);}return "token 无效或已过期";} }
4. 请求示例
(1) 登录获取 Token
POST http://localhost:8080/auth/login?username=admin&password=123456
返回:
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMDAxIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNjkyMzQ1NjAwLCJleHAiOjE2OTIzNDkyMDB9.Tk7Q7jtwgm3d...
(2) 携带 Token 访问接口
GET http://localhost:8080/auth/check Header: Authorization: Bearer <上一步返回的token>
返回:
用户ID:1001,角色:admin
✅ 总结:
JWT 由 头部 + 载荷 + 签名 组成。
使用时,客户端存储 token 并在请求头中传递。
Spring Boot 中可以通过 工具类 + 拦截器/过滤器 来实现鉴权。