Spring Boot整合Spring Security与JWT实现无状态认证:实战指南

Spring Boot整合Spring Security与JWT实现无状态认证:实战指南

一、JWT认证原理简介

JSON Web Token(JWT)是一种开放标准(RFC 7519),由三部分组成:

  • Header(头部):声明令牌类型和签名算法
  • Payload(负载):携带用户身份信息
  • Signature(签名):防篡改验证

认证流程:

  1. 客户端提交登录凭证
  2. 服务端验证通过后生成JWT
  3. 客户端后续请求携带JWT
  4. 服务端验证JWT有效性

二、项目搭建与依赖准备

1. 创建Spring Boot项目

选择依赖:

  • Spring Web
  • Spring Security
  • Lombok
  • Spring Data JPA(数据库存储)

2. 添加JWT依赖

<dependency>
    <groupId>io.jsonwebtokengroupId>
    <artifactId>jjwt-apiartifactId>
    <version>0.11.5version>
dependency>
<dependency>
    <groupId>io.jsonwebtokengroupId>
    <artifactId>jjwt-implartifactId>
    <version>0.11.5version>
    <scope>runtimescope>
dependency>
<dependency>
    <groupId>io.jsonwebtokengroupId>
    <artifactId>jjwt-jacksonartifactId>
    <version>0.11.5version>
    <scope>runtimescope>
dependency>

三、核心组件实现

1. JWT工具类

public class JwtUtils {
    private static final String SECRET_KEY = "your-256-bit-secret";
    private static final long EXPIRATION = 86400000; // 24小时

    public static String generateToken(UserDetails userDetails) {
        return Jwts.builder()
                .setSubject(userDetails.getUsername())
                .claim("roles", userDetails.getAuthorities())
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION))
                .signWith(SignatureAlgorithm.HS256, SECRET_KEY)
                .compact();
    }

    public static Claims parseToken(String token) {
        return Jwts.parser()
                .setSigningKey(SECRET_KEY)
                .parseClaimsJws(token)
                .getBody();
    }

    public static boolean validateToken(String token, UserDetails userDetails) {
        final String username = extractUsername(token);
        return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
    }

    private static boolean isTokenExpired(String token) {
        return extractExpiration(token).before(new Date());
    }

    // 其他辅助方法...
}

2. 自定义UserDetails实现

@Entity
@Data
public class User implements UserDetails {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String password;
    
    @ManyToMany(fetch = FetchType.EAGER)
    private Set<Role> roles;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return roles.stream()
                .map(role -> new SimpleGrantedAuthority(role.getName()))
                .collect(Collectors.toList());
    }

    // 实现其他UserDetails方法...
}

四、安全配置类

1. Spring Security配置

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
    
    private final JwtAuthenticationFilter 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 new BCryptPasswordEncoder();
    }
}

2. JWT认证过滤器

@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final JwtUtils jwtUtils;
    private final UserDetailsService userDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                    HttpServletResponse response, 
                                    FilterChain filterChain) throws ServletException, IOException {
        final String authHeader = request.getHeader("Authorization");
        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            filterChain.doFilter(request, response);
            return;
        }

        try {
            final String jwt = authHeader.substring(7);
            final String username = jwtUtils.extractUsername(jwt);
            
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                if (jwtUtils.validateToken(jwt, userDetails)) {
                    UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
                            userDetails, null, userDetails.getAuthorities());
                    authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    SecurityContextHolder.getContext().setAuthentication(authToken);
                }
            }
        } catch (Exception e) {
            logger.error("Cannot set user authentication", e);
        }
        
        filterChain.doFilter(request, response);
    }
}

五、认证控制器实现

@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
public class AuthController {

    private final AuthenticationManager authenticationManager;
    private final JwtUtils jwtUtils;
    private final UserService userService;

    @PostMapping("/login")
    public ResponseEntity<?> authenticateUser(@RequestBody LoginRequest request) {
        Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(
                        request.getUsername(),
                        request.getPassword()));
        
        SecurityContextHolder.getContext().setAuthentication(authentication);
        UserDetails userDetails = (UserDetails) authentication.getPrincipal();
        
        String jwt = jwtUtils.generateToken(userDetails);
        return ResponseEntity.ok(new JwtResponse(jwt));
    }

    @PostMapping("/register")
    public ResponseEntity<?> registerUser(@RequestBody RegisterRequest request) {
        if (userService.existsByUsername(request.getUsername())) {
            return ResponseEntity.badRequest().body("用户名已存在");
        }
        
        User user = new User();
        user.setUsername(request.getUsername());
        user.setPassword(passwordEncoder.encode(request.getPassword()));
        userService.saveUser(user);
        
        return ResponseEntity.ok("用户注册成功");
    }
}

六、测试验证

使用Postman测试流程:

  1. 注册用户
POST /api/auth/register
{
    "username": "testuser",
    "password": "Test@1234"
}
  1. 登录获取Token
POST /api/auth/login
{
    "username": "testuser",
    "password": "Test@1234"
}

响应:
{
    "token": "eyJhbGciOiJIUzI1NiJ9.xxxxxx"
}
  1. 访问受保护接口
GET /api/protected
Headers: Authorization: Bearer 

七、安全增强措施

  1. 密钥管理:
// 推荐从环境变量读取密钥
private static final String SECRET_KEY = System.getenv("JWT_SECRET");
  1. 刷新令牌机制:
@PostMapping("/refresh-token")
public ResponseEntity<?> refreshToken(HttpServletRequest request) {
    String refreshToken = jwtUtils.getRefreshTokenFromRequest(request);
    // 验证刷新令牌并颁发新访问令牌
}
  1. 黑名单机制:
// 登出时将令牌加入黑名单
public void logout(String token) {
    long expiration = jwtUtils.getExpirationFromToken(token);
    long currentTime = System.currentTimeMillis();
    if (expiration > currentTime) {
        blacklistService.addToBlacklist(token, expiration - currentTime);
    }
}

八、最佳实践建议

  1. 使用HTTPS传输JWT
  2. 设置合理的令牌有效期(访问令牌1小时,刷新令牌7天)
  3. 存储敏感信息在服务端,Payload只放必要信息
  4. 定期轮换签名密钥
  5. 实现令牌吊销机制
  6. 监控异常认证尝试
  7. 使用强密码策略(至少8位,包含大小写字母、数字和特殊字符)

结语

通过本实践案例,我们实现了基于JWT的无状态认证系统。实际开发中还需要考虑:

  • 分布式系统的会话管理
  • 微服务架构中的令牌传递
  • 第三方登录集成(OAuth2)
  • 审计日志记录

建议结合具体业务需求调整安全策略,并定期进行安全渗透测试。Spring Security与JWT的结合为现代Web应用提供了灵活强大的安全解决方案,正确实施可以有效保护系统资源。

你可能感兴趣的:(笔记,spring,spring,boot,数据库)