java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > JWT 拦截器无状态登录

JWT + 拦截器实现无状态登录系统

作者:白仑色

JWT(JSON Web Token)提供了一种无状态的解决方案:用户登录后,服务器返回一个 Token,后续请求携带该 Token 即可完成身份验证,无需服务器存储会话信息,本文将结合 Spring Boot 拦截器,手把手实现一个完整的JWT无状态登录系统,感兴趣的朋友一起看看吧

✅ 引言

在现代 Web 开发中,传统的 Session 认证方式在分布式、微服务架构下面临挑战:

JWT(JSON Web Token) 提供了一种无状态的解决方案:用户登录后,服务器返回一个 Token,后续请求携带该 Token 即可完成身份验证,无需服务器存储会话信息。

本文将结合 Spring Boot 拦截器,手把手实现一个完整的 JWT 无状态登录系统。

📌 一、JWT 是什么?

JWT 是一个开放标准(RFC 7519),用于在各方之间安全地传输信息。

一个 JWT 通常由三部分组成,用 . 分隔:

xxxxx.yyyyy.zzzzz

优点:自包含、可扩展、跨语言、无状态。

📌 二、技术选型

📌 三、项目结构

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 拦截器无状态登录内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文