JWT
定义
JSON Web Token,一种开放标准,用于在各方之间安全地传输信息,通常用于身份认证和信息交换。
结构
三部分
header.payload.signature
- header 包含令牌类型和使用的签名算法
- payload 包含声明,即要传输的数据,比如用户ID、角色、过期时间等
- signature 用于验证消息未被篡改,由 Header+Payload+密钥 通过指定算法生成
用途
- 用户登录后,服务器生成一个 JWT 返回给客户端。
- 客户端在后续请求的
Authorization头中携带该JWT。 - 服务器验证JWT的签名和有效期,从而确认用户身份,无需每次都查询数据库。
优点
- 无状态(stateless) 服务端不需要保存会话信息
- 可跨域使用
- 自包含 Payload 中可携带必要信息
拦截器
定义
拦截器是一种中间件机制,用于在请求到达目标处理函数(如控制器方法) 之前或之后执行通用逻辑。
常见功能
- 身份验证
- 日志记录
- 请求/响应修改
- 性能监控
- 错误统一处理
典型使用场景
JWT和拦截器的使用典型场景是:
- 用户登录后获得JWT;
- 后续每个请求都带上JWT
- 服务器的拦截器在请求进入业务逻辑前
- 检验是否存在 JWT
- 验证JWT的签名和有效期
- 如果有效,解析用户信息并附加到请求上下文种
- 如果无效,直接返回 401 错误,阻止请求继续
业务代码无需重复处理认证逻辑。
操作步骤
添加依赖
maven的pom.xml中添加依赖,添加后出现标红,需要reload,刷新Maven
xml
<!-- JWT 工具包 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!-- XML 解析器 (JDK 11+ 需要手动引入) -->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
创建 JWT 工具类
后端工具类(一般是utils目录),创建 JwtUtils.java
分为几个步骤:
- 设置密钥
- 过期时间
- 生成 token
- 解析 token,获取 Claims
java
package com.student.devflow.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
public class JwtUtils {
// 密钥 (真实项目中应该配置在 yml 里,这里为了简单写死)
private static final String SECRET = "123456";
// 过期时间 24小时
private static final long EXPIRATION = 86400000;
// 生成 Token
public static String generateToken(Long userId, String username) {
return Jwts.builder()
.setSubject(username)
.claim("userId", userId)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION))
.signWith(SignatureAlgorithm.HS256, SECRET)
.compact();
}
// 解析 Token 获取 Claims
public static Claims getClaimsByToken(String token) {
try {
return Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
return null; // 解析失败或过期
}
}
}
创建登录拦截器
要拦截所有的请求,检查Header中有没有token。
代码位置:
后端/config/xxx.java
java
package com.student.devflow.config;
import com.student.devflow.utils.JwtUtils;
import io.jsonwebtoken.Claims;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 放行 OPTIONS 请求 (跨域预检)
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
return true;
}
// 1. 从 Header 获取 Token
String token = request.getHeader("Authorization");
// 2. 校验 Token
if (token != null && !token.isEmpty()) {
Claims claims = JwtUtils.getClaimsByToken(token);
if (claims != null) {
// 登录成功,放行
// 可以把 userId 存入 request,方便 Controller 使用
request.setAttribute("userId", claims.get("userId"));
return true;
}
}
// 3. 失败,返回 401
response.setStatus(401);
response.getWriter().write("{\"code\": 401, \"msg\": \"No Permission\"}");
return false;
}
}
代码细节
- 判断条件
java
token != null && !token.isEmpty()
一种防御性编程的经典写法,安全地判断一个字符串是否"真正有内容"。
token != null判断token是否为null。
如果前端根本没传 Authorization头,request.getHeader("Authorization") 会返回 null。
如果直接对 null调用 isEmpty(),会抛出空指针异常。
!token.isEmpty()
在确认 token不是null的前提下,在判断它是否为空字符串。
有些客户端传了Header,但它是空的。
-
Claims claims = JwtUtils.getClaimsByToken(token);
是 JWT 的 payload 部分的Java对象表示,本质上是键值对,存储了签发Token时放入的所有信息。
JWT 的结构是Header.PayloadSignature
其中 ````Payload```是JSON格式:
json
{
"sub": "alice",
"userId": 1001,
"iat": 1712345678,
"exp": 1712432078
}
调用
Claims claims = JwtUtils.getClaimsByToken(token);
时,JwtUtils内部使用Jwts.parser().parseClaimsJws(token).getBody(),会验证签名+检查过期 。
如果成功,则会把 payload 解析成一个 Claims对象。
Claims继承自Map<String, Object>,可以像操作Map一样操作它。比如调用get方法。
补充知识
中间件
- 在请求到达最终处理逻辑之前(或之后)执行的函数,用于处理通用任务,比如日志记录、身份验证、数据解析、错误处理等。
- 特点
- 可以访问请求对象、响应对象,以及下一个中间件。
- 可以修改请求或响应。
- 可以决定是否将请求继续传递给下一个中间件或最终处理函数。
- 可以提前终止请求(比如返回401未授权错误)
在spring中,叫Filter或Interceptor
目标处理函数
- 通常指真正处理某个 HTTP 请求业务逻辑的函数,也就是请求流程的终点。
- 比如Express的路由处理函数,Spring Controller方法,NestJS控制器的方法等。
控制器方法
- MVC架构或现代Web框架中,控制器是专门负责处理HTTP请求的类,控制器方法是类中的具体方法,每个方法对应一个路由(URL+HTTP方法)。
- 作用:1. 接收请求参数;2. 调用业务逻辑;3. 返回响应。
典型的 HTTP 请求处理顺序
[客户端请求]
↓
[全局中间件] → [路由中间件] → [控制器方法(即目标处理函数)]
↓
[响应返回]
签名密钥
用于JWT的签名与验签,核心目的是确保JWT的完整性和真实性。
包括令牌未被篡改、令牌确实由可信服务器签发。
如果没有签名或密钥泄露,攻击者可以伪造任意用户身份,如修改userId为管理员ID,服务器无法判断令牌是否可信。
密钥在JWT生命周期中的使用步骤
-
生成JWT时,用密钥进行签名。
-
服务器将 Header 和 Payload 拼接成字符串(Base64Url 编码后)。使用 HMAC-SHA256 算法 + SECRET 密钥 对这个字符串进行加密,生成 Signature。(只有知道
SECRET的人,才能生成合法的签名) -
验证JWT时,用密钥重新计算并比对签名。
-
服务器收到JWT,从token中提取
header和payload,用同样的算法和相同的SECRET,重新计算签名,expectedSignature = HMAC-SHA256(a + "." + b, SECRET)
-
将计算出的
expectedSignature与token中的原始签名进行比对,如果一致,则令牌合法,未被篡改,如果不一致,则是令牌被伪造或密钥错误,抛出SignatureException
图解流程
[服务器生成 JWT]
↓
Header + Payload + 【SECRET】 → 计算 Signature → 组成 token
↓
[客户端保存 token,每次请求携带]
↓
[服务器收到 token]
↓
用【SECRET】重新计算 Signature
↓
比对计算值 vs token 中的 Signature
↓
一致? → 信任数据(如 userId) → 允许访问
不一致? → 拒绝请求(401 Unauthorized)