卖不甜枣 发表于 2025-3-7 14:34:50

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

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

一、JWT认证原理简介

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


[*]Header(头部):声明令牌范例和签名算法
[*]Payload(负载):携带用户身份信息
[*]Signature(签名):防窜改验证
认证流程:

[*]客户端提交登录凭证
[*]服务端验证通过后天生JWT
[*]客户端后续哀求携带JWT
[*]服务端验证JWT有用性
二、项目搭建与依赖准备

1. 创建Spring Boot项目

选择依赖:


[*]Spring Web
[*]Spring Security
[*]Lombok
[*]Spring Data JPA(数据库存储)
2. 添加JWT依赖

<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>
三、核心组件实现

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测试流程:

[*]注册用户
POST /api/auth/register
{
    "username": "testuser",
    "password": "Test@1234"
}

[*]登录获取Token
POST /api/auth/login
{
    "username": "testuser",
    "password": "Test@1234"
}

响应:
{
    "token": "eyJhbGciOiJIUzI1NiJ9.xxxxxx"
}

[*]访问受保护接口
GET /api/protected
Headers: Authorization: Bearer <your-token>
七、安全加强措施


[*]密钥管理:
// 推荐从环境变量读取密钥
private static final String SECRET_KEY = System.getenv("JWT_SECRET");

[*]革新令牌机制:
@PostMapping("/refresh-token")
public ResponseEntity<?> refreshToken(HttpServletRequest request) {
    String refreshToken = jwtUtils.getRefreshTokenFromRequest(request);
    // 验证刷新令牌并颁发新访问令牌
}

[*]黑名单机制:
// 登出时将令牌加入黑名单
public void logout(String token) {
    long expiration = jwtUtils.getExpirationFromToken(token);
    long currentTime = System.currentTimeMillis();
    if (expiration > currentTime) {
      blacklistService.addToBlacklist(token, expiration - currentTime);
    }
}
八、最佳实践建议


[*]使用HTTPS传输JWT
[*]设置公道的令牌有用期(访问令牌1小时,革新令牌7天)
[*]存储敏感信息在服务端,Payload只放必要信息
[*]定期轮换签名密钥
[*]实现令牌吊销机制
[*]监控非常认证尝试
[*]使用强密码计谋(至少8位,包罗大小写字母、数字和特殊字符)
结语

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


[*]分布式系统的会话管理
[*]微服务架构中的令牌传递
[*]第三方登录集成(OAuth2)
[*]审计日志记录
建议联合具体业务需求调整安全计谋,并定期进行安全渗透测试。Spring Security与JWT的联合为现代Web应用提供了灵活强盛的安全办理方案,正确实行可以有用保护系统资源。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: Spring Boot整合Spring Security与JWT实现无状态认证:实战指南