[Java实战]Spring Boot 3整合JWT实现无状态身份认证(二十四)

发布于:2025-05-15 ⋅ 阅读:(19) ⋅ 点赞:(0)

[Java实战]Spring Boot 3整合JWT实现无状态身份认证(二十四)

一、JWT简介与核心概念

1. JWT是什么?

JSON Web Token (JWT) 是一种开放标准(RFC 7519),用于在各方之间安全地传输信息。JWT由三部分组成:

  • Header:描述令牌类型和签名算法(如HS256、RS256)。
  • Payload:包含用户身份信息(如用户ID、角色)和其他声明(如过期时间)。
  • Signature:对前两部分的签名,确保令牌未被篡改。

2. 为什么要用JWT?

  • 无状态:服务端无需存储会话信息,适合分布式系统。
  • 跨域支持:可通过HTTP头部轻松传递,适用于前后端分离架构。
  • 安全性:基于签名验证,防止数据篡改。

二、环境准备

1. 创建Spring Boot 3.4.5项目

通过 Spring Initializr 创建项目,选择以下依赖:

  • Spring Web
  • Spring Security
  • Lombok(简化代码)
    在这里插入图片描述

2. 添加JWT依赖

pom.xml 中添加 jjwt 库:

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.12.3</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.12.3</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.12.3</version>
    <scope>runtime</scope>
</dependency>

三、核心代码实现

1. JWT工具类



/**
 * JwtUtils - 类功能描述
 *
 * @author csdn:曼岛_
 * @version 1.0
 * @date 2025/5/14 15:00
 * @since openJDK 17
 */
@Component
public class JwtUtils {
    // 密钥(示例,实际应使用安全随机生成并妥善存储)
    private static final SecretKey SECRET_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256);
    private static final long EXPIRATION_TIME = 86400000; // 24小时(毫秒)

    // 生成JWT令牌
    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        return Jwts.builder()
                .claims(claims)
                .subject(userDetails.getUsername())
                .issuedAt(new Date())
                .expiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
                .signWith(SECRET_KEY)
                .compact();
    }

    // 解析用户名
    public String extractUsername(String token) {
        return extractClaim(token, Claims::getSubject);
    }

    // 验证令牌有效性
    public boolean validateToken(String token, UserDetails userDetails) {
        final String username = extractUsername(token);
        return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
    }

    // 检查令牌是否过期
    private boolean isTokenExpired(String token) {
        return extractExpiration(token).before(new Date());
    }

    // 提取过期时间(新增方法)
    private Date extractExpiration(String token) {
        return extractClaim(token, Claims::getExpiration);
    }

    // 提取声明
    private <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = extractAllClaims(token);
        return claimsResolver.apply(claims);
    }

    // 解析全部声明
    private Claims extractAllClaims(String token) {
        return Jwts.parser()
                .verifyWith(SECRET_KEY)
                .build()
                .parseSignedClaims(token)
                .getPayload();
    }

    // 从请求头获取令牌
    public String getTokenFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        return null;
    }
}

四、集成Spring Security

1. 配置Spring Security



@Configuration
@EnableWebSecurity
public class SecurityConfig {

    private final JwtAuthFilter jwtAuthFilter;

    public SecurityConfig(JwtAuthFilter jwtAuthFilter) {
        this.jwtAuthFilter = jwtAuthFilter;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .csrf(AbstractHttpConfigurer::disable)
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/api/auth/**").permitAll()
                        .anyRequest().authenticated()
                )
                .sessionManagement(session -> session
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                )
                .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance(); // 临时使用 NoOpPasswordEncoder
       // return new BCryptPasswordEncoder();  //生产环境这样用
    }

    @Bean
    public AuthenticationManager authenticationManager(
            AuthenticationConfiguration config
    ) throws Exception {
        return config.getAuthenticationManager();
    }
}

2. 实现JWT认证过滤器



/**
 * JwtAuthFilter - 类功能描述
 *
 * @author csdn:曼岛_
 * @version 1.0
 * @date 2025/5/14 15:04
 * @since openJDK 17
 */
@Component
public class JwtAuthFilter extends OncePerRequestFilter {
    private final JwtUtils jwtUtils;
    private final CustomUserDetailsService userDetailsService;

    public JwtAuthFilter(JwtUtils jwtUtils, UserDetailsService userDetailsService) {
        this.jwtUtils = jwtUtils;
        this.userDetailsService = (CustomUserDetailsService) userDetailsService;
    }

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain
    ) throws IOException, ServletException {
        String token = jwtUtils.getTokenFromRequest(request);
        if (token != null) {
            String username = jwtUtils.extractUsername(token);
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);
            UsernamePasswordAuthenticationToken authentication =
                    new UsernamePasswordAuthenticationToken(
                            userDetails,
                            null,
                            userDetails.getAuthorities()
                    );
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        filterChain.doFilter(request, response);
    }
}

五、创建认证接口

1. 用户登录请求DTO

public record LoginRequest(String username, String password) {}

2. 认证控制器



/**
 * AuthController - 类功能描述
 *
 * @author csdn:曼岛_
 * @version 1.0
 * @date 2025/5/14 15:07
 * @since openJDK 17
 */
@RestController
@RequestMapping("/api/auth")
public class AuthController {
    private final AuthenticationManager authenticationManager;
    private final JwtUtils jwtUtils;

    public AuthController(
            AuthenticationManager authenticationManager,
            JwtUtils jwtUtils
    ) {
        this.authenticationManager = authenticationManager;
        this.jwtUtils = jwtUtils;
    }

    @PostMapping("/login")
    public ResponseEntity<String> login(@RequestBody LoginRequest request) {
        String username = request.username();
        String password = request.password();

        System.out.println("=====username====="+username);
        System.out.println("=====password====="+password);

        Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(
                        request.username(),
                        request.password()
                )
        );
        // 设置安全上下文
        SecurityContextHolder.getContext().setAuthentication(authentication);

        // 从 Authentication 对象中提取 UserDetails
        UserDetails userDetails = (UserDetails) authentication.getPrincipal();
        // 生成 JWT 令牌
        String token = jwtUtils.generateToken(userDetails);
        return ResponseEntity.ok(token);
    }
}

六、测试验证

1. 登录获取Token

curl -X POST http://localhost:8080/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"user","password":"password"}'

在这里插入图片描述
在这里插入图片描述

2. 访问受保护接口

curl http://localhost:8080/api/protected \
  -H "Authorization: Bearer <your-jwt-token>"

七、常见问题与解决方案

1. SignatureException: JWT signature does not match

  • 原因:密钥不匹配或令牌被篡改。
  • 解决:确保服务端密钥一致且安全存储。

2. ExpiredJwtException

  • 原因:令牌已过期。
  • 解决:重新登录获取新令牌,或实现令牌刷新机制。

3. Spring Security配置不生效

  • 检查点:确保 @EnableWebSecurity 注解已添加,过滤器链配置正确。

八、总结

通过本文,已完成以下关键步骤:

  1. 集成 jjwt 库实现JWT生成与解析。
  2. 配置Spring Security实现无状态认证。
  3. 创建登录接口颁发JWT令牌。
  4. 通过过滤器验证请求中的JWT令牌。

扩展建议

  • 添加角色权限控制(@PreAuthorize)。
  • 实现令牌刷新机制。
  • 使用HTTPS增强传输安全性。

如果你在使用过程中遇到任何问题,欢迎在评论区留言交流。感谢你的阅读,希望这篇文章对你有所帮助!


网站公告

今日签到

点亮在社区的每一天
去签到