做过前后端分离项目的人都知道,传统的 Session 认证在分布式场景下越来越吃力:服务端要保存会话状态,集群环境还得考虑 Session 共享,移动端对接也麻烦。JWT 的出现很好地解决了这个问题——把用户信息直接编码进令牌,服务端不需要存储任何会话数据,只要验签就能确认身份。这篇文章就以 Spring Boot 为例,完整讲一遍 JWT 的整合过程,从原理到落地代码全部覆盖。

JWT 的结构与工作原理
JWT 由三部分组成,用点号分隔:Header.Payload.Signature。Header 描述令牌类型和签名算法,比如 HS256;Payload 是主体,存放声明(Claim),例如用户 ID、过期时间、签发者等;Signature 是对前两段内容用密钥计算出的签名,用于防止内容被篡改。
这里有个很多人容易忽略的点:Payload 默认只是 Base64Url 编码,并不加密。也就是说,谁拿到令牌都能解出里面的内容。所以绝对不要把密码、手机号这类敏感信息放进 Payload。JWT 解决的是防篡改问题,不是保密问题,两者不能混为一谈。
整个认证流程大致是这样的:用户登录成功后,服务端生成一个 JWT 返回给前端;前端把它存在 localStorage 或请求头里,之后每次请求都带上这个令牌;服务端收到请求后,用密钥重新计算签名与令牌中的签名比对,一致且未过期就放行。全程服务端不存任何东西,天然支持水平扩展。
引入依赖并编写 JWT 工具类
整合的第一步是引入依赖。常用的库是 jjwt,注意 0.11 版本之后需要显式引入 api 和 impl 两个包:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<lt;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>
<scope>runtime</scope>
</dependency>依赖就绪后,写一个 JWT 工具类,把生成、解析、校验三件事封装起来。密钥建议放在配置文件里,不要硬编码在代码中,而且密钥长度必须足够(HS256 至少 256 位),否则 jjwt 会直接抛异常:
@Component
public class JwtUtil {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expire}") // 过期时间,单位毫秒
private long expire;
private SecretKey getKey() {
return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
}
// 生成令牌
public String createToken(Long userId, String username) {
Date now = new Date();
Date expiry = new Date(now.getTime() + expire);
return Jwts.builder()
.setSubject(String.valueOf(userId))
.claim("username", username)
.setIssuedAt(now)
.setExpiration(expiry)
.signWith(getKey(), SignatureAlgorithm.HS256)
.compact();
}
// 解析令牌,无效或过期会抛出异常
public Claims parseToken(String token) {
return Jwts.parserBuilder()
.setSigningKey(getKey())
.build()
.parseClaimsJws(token)
.getBody();
}
// 判断令牌是否有效
public boolean validateToken(String token) {
try {
parseToken(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
}配置文件里这样写:jwt.secret 用一段足够长的随机字符串,jwt.expire 一般设为两小时左右。生成令牌时除了用户标识,还可以放角色、租户 ID 这类高频读取的字段,省去每次查库的开销。
登录发放令牌与拦截器鉴权
工具类写好之后,先实现登录接口。校验账号密码成功后调用 createToken 把令牌返回给前端:
@RestController
public class LoginController {
@Autowired
private UserService userService;
@Autowired
private JwtUtil jwtUtil;
@PostMapping("/login")
public Map<String, Object> login(@RequestBody LoginDTO dto) {
User user = userService.checkPassword(dto.getUsername(), dto.getPassword());
if (user == null) {
throw new BusinessException("用户名或密码错误");
}
String token = jwtUtil.createToken(user.getId(), user.getUsername());
Map<String, Object> result = new HashMap<>();
result.put("token", token);
return result;
}
}接下来是关键的一步:用拦截器统一校验请求携带的令牌。前端通常把令牌放在请求头 Authorization 中,格式为 Bearer <token>。拦截器解析出用户信息后,可以塞进 HttpServletRequest 的 attribute 里,后续 Controller 直接取用:
@Component
public class JwtInterceptor implements HandlerInterceptor {
@Autowired
private JwtUtil jwtUtil;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
String header = request.getHeader("Authorization");
if (header == null || !header.startsWith("Bearer ")) {
response.setStatus(401);
return false;
}
String token = header.substring(7);
if (!jwtUtil.validateToken(token)) {
response.setStatus(401);
return false;
}
Claims claims = jwtUtil.parseToken(token);
request.setAttribute("userId", Long.valueOf(claims.getSubject()));
request.setAttribute("username", claims.get("username", String.class));
return true;
}
}再写一个配置类注册拦截器,把登录、注册等公开接口排除在外。如果你用的是 Spring Security,也可以换成自定义的 OncePerRequestFilter 接入过滤链,思路是一样的,只是挂载位置不同。
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private JwtInterceptor jwtInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/login", "/register");
}
}令牌刷新与安全注意事项
JWT 最大的短板是签发后无法主动作废。如果用户改密码或账号被盗,旧令牌在过期前依然有效。常见的应对方案是双令牌机制:签发一个短效的 accessToken 和一个长效的 refreshToken,accessToken 过期后前端用 refreshToken 换新的。对于需要强制下线的场景,可以维护一个 Redis 黑名单,注销时把令牌的唯一标识 jti 存进去,拦截器校验时多查一次黑名单,牺牲一点无状态的纯粹性来换取可控性。
另外几点安全建议值得记牢:密钥不要写死在代码里,更不要提交到 Git 仓库,可以用环境变量或配置中心管理;令牌的有效期不要设太长,两小时以内比较稳妥;生产环境建议上 HTTPS,避免令牌在传输中被窃听;如果系统对权限要求细,别忘了在 Payload 里加角色声明,配合注解做接口级别的权限控制。
整体来看,Spring Boot 整合 JWT 并不复杂:一个工具类负责生成和解析,一个拦截器负责校验,再配合登录接口发令牌,核心链路就通了。真正决定系统安全的,是你在密钥管理、令牌生命周期和黑名单这些细节上的处理,把这些做到位,一套可靠的认证体系就成型了。
Spring BootJWT令牌认证修改时间:2026-09-03 04:56:34