Spring Boot REST API:POST接口Bearer Token认证问题求助
解决Spring Boot中/api/interests接口的JWT身份认证问题
要实现Bearer Token的校验,核心是通过Spring Security的过滤器链拦截请求,解析并验证JWT,然后将认证信息注入到安全上下文。以下是针对你的场景的具体修改步骤:
1. 编写JWT认证过滤器
创建一个继承OncePerRequestFilter的过滤器,负责从请求头提取Token、验证Token有效性,并设置用户认证信息:
@Component public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtUtil jwtUtil; private final UserDetailsService userDetailsService; public JwtAuthenticationFilter(JwtUtil jwtUtil, UserDetailsService userDetailsService) { this.jwtUtil = jwtUtil; this.userDetailsService = userDetailsService; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 从请求头获取Authorization字段 String authHeader = request.getHeader("Authorization"); String token = null; String username = null; // 提取Bearer Token if (authHeader != null && authHeader.startsWith("Bearer ")) { token = authHeader.substring(7); username = jwtUtil.extractUsername(token); // 从Token中获取用户名/邮箱 } // 如果Token有效且当前上下文未认证 if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = userDetailsService.loadUserByUsername(username); // 验证Token有效性 if (jwtUtil.validateToken(token, userDetails)) { // 创建认证对象 UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); // 设置到安全上下文 SecurityContextHolder.getContext().setAuthentication(authToken); } } filterChain.doFilter(request, response); } }
2. 修改Spring Security配置类
将JWT过滤器添加到过滤链中,同时配置/api/interests的POST请求需要认证,放开注册、登录接口的权限:
@Configuration @EnableWebSecurity public class SecurityConfig { private final JwtAuthenticationFilter jwtAuthFilter; private final AuthenticationProvider authenticationProvider; public SecurityConfig(JwtAuthenticationFilter jwtAuthFilter, AuthenticationProvider authenticationProvider) { this.jwtAuthFilter = jwtAuthFilter; this.authenticationProvider = authenticationProvider; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) .authorizeHttpRequests(auth -> auth // 放开注册、登录接口 .requestMatchers("/api/auth/register", "/api/auth/login").permitAll() // /api/interests的POST请求需要认证 .requestMatchers(HttpMethod.POST, "/api/interests").authenticated() // 其他请求按需配置 .anyRequest().authenticated() ) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authenticationProvider(authenticationProvider) // 将JWT过滤器加到用户名密码过滤器之前 .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } // 配置基于数据库的用户认证提供者 @Bean public AuthenticationProvider authenticationProvider(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) { DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); authProvider.setUserDetailsService(userDetailsService); authProvider.setPasswordEncoder(passwordEncoder); return authProvider; } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
3. 完善JwtUtil工具类的关键方法
确保你的JwtUtil包含以下核心方法,用于提取用户信息和验证Token:
@Component public class JwtUtil { // 建议从配置文件读取密钥和过期时间 private String secretKey = "your-strong-secret-key-here"; private long jwtExpirationMs = 86400000; // 24小时过期 // 从Token中提取用户名/邮箱 public String extractUsername(String token) { return extractClaim(token, Claims::getSubject); } // 通用提取Claims方法 public <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.parserBuilder() .setSigningKey(getSignInKey()) .build() .parseClaimsJws(token) .getBody(); } // 验证Token是否有效(匹配用户信息+未过期) 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 Key getSignInKey() { byte[] keyBytes = Decoders.BASE64.decode(secretKey); return Keys.hmacShaKeyFor(keyBytes); } // 登录时生成Token的方法 public String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(); return createToken(claims, userDetails.getUsername()); } private String createToken(Map<String, Object> claims, String subject) { return Jwts.builder() .setClaims(claims) .setSubject(subject) .setIssuedAt(new Date(System.currentTimeMillis())) .setExpiration(new Date(System.currentTimeMillis() + jwtExpirationMs)) .signWith(getSignInKey(), SignatureAlgorithm.HS256) .compact(); } }
4. 优化兴趣接口(可选)
如果需要关联兴趣到当前用户,可以在接口中获取认证用户信息,同时添加注解确保只有认证用户能访问:
@RestController @RequestMapping("/api/interests") public class InterestController { private final InterestService interestService; public InterestController(InterestService interestService) { this.interestService = interestService; } @PostMapping @PreAuthorize("isAuthenticated()") public ResponseEntity<Interest> createInterest(@RequestBody InterestRequest request) { // 获取当前登录用户的用户名/邮箱 UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); Interest interest = interestService.createInterest(request.getDescription(), userDetails.getUsername()); return ResponseEntity.status(HttpStatus.CREATED).body(interest); } }
验证步骤
- 调用注册接口创建用户,再调用登录接口获取Bearer Token。
- 调用POST /api/interests时,在请求头中添加:
Authorization: Bearer <你的Token>。 - Token有效则正常创建兴趣,无效/过期则返回401 Unauthorized。
内容的提问来源于stack exchange,提问作者Europa
相关产品推荐
相关产品推荐

