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

Spring Security自定义认证通过却返回/login接口403问题求助

问题原因分析与解决方案

你遇到的认证成功但/login返回403的问题,核心原因有两个:


1. 原生UsernamePasswordAuthenticationFilter重复拦截/login请求

你通过addFilterAt(initialAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)将自定义过滤器放到了和原生过滤器相同的执行位置,但原生UsernamePasswordAuthenticationFilter默认仍会拦截/login路径。

当请求到达时:

  • 你的自定义过滤器先读取请求体完成认证,设置了SecurityContext
  • 随后原生过滤器尝试再次读取请求体,但此时输入流已经被耗尽,无法获取账号密码,触发认证失败,最终返回403

2. 请求体未重置导致后续组件读取失败

自定义过滤器中读取请求体字节流后,没有重置输入流,后续的过滤器或控制器无法再次读取请求内容,进一步加剧了认证异常的触发。


解决方案

方案一:直接移除原生UsernamePasswordAuthenticationFilter

既然已经用自定义过滤器处理/login认证,不需要保留原生过滤器,在Security配置中移除它:

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http.csrf(AbstractHttpConfigurer::disable);

    http.authenticationProvider(authenticationProvider)
            .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .addFilterAt(initialAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
            // 移除原生过滤器
            .removeFilter(UsernamePasswordAuthenticationFilter.class);

    http.authorizeHttpRequests(authorization ->
            authorization
                    .requestMatchers(HttpMethod.POST, "/login").permitAll()
                    .anyRequest().authenticated());

    return http.build();
}

方案二:重置请求输入流(兼容后续组件读取)

如果需要保留原生过滤器或让后续控制器能读取请求体,在自定义过滤器中重置输入流:

@Override
protected void doFilterInternal(
        HttpServletRequest request,
        HttpServletResponse response,
        FilterChain filterChain
) throws ServletException, IOException {
    byte[] body = StreamUtils.copyToByteArray(request.getInputStream());
    
    // 包装请求,重置输入流
    HttpServletRequest wrappedRequest = new HttpServletRequestWrapper(request) {
        @Override
        public ServletInputStream getInputStream() throws IOException {
            return new ByteArrayInputStream(body);
        }

        @Override
        public BufferedReader getReader() throws IOException {
            return new BufferedReader(new InputStreamReader(getInputStream()));
        }
    };

    Map<String, Object> jsonRequest = new ObjectMapper().readValue(body, Map.class);
    String email = jsonRequest.get("email").toString();
    String password = jsonRequest.get("password").toString();

    UsernamePasswordAuthenticationToken authentication =
            new UsernamePasswordAuthenticationToken(email, password);

    authenticationManager.authenticate(authentication);

    filterChain.doFilter(wrappedRequest, response);
}

方案三:修改原生过滤器的拦截路径

如果需要保留原生过滤器处理其他登录路径,修改其拦截URL避免冲突:

@Bean
public UsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter(AuthenticationManager authenticationManager) {
    UsernamePasswordAuthenticationFilter filter = new UsernamePasswordAuthenticationFilter();
    // 将原生过滤器的拦截路径改为非/login的地址,比如/login-form
    filter.setFilterProcessesUrl("/login-form");
    filter.setAuthenticationManager(authenticationManager);
    return filter;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 08:13:25