Spring API集成JWT后登录正常但GET请求失效,注释CORS配置与@RequiredArgsConstructor后现象反转的问题排查
我最近基于Spring框架开发API,集成JWT认证后登录功能正常,但所有GET请求都没法正常工作。有意思的是,如果我注释掉SecurityConfig类里的CorsConfigurationSource方法和Lombok的@RequiredArgsConstructor注解,登录功能会挂,但GET请求又能正常跑了。下面是我的三个核心类代码,麻烦帮忙找找问题出在哪?
SecurityConfig类代码
import org.alterdata.shopback.app.security.AuthenticationFilter; import org.alterdata.shopback.app.security.AuthorizationFilter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import lombok.RequiredArgsConstructor; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import java.util.Arrays; @Configuration @EnableWebSecurity @RequiredArgsConstructor public class SecurityConfig extends WebSecurityConfigurerAdapter{ @Autowired UserDetailsService userDetailsService; @Autowired BCryptPasswordEncoder bCryptPasswordEncoder; @Override protected void configure(HttpSecurity http) throws Exception { AuthenticationFilter authenticationFilter = new AuthenticationFilter(authenticationManagerBean()); http.cors().and().csrf().disable(); http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); http.authorizeRequests().antMatchers("/login").permitAll() .antMatchers("/cadastrar/**").hasAnyAuthority("ADMIN") .antMatchers("/cadastro/**").hasAnyAuthority("ADMIN") .anyRequest().authenticated(); http.addFilter(authenticationFilter); http.addFilterBefore(new AuthorizationFilter(), UsernamePasswordAuthenticationFilter.class); } @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder); } @Bean @Override public AuthenticationManager authenticationManager()throws Exception{ return super.authenticationManager(); } @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(Arrays.asList("http://localhost:3000")); configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE","OPTIONS", "HEAD")); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); return source; } }
AuthorizationFilter类代码
import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Arrays; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.auth0.jwt.exceptions.JWTVerificationException; import io.jsonwebtoken.JwtException; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpHeaders; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.filter.OncePerRequestFilter; import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.JWTVerifier; import com.fasterxml.jackson.databind.ObjectMapper; import net.bytebuddy.implementation.bind.annotation.IgnoreForBinding.Verifier; import static org.springframework.util.MimeTypeUtils.APPLICATION_JSON_VALUE; @Slf4j public class AuthorizationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { if(request.getServletPath().equals("/login")) { filterChain.doFilter(request, response); }else { String authorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION); if(authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) { try { String token = authorizationHeader.substring("Bearer ".length()); Algorithm algorithm = Algorithm.HMAC256("segredinho".getBytes()); JWTVerifier jwtVerifier = JWT.require(algorithm).build(); DecodedJWT decodedJWT = jwtVerifier.verify(token); String user = decodedJWT.getSubject(); String [] roles = decodedJWT.getClaim("roles").asArray(String.class); Collection<SimpleGrantedAuthority> authorities = new ArrayList<>(); Arrays.stream(roles).forEach(role ->{ authorities.add(new SimpleGrantedAuthority(role)); }); UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user,null, authorities); SecurityContextHolder.getContext().setAuthentication(authenticationToken); filterChain.doFilter(request, response); }catch(Exception e){ log.error("erro ao realizar o login! {}", e.getMessage()); response.setHeader("erro", e.getMessage()); response.setStatus(401); Map<String, String> error = new HashMap<>(); error.put("mensagem de erro", e.getMessage()); response.setContentType(APPLICATION_JSON_VALUE); new ObjectMapper().writeValue(response.getOutputStream(), error); } }else { filterChain.doFilter(request, response); } } } }
AuthenticationFilter类代码
import java.io.IOException; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.http.MediaType; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.User; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import org.springframework.web.bind.annotation.CrossOrigin; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; @CrossOrigin(origins = {"*"}) public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter { private final AuthenticationManager authenticationManager; public AuthenticationFilter (AuthenticationManager authenticationManager) { this.authenticationManager = authenticationManager; } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { try{ String nome = request.getParameter("nome"); String senha = request.getParameter("senha"); UsernamePasswordAuthenticationToken uspsToken = new UsernamePasswordAuthenticationToken(nome, senha); //response.setHeader("teste", String.valueOf(uspsToken)); return authenticationManager.authenticate(uspsToken); }catch (Exception e){ throw new RuntimeException(); } } // @Override // public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) // throws AuthenticationException { // try{ // UserPasswordAuthRequest userPasswordAuthRequest = new ObjectMapper() // .readValue(request.getInputStream(), UserPasswordAuthRequest.class); // // Authentication authentication = new UsernamePasswordAuthenticationToken( // userPasswordAuthRequest.getNome(), // userPasswordAuthRequest.getSenha() // ); // return authenticationManager.authenticate(authentication); // }catch (IOException e){ // throw new RuntimeException(); // } // } @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { User user = (User) authResult.getPrincipal(); Algorithm algorithm = Algorithm.HMAC256("segredinho".getBytes()); String tokenAcesso = JWT.create().withSubject(user.getUsername()) .withExpiresAt(new Date(System.currentTimeMillis() + 10*60*1000*60)) .withIssuer(request.getRequestURL() .toString()).withClaim("roles", user.getAuthorities() .stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList())) .sign(algorithm); String tokenRefresh = JWT.create().withSubject(user.getUsername()) .withExpiresAt(new Date(System.currentTimeMillis() + 10*60*1000*60)) .withIssuer(request.getRequestURL() .toString()).withClaim("roles", user.getAuthorities() .stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList())) .sign(algorithm); Map<String, String> tokens = new HashMap<>(); tokens.put("tokenacesso", tokenAcesso); tokens.put("tokenrefresh", tokenRefresh); response.setContentType(APPLICATION_JSON_VALUE); new ObjectMapper().writeValue(response.getOutputStream(),tokens); } }
问题根源分析
我仔细捋了你的代码,问题主要出在依赖注入冲突、CORS配置逻辑、以及过滤器的异常处理这几个点上:
@RequiredArgsConstructor和@Autowired的冲突
你在SecurityConfig里同时混用了@RequiredArgsConstructor和@Autowired注入UserDetailsService和BCryptPasswordEncoder。@RequiredArgsConstructor会生成基于final字段的构造函数,但你的这两个字段不是final的,再加上@Autowired的存在,会让Spring的依赖注入逻辑混乱,间接导致过滤器初始化异常,最终影响GET请求的认证流程。CORS配置的缺失与冲突
你的CORS配置里只允许了请求方法,但没有明确允许Authorization请求头——这是JWT认证必须的头信息。另外,你在AuthenticationFilter上加了@CrossOrigin注解,这和全局CORS配置会产生冲突,因为Spring Security的CORS过滤器优先级更高,局部注解会被忽略。AuthorizationFilter的异常处理过于宽泛
你用catch(Exception e)捕获了所有异常,包括非JWT相关的错误(比如CORS预检请求的异常),这会导致合法的GET请求被错误地返回401状态码。AuthenticationFilter的参数解析局限性
当前的attemptAuthentication方法只支持form-data/x-www-form-urlencoded格式的参数,如果前端用JSON格式传递用户名密码,会直接登录失败,这也是你注释掉JSON解析代码的潜在问题。
修复步骤
1. 修复依赖注入冲突
把SecurityConfig里的@Autowired去掉,改用@RequiredArgsConstructor管理final字段的注入:
@Configuration @EnableWebSecurity @RequiredArgsConstructor public class SecurityConfig extends WebSecurityConfigurerAdapter{ private final UserDetailsService userDetailsService; private final BCryptPasswordEncoder bCryptPasswordEncoder; // 其余代码保持不变 }
2. 完善全局CORS配置并移除局部注解
删掉AuthenticationFilter上的@CrossOrigin注解,同时在CORS配置里添加允许的请求头:
@Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(Arrays.asList("http://localhost:3000")); configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE","OPTIONS", "HEAD")); configuration.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type")); // 新增允许JWT所需的头 configuration.setAllowCredentials(true); // 如果前端需要携带Cookie可开启,按需调整 UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); return source; }
3. 优化AuthorizationFilter的异常处理
精准捕获JWT验证异常,避免误拦截合法请求:
catch(JWTVerificationException e){ log.error("JWT验证失败! {}", e.getMessage()); response.setHeader("erro", e.getMessage()); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); Map<String, String> error = new HashMap<>(); error.put("mensagem de erro", "无效的JWT令牌,请重新登录"); response.setContentType(APPLICATION_JSON_VALUE); new ObjectMapper().writeValue(response.getOutputStream(), error); } catch(Exception e){ log.error("请求处理出错! {}", e.getMessage()); // 非JWT异常返回500,避免误判为认证失败 response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); Map<String, String> error = new HashMap<>(); error.put("mensagem de erro", "服务器内部错误"); response.setContentType(APPLICATION_JSON_VALUE); new ObjectMapper().writeValue(response.getOutputStream(), error); }
4. 恢复JSON格式的登录参数解析
把AuthenticationFilter里注释掉的JSON解析代码恢复,同时创建对应的请求实体类:
// 替换原attemptAuthentication方法 @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { try{ UserPasswordAuthRequest userPasswordAuthRequest = new ObjectMapper() .readValue(request.getInputStream(), UserPasswordAuthRequest.class); Authentication authentication = new UsernamePasswordAuthenticationToken( userPasswordAuthRequest.getNome(), userPasswordAuthRequest.getSenha() ); return authenticationManager.authenticate(authentication); }catch (IOException e){ throw new RuntimeException("无法解析登录请求体", e); } } // 新增请求实体类 public class UserPasswordAuthRequest { private String nome; private String senha; // getter和setter public String getNome() { return nome; } public void setNome(String nome) { this.nome = nome; } public String getSenha() { return senha; } public void setSenha(String senha) { this.senha = senha; } }
验证修复
做完以上修改后重启应用,依次测试:
- 登录接口:用JSON格式传递用户名密码,确认能正常获取JWT令牌
- 带令牌的GET请求:确认能正常返回数据
- 不带令牌的GET请求:确认返回401状态码(符合你的权限配置)
内容的提问来源于stack exchange,提问作者Guilherme Gusman

