1. 项目概述
在前后端分离的现代Web应用中,Token认证已成为主流的身份验证机制。相比传统的Session认证,Token方案具有无状态、跨域友好、适合分布式系统等优势。本文将基于Spring Boot框架,从零构建一个完整的Token认证系统,包含JWT生成、拦截器验证、统一响应等核心模块。
这个方案特别适合以下场景:
- 需要快速为API接口添加鉴权层的中小型项目
- 开发移动应用后端服务(iOS/Android)
- 构建需要支持多端登录的Web应用
- 准备将单体应用改造为微服务架构
整套实现包含9个核心组件,从基础的JWT工具类到全局异常处理,每个模块都经过生产环境验证。我将重点讲解那些官方文档不会提及的实战细节,比如ThreadLocal的内存泄漏预防、JJWT版本选型考量等。
2. 环境准备与工程搭建
2.1 技术栈选型解析
选择Spring Boot 2.7.x而非最新3.x版本是经过深思熟虑的:
- 企业现有系统多数仍在使用Java 8,2.7.x有更好的兼容性
- JJWT 0.11.x与Spring Boot 2.x的集成更稳定
- 2.7.x是2.x系列的最后一个功能版本,社区支持周期长
xml复制<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
</parent>
2.2 项目结构设计
采用分层清晰的包结构,关键设计点:
- 将拦截器配置与业务代码物理隔离
- 异常处理单独成包,便于集中管理
- 工具类使用final修饰防止继承
code复制src/main/java/com/example/token/
├── config/ # 配置类
├── interceptor/ # 拦截器实现
├── util/ # 工具类
├── controller/ # API接口
├── exception/ # 异常处理
└── vo/ # 值对象
提示:生产环境建议增加constant包存放配置常量,避免魔法值散落各处
3. 核心组件实现
3.1 JWT工具类深度优化
JwtUtil类有三个容易被忽视的安全细节:
- 密钥长度必须≥256位(32字节),否则会抛出WeakKeyException
- 使用Keys.hmacShaKeyFor()而非直接new SecretKeySpec()
- 过期时间应设置为2-4小时,敏感操作需额外验证
java复制// 生产环境应从配置中心读取密钥
private static final String SECRET_KEY = "your_very_secure_secret_key_1234567890abcdef";
public static String generateToken(Long userId, String username) {
SecretKey key = Keys.hmacShaKeyFor(SECRET_KEY.getBytes(StandardCharsets.UTF_8));
return Jwts.builder()
.claim("userId", userId)
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE_TIME))
.signWith(key, SignatureAlgorithm.HS256)
.compact();
}
3.2 拦截器实现要点
AuthInterceptor的四个关键设计:
- 严格校验Authorization头格式(Bearer schema)
- 使用try-catch包裹JWT解析逻辑
- 通过ThreadLocal传递用户身份
- 实现afterCompletion清理资源
java复制@Override
public boolean preHandle(HttpServletRequest request, ...) {
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new TokenException(401, "Token格式错误");
}
try {
Long userId = JwtUtil.validateToken(token);
UserContextHolder.setUserId(userId);
} catch (Exception e) {
throw new TokenException(401, "Token验证失败");
}
}
4. 进阶配置与优化
4.1 拦截器配置策略
WebMvcConfig的路径匹配规则需要注意:
- /** 匹配所有路径(包括静态资源)
- 排除登录接口同时要排除Swagger等文档路径
- 可按业务模块配置不同拦截规则
java复制@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AuthInterceptor())
.addPathPatterns("/api/**")
.excludePathPatterns("/auth/login", "/doc/**");
}
4.2 全局异常处理技巧
GlobalExceptionHandler的异常处理优先级:
- 先处理自定义业务异常
- 再处理框架特定异常
- 最后兜底处理Exception
java复制@ExceptionHandler(TokenException.class)
public ResultVo<?> handleTokenException(TokenException e) {
log.warn("Token异常: {}", e.getMessage());
return ResultVo.fail(e.getCode(), e.getMessage());
}
@ExceptionHandler(Exception.class)
public ResultVo<?> handleException(Exception e) {
log.error("系统异常", e);
return ResultVo.fail(500, "系统繁忙");
}
5. 生产环境注意事项
5.1 安全加固措施
- 密钥管理:使用Vault或KMS服务动态获取密钥
- Token黑名单:集成Redis实现主动注销
- 限流防护:对/login接口添加RateLimit
- 日志脱敏:过滤日志中的Token信息
5.2 性能优化建议
- 使用本地缓存缓存用户基本信息
- 避免在JWT中存储过多claims
- 对静态资源路径完全放行拦截器
- 考虑使用Servlet Filter替代Interceptor
6. 测试方案设计
6.1 单元测试要点
- 模拟HttpServletRequest测试拦截器
- 验证Token过期和篡改场景
- 测试ThreadLocal的线程隔离性
- 覆盖所有异常分支
java复制@Test
void testExpiredToken() {
String expiredToken = generateExpiredTestToken();
HttpServletRequest request = mockRequestWithToken(expiredToken);
assertThrows(TokenException.class, () ->
interceptor.preHandle(request, null, null));
}
6.2 集成测试场景
- 测试登录→访问受保护接口→注销的完整流程
- 验证多线程并发下的ThreadLocal隔离
- 模拟集群环境下的Token验证
- 压力测试Token验证的性能损耗
7. 常见问题排查
7.1 典型问题速查表
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 返回401但Token有效 | 请求头未带Bearer前缀 | 检查Authorization头格式 |
| Token解析报错 | 密钥不一致 | 验证服务间密钥一致性 |
| 获取到null用户ID | ThreadLocal未清理 | 检查afterCompletion是否调用 |
| 性能突然下降 | Token过大 | 减少claims数量 |
7.2 内存泄漏预防
ThreadLocal使用必须配套清理操作:
java复制@Override
public void afterCompletion(...) {
// 必须执行remove防止内存泄漏
UserContextHolder.removeUserId();
}
在Spring Boot Actuator中添加健康检查:
java复制@Bean
public HealthIndicator threadLocalHealthIndicator() {
return () -> {
if(UserContextHolder.hasLeak()) {
return Health.down().build();
}
return Health.up().build();
};
}
8. 扩展与演进
8.1 微服务适配改造
- 将JwtUtil拆分为独立starter
- 增加Feign拦截器自动传递Token
- 集成Spring Cloud Gateway做统一鉴权
- 实现Token的自动续期机制
8.2 多端登录方案
- 增加设备类型标识(web/ios/android)
- 实现同账号多Token发放
- 基于Redis维护活跃会话
- 支持踢下线功能
这套Token认证体系已在笔者参与的多个电商和金融项目中验证,日均承载千万级API调用。关键点在于处理好安全与性能的平衡,以及完善的异常处理机制。实际开发中建议结合具体业务需求,在基础方案上做针对性增强。