JWT + 拦截器实现无状态登录系统
作者:白仑色
JWT(JSON Web Token)提供了一种无状态的解决方案:用户登录后,服务器返回一个 Token,后续请求携带该 Token 即可完成身份验证,无需服务器存储会话信息,本文将结合 Spring Boot 拦截器,手把手实现一个完整的JWT无状态登录系统,感兴趣的朋友一起看看吧
✅ 引言
在现代 Web 开发中,传统的 Session 认证方式在分布式、微服务架构下面临挑战:
- Session 存储依赖服务器内存或 Redis
- 跨域、跨服务共享 Session 复杂
- 增加服务器负担
JWT(JSON Web Token) 提供了一种无状态的解决方案:用户登录后,服务器返回一个 Token,后续请求携带该 Token 即可完成身份验证,无需服务器存储会话信息。
本文将结合 Spring Boot 拦截器,手把手实现一个完整的 JWT 无状态登录系统。
📌 一、JWT 是什么?
JWT 是一个开放标准(RFC 7519),用于在各方之间安全地传输信息。
一个 JWT 通常由三部分组成,用 .
分隔:
xxxxx.yyyyy.zzzzz
- Header:令牌类型和签名算法
- Payload:存放用户信息(如用户 ID、角色、过期时间等)
- Signature:签名,用于验证 Token 是否被篡改
✅ 优点:自包含、可扩展、跨语言、无状态。
📌 二、技术选型
- Spring Boot 2.7+
- JWT 库:
io.jsonwebtoken:jjwt
- 加密算法:HMAC SHA256
- 拦截器:
HandlerInterceptor
📌 三、项目结构
src/ ├── main/ │ ├── java/ │ │ └── com/example/jwtlogin/ │ │ ├── config/ → 配置类 │ │ ├── interceptor/ → 拦截器 │ │ ├── util/ → 工具类 │ │ ├── controller/ → 控制器 │ │ └── entity/ → 实体类
📌 四、核心代码实现
4.1 添加依赖(pom.xml)
<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> <scope>runtime</scope> </dependency>
⚠️ 注意:JJWT 0.11+ 版本拆分了模块,需引入三个依赖。
4.2 JWT 工具类
@Component public class JwtUtil { // 密钥(应放在配置文件中) private static final String SECRET = "your-256-bit-secret-your-256-bit-secret"; // 过期时间:24小时 private static final long EXPIRATION = 1000 * 60 * 60 * 24; /** * 生成 Token */ public String generateToken(String username, Long userId, String role) { Map<String, Object> claims = new HashMap<>(); claims.put("userId", userId); claims.put("role", role); return Jwts.builder() .setSubject(username) .setClaims(claims) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION)) .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); } /** * 解析 Token */ public Claims parseToken(String token) { try { return Jwts.parser() .setSigningKey(SECRET) .parseClaimsJws(token) .getBody(); } catch (ExpiredJwtException e) { throw new RuntimeException("Token 已过期"); } catch (UnsupportedJwtException | MalformedJwtException | SignatureException | IllegalArgumentException e) { throw new RuntimeException("Token 无效"); } } /** * 验证 Token 是否有效 */ public boolean validateToken(String token) { try { Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token); return true; } catch (Exception e) { return false; } } }
4.3 登录控制器
@RestController @RequestMapping("/auth") public class AuthController { @Autowired private JwtUtil jwtUtil; /** * 用户登录 */ @PostMapping("/login") public ResponseEntity<Map<String, Object>> login(@RequestBody LoginRequest request) { // 这里应调用 UserService 验证用户名密码 // 为简化,假设用户名密码正确 if ("admin".equals(request.getUsername()) && "123456".equals(request.getPassword())) { String token = jwtUtil.generateToken(request.getUsername(), 1L, "ADMIN"); Map<String, Object> result = new HashMap<>(); result.put("token", token); result.put("username", request.getUsername()); result.put("role", "ADMIN"); return ResponseEntity.ok(result); } else { return ResponseEntity.status(401).body(Map.of("msg", "用户名或密码错误")); } } /** * 用户登出(前端清空 Token 即可) */ @PostMapping("/logout") public ResponseEntity<String> logout() { return ResponseEntity.ok("登出成功"); } } // 登录请求 DTO class LoginRequest { private String username; private String password; // getter & setter }
4.4 JWT 拦截器
@Component public class JwtInterceptor implements HandlerInterceptor { @Autowired private JwtUtil jwtUtil; private static final String AUTH_HEADER = "Authorization"; private static final String TOKEN_PREFIX = "Bearer "; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 1. 放行 OPTIONS 请求(预检请求) if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { response.setStatus(HttpServletResponse.SC_OK); return true; } // 2. 放行登录接口 String requestURI = request.getRequestURI(); if ("/auth/login".equals(requestURI) || "/auth/logout".equals(requestURI)) { return true; } // 3. 获取并验证 Token String token = request.getHeader(AUTH_HEADER); if (token == null || !token.startsWith(TOKEN_PREFIX)) { response.setStatus(401); response.getWriter().write("{\"code\":401,\"msg\":\"缺少 Token\"}"); return false; } token = token.substring(TOKEN_PREFIX.length()); try { Claims claims = jwtUtil.parseToken(token); // 将用户信息存入 request,供后续 Controller 使用 request.setAttribute("currentUser", claims.getSubject()); request.setAttribute("userId", claims.get("userId")); request.setAttribute("role", claims.get("role")); return true; } catch (RuntimeException e) { response.setStatus(401); response.getWriter().write("{\"code\":401,\"msg\":\"" + e.getMessage() + "\"}"); return false; } } }
4.5 注册拦截器
@Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private JwtInterceptor jwtInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(jwtInterceptor) .addPathPatterns("/api/**") // 保护所有 API 接口 .excludePathPatterns("/auth/**", "/public/**"); // 放行认证和公共接口 } }
4.6 测试受保护的接口
@RestController @RequestMapping("/api") public class UserController { @GetMapping("/profile") public Map<String, Object> getProfile(HttpServletRequest request) { Map<String, Object> profile = new HashMap<>(); profile.put("username", request.getAttribute("currentUser")); profile.put("userId", request.getAttribute("userId")); profile.put("role", request.getAttribute("role")); profile.put("email", "admin@example.com"); return profile; } @GetMapping("/admin/data") public String adminData(HttpServletRequest request) { String role = (String) request.getAttribute("role"); if ("ADMIN".equals(role)) { return "敏感数据:只有管理员可见"; } else { return "权限不足"; } } }
📌 五、测试流程
1. 登录获取 Token
curl -X POST http://localhost:8080/auth/login \ -H "Content-Type: application/json" \ -d '{"username":"admin","password":"123456"}'
响应:
{ "token": "eyJhbGciOiJIUzI1NiJ9.xxxxx.yyyyy", "username": "admin", "role": "ADMIN" }
2. 携带 Token 访问受保护接口
curl http://localhost:8080/api/profile \ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.xxxxx.yyyyy"
响应:
{ "username": "admin", "userId": 1, "role": "ADMIN", "email": "admin@example.com" }
3. 不带 Token 访问 → 401 未授权
📌 六、生产环境优化建议
优化点 | 说明 |
---|---|
🔐 密钥安全 | 将 SECRET 放在 application.yml 或环境变量中,不要硬编码 |
🔄 Token 刷新 | 实现 Refresh Token 机制,避免频繁登录 |
🛑 Token 黑名单 | 使用 Redis 记录已注销的 Token,防止被盗用 |
🧩 自定义注解 | 使用 @RequireAuth(role="ADMIN") 简化权限控制 |
📊 日志监控 | 记录 Token 解析失败日志,便于排查问题 |
✅ 总结
步骤 | 说明 |
---|---|
1. 用户登录 | 验证账号密码,生成 JWT |
2. 客户端存储 | 将 Token 存入 localStorage 或 Cookie |
3. 携带请求 | 每次请求在 Authorization 头中携带 Bearer Token |
4. 拦截器验证 | 解析 Token,校验签名和过期时间 |
5. 放行或拒绝 | 验证通过则放行,否则返回 401 |
💡 无状态登录的核心:服务器不保存会话状态,所有信息都封装在 Token 中,由客户端负责携带和管理。
📚 推荐
到此这篇关于JWT + 拦截器实现无状态登录系统的文章就介绍到这了,更多相关JWT 拦截器无状态登录内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!