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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 12:32:47