Spring Security配置JWT后,/auth/**请求仍返回401求助
问题:Spring Security中/auth/**端点配置放行仍返回401错误
我为Spring Security配置了JWT认证,已在配置类中允许/auth/**路径的请求,但这些端点仍返回401状态码。以下是相关代码:
SecurityConfig.java
@Configuration @EnableWebSecurity @EnableMethodSecurity public class SecurityConfig { private final UserDetailsServiceImpl userDetailsService; private final AuthEntryPointJwt unauthorizedHandler; public SecurityConfig(UserDetailsServiceImpl userDetailsService, AuthEntryPointJwt unauthorizedHandler) { this.userDetailsService = userDetailsService; this.unauthorizedHandler = unauthorizedHandler; } @Bean public AuthTokenFilter authenticationJwtTokenFilter() { return new AuthTokenFilter(); } @Bean public DaoAuthenticationProvider authenticationProvider() { DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); authProvider.setUserDetailsService(userDetailsService); authProvider.setPasswordEncoder(passwordEncoder()); return authProvider; } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception { return authConfig.getAuthenticationManager(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http.csrf(AbstractHttpConfigurer::disable) .exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler)) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth.requestMatchers("/auth/**").permitAll() .requestMatchers("/api/**").authenticated() .anyRequest().authenticated() ).addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class) .authenticationProvider(authenticationProvider()) .build(); } @Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurer() { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS") .allowedHeaders("*") .allowCredentials(false) .maxAge(3600); } }; } }
AuthController.java
@RestController @RequestMapping("/auth") public class AuthController { private final AuthenticationManager authenticationManager; private final UserRepository userRepository; private final PasswordEncoder encoder; private final JwtUtils jwtUtils; public AuthController(AuthenticationManager authenticationManager, UserRepository userRepository, PasswordEncoder encoder, JwtUtils jwtUtils) { this.authenticationManager = authenticationManager; this.userRepository = userRepository; this.encoder = encoder; this.jwtUtils = jwtUtils; } @PostMapping("/signin") public ResponseEntity<?> authenticateUser(@Valid @RequestBody LoginRequest loginRequest) { Authentication authentication = authenticationManager .authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword())); SecurityContextHolder.getContext().setAuthentication(authentication); UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal(); ResponseCookie jwtCookie = jwtUtils.generateJwtCookie(userDetails); return ResponseEntity.ok().header(HttpHeaders.SET_COOKIE, jwtCookie.toString()) .body(new UserInfoResponse(userDetails.getId(), userDetails.getUsername(), userDetails.getEmail())); } @PostMapping("/signup") public ResponseEntity<?> registerUser(@Valid @RequestBody SignupRequest signUpRequest) { if (userRepository.existsByUsername(signUpRequest.getUsername())) { return ResponseEntity.badRequest().body("Error: Username is already taken!"); } if (userRepository.existsByEmail(signUpRequest.getEmail())) { return ResponseEntity.badRequest().body("Error: Email is already in use!"); } // Create new user's account User user = new User(signUpRequest.getUsername(), signUpRequest.getEmail(), encoder.encode(signUpRequest.getPassword())); userRepository.save(user); return ResponseEntity.ok("User registered successfully!"); } @PostMapping("/signout") public ResponseEntity<?> logoutUser() { ResponseCookie cookie = jwtUtils.getCleanJwtCookie(); return ResponseEntity.ok().header(HttpHeaders.SET_COOKIE, cookie.toString()) .body("You've been signed out!"); } } @Data @AllArgsConstructor @NoArgsConstructor class SignupRequest { private String username; private String email; private String password; } @Data @AllArgsConstructor @NoArgsConstructor class LoginRequest { private String username; private String password; } @Data @AllArgsConstructor @NoArgsConstructor class UserInfoResponse { private Long id; private String username; private String email; }
更新:AuthTokenFilter.java
@Service public class AuthTokenFilter extends OncePerRequestFilter { @Autowired private JwtUtils jwtUtils; @Autowired private UserDetailsServiceImpl userDetailsService; private static final Logger logger = LoggerFactory.getLogger(AuthTokenFilter.class); @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { try { String jwt = parseJwt(request); if (jwt != null && jwtUtils.validateJwtToken(jwt)) { String username = jwtUtils.getUserNameFromJwtToken(jwt); UserDetails userDetails = userDetailsService.loadUserByUsername(username); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); } } catch (Exception e) { logger.error("Cannot set user authentication: {}", e); } filterChain.doFilter(request, response); } private String parseJwt(HttpServletRequest request) { return request.getHeader(HttpHeaders.AUTHORIZATION); } }
问题排查与解决方案
核心问题
- **AuthTokenFilter未跳过/auth/路径:该过滤器会对所有请求执行JWT解析逻辑,即使请求属于已放行的
/auth/**路径,解析失败(比如无Authorization头)会触发认证异常,最终被AuthEntryPointJwt返回401。 - JWT解析未处理Bearer前缀:标准Authorization头格式为
Bearer <token>,直接提取头内容会导致JWT验证失败。 - Filter重复注册风险:
AuthTokenFilter标注了@Service,可能被Spring自动注册为全局Filter,与SecurityConfig中配置的Filter重复执行。
具体修复步骤
1. 修改AuthTokenFilter,跳过/auth/**路径验证
在doFilterInternal开头添加路径判断,直接放行认证相关请求:
@Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 跳过/auth路径的JWT验证 if (request.getRequestURI().startsWith("/auth/")) { filterChain.doFilter(request, response); return; } try { String jwt = parseJwt(request); if (jwt != null && jwtUtils.validateJwtToken(jwt)) { // 原有认证逻辑 } } catch (Exception e) { logger.error("Cannot set user authentication: {}", e); } filterChain.doFilter(request, response); }
2. 修复JWT解析逻辑,处理Bearer前缀
private String parseJwt(HttpServletRequest request) { String headerAuth = request.getHeader(HttpHeaders.AUTHORIZATION); if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) { return headerAuth.substring(7); } return null; }
需导入org.springframework.util.StringUtils。
3. 避免Filter重复注册
去掉AuthTokenFilter上的@Service注解,改用构造注入,由SecurityConfig的Bean创建实例:
public class AuthTokenFilter extends OncePerRequestFilter { private final JwtUtils jwtUtils; private final UserDetailsServiceImpl userDetailsService; public AuthTokenFilter(JwtUtils jwtUtils, UserDetailsServiceImpl userDetailsService) { this.jwtUtils = jwtUtils; this.userDetailsService = userDetailsService; } // 原有方法... }
修改SecurityConfig中的Bean定义:
@Bean public AuthTokenFilter authenticationJwtTokenFilter(JwtUtils jwtUtils, UserDetailsServiceImpl userDetailsService) { return new AuthTokenFilter(jwtUtils, userDetailsService); }
4. 统一配置Spring Security的CORS
替换原WebMvcConfigurer的CORS配置,确保Security优先处理:
@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http.csrf(AbstractHttpConfigurer::disable) .cors(cors -> cors.configurationSource(corsConfigurationSource())) .exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler)) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth.requestMatchers("/auth/**").permitAll() .requestMatchers("/api/**").authenticated() .anyRequest().authenticated() ).addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class) .authenticationProvider(authenticationProvider()) .build(); } @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(Collections.singletonList("*")); configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")); configuration.setAllowedHeaders(Collections.singletonList("*")); configuration.setAllowCredentials(false); configuration.setMaxAge(3600L); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); return source; }
删除原corsConfigurer Bean。
内容的提问来源于stack exchange,提问作者Hemanth S R
相关产品推荐
相关产品推荐

