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

OAuth2登录后调用接口返回403无权限问题求助

Spring Security 5登录后403 Unauthorized问题解决

问题描述

使用Spring Security 5实现登录功能后,登录成功调用其他接口始终返回403 Unauthorized。自定义的TokenAuthenticationFilter的doFilterInternal方法仅在登录请求时触发,后续请求不会执行该过滤器逻辑;后续请求会进入org.springframework.security.web.session.SessionManagementFilter#doFilter,但此时安全上下文、认证信息及会话均不存在。

相关配置代码

SecurityConfig.java

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(
    securedEnabled = true,
    jsr250Enabled = true,
    prePostEnabled = true
)
public class SecurityConfig {

    @Autowired
    private CustomUserDetailsService customUserDetailsService;

    @Autowired
    private CustomOAuth2UserService customOAuth2UserService;

    @Autowired
    private OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler;

    @Autowired
    private OAuth2AuthenticationFailureHandler oAuth2AuthenticationFailureHandler;

    @Autowired
    private HttpCookieOAuth2AuthorizationRequestRepository httpCookieOAuth2AuthorizationRequestRepository;

    @Bean
    public TokenAuthenticationFilter tokenAuthenticationFilter() {
        return new TokenAuthenticationFilter();
    }

    /*
      默认Spring OAuth2使用HttpSessionOAuth2AuthorizationRequestRepository保存授权请求,
      但因为服务是无状态的,不能存在会话中,所以改用Base64编码的Cookie存储
    */
    @Bean
    public HttpCookieOAuth2AuthorizationRequestRepository cookieAuthorizationRequestRepository() {
        return new HttpCookieOAuth2AuthorizationRequestRepository();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManager(
        AuthenticationConfiguration authConfig) throws Exception {
        return authConfig.getAuthenticationManager();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        http
            .cors()
                .and()
            .csrf().disable()
            .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
            .authorizeHttpRequests()
                .requestMatchers("/auth/login").permitAll()
                .anyRequest().authenticated();

        return http.build();
    }
}

HttpCookieOAuth2AuthorizationRequestRepository.java

@Component
public class HttpCookieOAuth2AuthorizationRequestRepository implements AuthorizationRequestRepository<OAuth2AuthorizationRequest> {
    public static final String OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME = "oauth2_auth_request";
    public static final String REDIRECT_URI_PARAM_COOKIE_NAME = "redirect_uri";
    private static final int cookieExpireSeconds = 180;

    @Override
    public OAuth2AuthorizationRequest loadAuthorizationRequest(HttpServletRequest request) {
        return CookieUtils.getCookie(request, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME)
                .map(cookie -> CookieUtils.deserialize(cookie, OAuth2AuthorizationRequest.class))
                .orElse(null);
    }

    @Override
    public void saveAuthorizationRequest(OAuth2AuthorizationRequest authorizationRequest, HttpServletRequest request, HttpServletResponse response) {
        if (authorizationRequest == null) {
            CookieUtils.deleteCookie(request, response, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME);
            CookieUtils.deleteCookie(request, response, REDIRECT_URI_PARAM_COOKIE_NAME);
            return;
        }

        CookieUtils.addCookie(response, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME, CookieUtils.serialize(authorizationRequest), cookieExpireSeconds);
        String redirectUriAfterLogin = request.getParameter(REDIRECT_URI_PARAM_COOKIE_NAME);
        if (StringUtils.isNotBlank(redirectUriAfterLogin)) {
            CookieUtils.addCookie(response, REDIRECT_URI_PARAM_COOKIE_NAME, redirectUriAfterLogin, cookieExpireSeconds);
        }
    }

    @Override
    public OAuth2AuthorizationRequest removeAuthorizationRequest(HttpServletRequest request, HttpServletResponse response) {
        return this.loadAuthorizationRequest(request);
    }

//    @Override
//    public OAuth2AuthorizationRequest removeAuthorizationRequest(HttpServletRequest request) {
//        return this.loadAuthorizationRequest(request);
//    }

    public void removeAuthorizationRequestCookies(HttpServletRequest request, HttpServletResponse response) {
        CookieUtils.deleteCookie(request, response, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME);
        CookieUtils.deleteCookie(request, response, REDIRECT_URI_PARAM_COOKIE_NAME);
    }
}

问题原因

  1. 自定义Token过滤器未加入过滤器链:当前SecurityFilterChain配置仅定义了授权规则,但未将TokenAuthenticationFilter添加到Spring Security的过滤器链中。导致登录后的请求无法被token校验逻辑处理,无法构建安全上下文。
  2. 无状态会话配置要求:因为设置了SessionCreationPolicy.STATELESS,服务不会通过会话保存认证状态,必须依赖TokenAuthenticationFilter在每次请求时校验token并注入认证信息,否则请求会因无认证信息返回403。

解决方案

修改SecurityConfig中的filterChain方法,将自定义的TokenAuthenticationFilter添加到过滤器链中,通常放在UsernamePasswordAuthenticationFilter之前,确保token校验在用户名密码认证逻辑之后、其他授权逻辑之前执行。

修改后的filterChain代码:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

    http
        .cors()
            .and()
        .csrf().disable()
        .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
        .authorizeHttpRequests()
            .requestMatchers("/auth/login").permitAll()
            .anyRequest().authenticated()
            .and()
        // 添加自定义Token过滤器,放在UsernamePasswordAuthenticationFilter之前
        .addFilterBefore(tokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);

    return http.build();
}

额外检查点

  • 确保TokenAuthenticationFilter继承自OncePerRequestFilter,保证每个请求都会触发一次过滤器逻辑。
  • 确认TokenAuthenticationFilter的doFilterInternal方法正确实现了token提取、校验及认证信息注入逻辑,示例如下:
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    // 从请求头或Cookie中获取token,示例从Authorization头获取Bearer token
    String authorizationHeader = request.getHeader("Authorization");
    String token = null;
    if (StringUtils.hasText(authorizationHeader) && authorizationHeader.startsWith("Bearer ")) {
        token = authorizationHeader.substring(7);
    }

    // 校验token并设置安全上下文
    if (StringUtils.hasText(token) && tokenValidator.validateToken(token)) {
        Authentication authentication = tokenProvider.getAuthentication(token);
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }

    filterChain.doFilter(request, response);
}
  • 登录成功后需正确返回token,后续请求需携带该token(如放在Authorization: Bearer <token>请求头中)。

内容的提问来源于stack exchange,提问作者John

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 23:25:17