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

自定义认证过滤器认证成功仍返回403,如何适配Spring Security?

问题:自定义认证过滤器通过认证后仍返回403,如何让Spring Security识别认证状态?

我的SettingsAPIAuthFilter继承了OncePerRequestFilter,已经能成功完成第三方认证,但因为配置了.authorizeHttpRequests(auth -> auth.anyRequest().authenticated()),请求还是返回403。想问下该怎么调整,才能让Spring Security的认证校验识别到请求已经通过认证?

我的SecurityConfig代码

@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Autowired
private SettingsAPIAuthFilter settingsAPIAuthFilter;

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
    return httpSecurity
            .formLogin(AbstractHttpConfigurer::disable)
            .httpBasic(AbstractHttpConfigurer::disable)
            .addFilterBefore(settingsAPIAuthFilter, UsernamePasswordAuthenticationFilter.class)
            .cors(corsConfigurer -> corsConfigurer.configurationSource(request -> {
                CorsConfiguration corsConfiguration = new CorsConfiguration();
                corsConfiguration.setAllowedOrigins(List.of("*"));
                corsConfiguration.setAllowedMethods(List.of(HttpMethod.GET.name(), "POST", "PUT", "DELETE"));
                corsConfiguration.setAllowedHeaders(List.of("*"));
                return corsConfiguration;
            }))
            .authorizeHttpRequests(auth -> auth.anyRequest()
                    .authenticated())
            .build();

}
}

我的SettingsAPIAuthFilter代码

@Component
public class SettingsAPIAuthFilter extends OncePerRequestFilter {

private static final String API_AUTHORISE_REQ_ACTION_ID_QUERY_PARAM = "requestActionId";

private static final String API_AUTH_REQ_SESSION_ID_HEADER_NAME = "userSessionId";

public static final String MAIN_API_AUTHORISE_ENDPOINT = "/authorise";

private final AppEventHandler appEventHandler = new AppEventHandler();

private final WebClient mainApiWebClient;

public SettingsAPIAuthFilter(
        @Qualifier("mainApiWebClient") WebClient webClient
) {
    this.mainApiWebClient = webClient;
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    try {
        ResponseEntity authResponse = authoriseRequest(
                request.getHeader("userSessionId"),
                "123");

        if (authResponse.getStatusCode().value() == 200) {
            appEventHandler.info("Authorisation successful. Moving on...");
            filterChain.doFilter(request, response);
        }

        response.setStatus(authResponse.getStatusCode().value());
        response.setHeader("Content-Type", "application/json");
        response.getOutputStream().print((String) authResponse.getBody());

    } catch (Exception exception) {
        appEventHandler.error("Error occurred in SettingsAPIAuthFilter", exception);
    }
}

public ResponseEntity<?> authoriseRequest(
        String sessionId,
        String requestedActionId
) throws Exception {
    try {
        return mainApiWebClient.get()
                .uri(uriBuilder -> uriBuilder
                        .path(MAIN_API_AUTHORISE_ENDPOINT)
                        .queryParam(API_AUTHORISE_REQ_ACTION_ID_QUERY_PARAM, requestedActionId)
                        .build()
                )
                .header(API_AUTH_REQ_SESSION_ID_HEADER_NAME, sessionId)
                .retrieve()
                .onStatus(HttpStatusCode::isError, errorResponse -> Mono.empty())
                .toEntity(String.class)
                .block();

    } catch (Exception exception) {
        appEventHandler.error("Exception occurred in authorising request : ", exception);
        throw new Exception("Exception occurred in authorising request : ", exception);
    }
}
}

解决办法

问题根源

你的过滤器虽然完成了第三方认证,但没把认证信息存入Spring Security的SecurityContext,后续的anyRequest().authenticated()校验根本不知道请求已经通过认证,所以直接返回403。

不需要更换过滤器父类,只需补充这一步

继承OncePerRequestFilter完全没问题,不用换其他父类。你要做的就是在认证成功后,把用户的认证信息封装成Authentication对象,存入SecurityContextHolder。

修改SettingsAPIAuthFilter的doFilterInternal方法:

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    try {
        ResponseEntity authResponse = authoriseRequest(
                request.getHeader("userSessionId"),
                "123");

        if (authResponse.getStatusCode().value() == 200) {
            appEventHandler.info("Authorisation successful. Moving on...");
            
            // 关键步骤:构造认证对象并放入安全上下文
            // 这里可以根据第三方返回的实际内容,替换成真实的用户信息和权限
            Authentication auth = new UsernamePasswordAuthenticationToken(
                    "用户唯一标识", // 比如从authResponse解析出的用户ID/username
                    null,         // 凭证信息,不需要的话设为null
                    Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")) // 用户权限,按需调整
            );
            SecurityContextHolder.getContext().setAuthentication(auth);
            
            // 放行请求
            filterChain.doFilter(request, response);
            
            // 清理上下文,避免线程池复用导致的上下文污染
            SecurityContextHolder.clearContext();
            return; // 直接返回,不要执行后面的响应设置
        }

        response.setStatus(authResponse.getStatusCode().value());
        response.setHeader("Content-Type", "application/json");
        response.getOutputStream().print((String) authResponse.getBody());

    } catch (Exception exception) {
        appEventHandler.error("Error occurred in SettingsAPIAuthFilter", exception);
        // 异常时返回500错误
        response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
        response.getOutputStream().print("{\"error\":\"认证过程出现异常\"}");
    }
}

额外注意点

  1. 权限配置:如果你的系统需要权限控制,要从第三方认证接口返回的内容里解析出用户的实际权限,替换示例中的ROLE_USER。
  2. 线程安全:SecurityContextHolder默认用ThreadLocal存上下文,所以在请求处理完后一定要调用clearContext(),防止线程池复用上下文导致的问题。
  3. 异常处理:原代码里异常时没设置响应状态,建议补充,避免请求无响应或返回默认错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 21:06:02