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,提问作者Арсений Горягин
相关产品推荐
相关产品推荐

