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

Spring Security已认证SecurityContext仍抛InsufficientAuthenticationException

JWT认证过滤器已设置Authentication,但请求仍抛InsufficientAuthenticationException问题排查

我实现了一个JWT请求认证过滤器,日志显示SecurityContext中的Authentication已设置为已认证状态,但请求始终无法到达目标控制器,仍抛出InsufficientAuthenticationException。

相关代码

JWTFilter.java

@RequiredArgsConstructor
@Slf4j
public class JWTFilter extends OncePerRequestFilter {

    private final JWTService jwtService;
    private final UserDetailsService userService;

    private Optional<String> getTokenFromHeader(HttpServletRequest request){
        final String authHeader = request.getHeader("Authorization");
        if(authHeader == null || !authHeader.startsWith("Bearer ")){
            return Optional.empty();
        }
        return Optional.of(authHeader.substring(7));
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                   FilterChain filterChain) throws ServletException, IOException {
        log.error("Running filter...");

        Optional<String> rawToken = getTokenFromHeader(request);

        if(rawToken.isEmpty()){
            filterChain.doFilter(request, response);
            return;
        }

        // TODO handle all the exception
        try {
            String token = rawToken.get();

            // Validate
            jwtService.validateToken(token);

            /*
            if(SecurityContextHolder.getContext().getAuthentication() != null){
                filterChain.doFilter(request, response);
                return;
            }*/

            String username = jwtService.getUserId(token);
            log.info("Username " + username);

            UserDetails userDetails = userService.loadUserByUsername(username);

            log.info("userdetails " + userDetails.toString());

            if(userDetails.getUsername() == null){
                filterChain.doFilter(request, response);
                return;
            }

            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);

            log.error("Doing last full auth is good " + SecurityContextHolder.getContext().getAuthentication().isAuthenticated());
            filterChain.doFilter(request, response);

            log.error("AFTER DO FILTER CHECK  " + SecurityContextHolder.getContext().getAuthentication().isAuthenticated());

        } catch (JwtException e) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.getWriter().write("Invalid token " + e.getLocalizedMessage());
        }
    }
}

SecurityConfig.java

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private final AuthorizationEntryPoint unauthorizedHandler;
    private final JWTService jwtService;
    private final UserRepository userRepository;
    private final UserDetailsService userService;

    @Bean
    public SecurityFilterChain provideSecurityFilterChain(HttpSecurity http) throws Exception {

        http.csrf(AbstractHttpConfigurer::disable);
        http.cors(AbstractHttpConfigurer::disable);

        http.authorizeHttpRequests((requests) ->
                requests
                        .requestMatchers("/auth/**").permitAll()
                        .requestMatchers("/swagger-ui/**").permitAll()
                        .requestMatchers("/v3/**").permitAll()
                        .anyRequest().authenticated()
        )
        .authenticationManager(provideAuthManager(http));

        http.sessionManagement(sessionManagementConfigurer ->
                sessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        );

        http.authenticationProvider(provideAuthenticationProvider());
        http.addFilterBefore(new JWTFilter(jwtService, userService), UsernamePasswordAuthenticationFilter.class);
        http.exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler));

        return http.build();
    }

    @Bean
    public PasswordEncoder provideEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationProvider provideAuthenticationProvider(){
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(userService);
        authProvider.setPasswordEncoder(provideEncoder());
        return authProvider;
    }

    @Bean
    public AuthenticationManager provideAuthManager(HttpSecurity httpSecurity) throws Exception {
        AuthenticationManagerBuilder authenticationManagerBuilder = httpSecurity.getSharedObject(AuthenticationManagerBuilder.class);
        authenticationManagerBuilder.userDetailsService(userService).passwordEncoder(provideEncoder());
        return authenticationManagerBuilder.build();
    }
}

PostController.java

@RestController()
@RequestMapping("/blog_posts")
@Slf4j
public class PostController {

    private final PostsService postsService;

    public PostController(PostsService postsService){
        this.postsService = postsService;
    }

    @GetMapping("/info")
    public String getPosts(){
        return "Blog posts";
    }

    @GetMapping("/")
    public List<BlogPost> getBlogPosts(@RequestParam() Long limit, @RequestParam Long offset){
        return postsService.getBlogPosts(limit, offset);
    }

    @PostMapping("/")
    public BlogPost addBlogPost(@RequestParam AddBlogPostRequest request){
        log.error("Doing controller");

        Authentication authentication  = SecurityContextHolder.getContext().getAuthentication();
        User user = (User) authentication.getDetails();

        BlogPost post = new BlogPost();
        post.setTitle(request.getTitle());
        post.setBody(request.getBody());
        post.setUserId(user.getId());

        return postsService.addBlogPost(post);
    }
}

AppConfig.java

@Configuration
@RequiredArgsConstructor
public class AppConfig {

    private final UserRepository userRepository;

    @Bean
    public UserDetailsService provideUserDetailsService() throws Exception {
        return new UserDetailsService() {
            @Override
            public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
                return userRepository.findByUsername(username)
                        .orElseThrow(() -> new UsernameNotFoundException("User not found"));
            }
        };
    }
}

排查关键点

  • UserDetails实现有效性检查:确认你的User实体类是否正确实现了UserDetails接口,其中isAccountNonExpired()、isAccountNonLocked()、isCredentialsNonExpired()、isEnabled()必须全部返回true,否则即使Authentication标记为已认证,也会被判定为无效身份。同时检查getAuthorities()是否返回非空集合(即使是空权限集合,也要确保返回Collections.emptyList()而非null)。
  • 控制器类型转换错误:在PostController的addBlogPost方法中,你尝试将Authentication.getDetails()强转为User实体,但details属性存储的是WebAuthenticationDetails(请求IP、会话ID等信息),而非用户对象。正确的做法是通过authentication.getPrincipal()获取UserDetails实例,再强转为你的User实体。
  • AuthenticationManager配置冲突:你的SecurityConfig中同时配置了authenticationProvider和手动构建authenticationManager,这可能导致Spring Security的认证流程冲突。建议移除手动构建authenticationManager的代码,Spring Security会自动从已配置的AuthenticationProvider生成AuthenticationManager。
  • 异常捕获范围:JWTFilter中的catch仅捕获JwtException,但InsufficientAuthenticationException会绕过该捕获进入Spring Security的全局异常处理。检查AFTER DO FILTER CHECK日志是否打印:如果未打印,说明filterChain.doFilter(request, response)执行时已抛出异常,需排查后续过滤器或认证逻辑的问题。
  • SecurityContext线程安全性:确认SecurityContextHolder的默认策略是否为MODE_THREADLOCAL(默认是),避免多线程环境下上下文被覆盖。如果是异步请求,需额外配置上下文传递,但当前场景是同步请求,此问题概率较低。

内容的提问来源于stack exchange,提问作者Арсений Горягин

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 23:10:55