You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring API集成JWT后登录正常但GET请求失效,注释CORS配置与@RequiredArgsConstructor后现象反转的问题排查

问题排查:Spring JWT集成后GET请求失效的问题

我最近基于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配置逻辑、以及过滤器的异常处理这几个点上:

  1. @RequiredArgsConstructor和@Autowired的冲突
    你在SecurityConfig里同时混用了@RequiredArgsConstructor和@Autowired注入UserDetailsService和BCryptPasswordEncoder。@RequiredArgsConstructor会生成基于final字段的构造函数,但你的这两个字段不是final的,再加上@Autowired的存在,会让Spring的依赖注入逻辑混乱,间接导致过滤器初始化异常,最终影响GET请求的认证流程。

  2. CORS配置的缺失与冲突
    你的CORS配置里只允许了请求方法,但没有明确允许Authorization请求头——这是JWT认证必须的头信息。另外,你在AuthenticationFilter上加了@CrossOrigin注解,这和全局CORS配置会产生冲突,因为Spring Security的CORS过滤器优先级更高,局部注解会被忽略。

  3. AuthorizationFilter的异常处理过于宽泛
    你用catch(Exception e)捕获了所有异常,包括非JWT相关的错误(比如CORS预检请求的异常),这会导致合法的GET请求被错误地返回401状态码。

  4. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 12:22:41