Spring Security登录返回403?自定义认证过滤器无法授权排查
问题排查与修复方案
你的核心问题出在自定义过滤器中构建的Authentication对象未被标记为已认证,且未正确关联权限信息,导致Spring Security仍认为请求未通过授权,返回403。以下是具体问题点和修复步骤:
1. 核心问题:Authentication对象未完成认证流程
你在CustomFilter中直接创建UsernamePasswordAuthenticationToken并设置到SecurityContext,但这个token默认是未认证状态(isAuthenticated()返回false)。Spring Security会拒绝未认证的Authentication对象,即使你手动放入上下文。
另外,你传入的是用户名和密码字符串,而非UserDetails实例,也没有赋予任何权限,这会导致后续授权检查失败。
2. 修复步骤
步骤1:修正CustomFilter的认证逻辑
推荐通过AuthenticationManager走标准认证流程,复用你的UserDetailsServiceImpl和密码编码器配置:
@Component @RequiredArgsConstructor public class CustomFilter extends OncePerRequestFilter { private final AuthenticationManager authenticationManager; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String headerAuth = request.getHeader("Authorization"); if (StringUtils.hasText(headerAuth) && headerAuth.equals("abc")) { // 1. 创建未认证的token UsernamePasswordAuthenticationToken unauthenticatedToken = new UsernamePasswordAuthenticationToken("dad", "4343"); // 2. 通过AuthenticationManager完成认证,自动调用UserDetailsServiceImpl和密码编码器 Authentication authenticatedToken = authenticationManager.authenticate(unauthenticatedToken); // 3. 将已认证的token放入SecurityContext SecurityContextHolder.getContext().setAuthentication(authenticatedToken); System.out.println("认证成功"); } filterChain.doFilter(request, response); } }
如果你的场景不需要密码校验(比如自定义令牌验证),可以直接构建已认证的token:
@Component @RequiredArgsConstructor public class CustomFilter extends OncePerRequestFilter { private final UserDetailsService userDetailsService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String headerAuth = request.getHeader("Authorization"); if (StringUtils.hasText(headerAuth) && headerAuth.equals("abc")) { // 从UserDetailsService获取用户信息 UserDetails userDetails = userDetailsService.loadUserByUsername("dad"); // 创建已认证的token,第三个参数为权限集合(空集合也需传入) UsernamePasswordAuthenticationToken authenticatedToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authenticatedToken); System.out.println("认证成功"); } filterChain.doFilter(request, response); } }
步骤2:修正SecurityConfiguration中的过滤器注入
你当前手动new CustomFilter(),但CustomFilter依赖Spring管理的Bean,应该通过注入方式引入:
@Configuration @EnableMethodSecurity @EnableWebSecurity @RequiredArgsConstructor public class SecurityConfiguration { private final UserDetailsService userDetailsService; private final CustomFilter customFilter; // 注入CustomFilter @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http .csrf(AbstractHttpConfigurer::disable) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> { auth.requestMatchers("/api/v1/dfsdfs").permitAll() .anyRequest().authenticated(); }) .authenticationProvider(authenticationProvider()) .addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class) // 使用注入的实例 .build(); } // 其他Bean配置不变 }
步骤3:修正UserDetailsServiceImpl的密码编码
你的UserDetailsServiceImpl返回明文密码,但密码编码器用的是BCryptPasswordEncoder,会导致认证失败。先生成BCrypt加密后的密码:
public class PasswordEncoderTest { public static void main(String[] args) { BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); System.out.println(encoder.encode("4343")); // 输出类似 $2a$10... 的加密字符串 } }
再修改UserDetailsServiceImpl:
@Component public class UserDetailsServiceImpl implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 替换为上面生成的BCrypt加密密码 return new User("dad", "$2a$10$Z8HxQx0eUeV7X7X7X7X7X7X7X7X7X7X7X7X7X7X7X7X7X7X7", new ArrayList<>()); } }
额外注意点
- 即使不需要权限控制,也必须为已认证token传入非空的权限集合(比如空ArrayList),否则授权检查会失败。
- 生产环境禁止硬编码用户名密码,建议从数据库或配置中心读取。
内容的提问来源于stack exchange,提问作者Andrey Picasso
相关产品推荐
相关产品推荐

