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
相关产品推荐
相关产品推荐

