Spring Boot中仅对/rest/**接口应用JWT认证的登录问题排查
问题分析与解决方案
你的问题核心在于全局添加的JWT过滤器和无状态会话配置干扰了原有formLogin的认证流程:
- 你设置了
sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS),而formLogin默认依赖Session保存认证状态,无状态配置会导致登录成功后无法维持用户会话。 - 虽然过滤器里通过
shouldNotFilter尝试跳过非/rest/**的请求,但全局添加过滤器的方式还是会让登录请求被过滤器处理,再加上无状态会话的限制,最终导致登录时直接返回401。
最优解决方案:拆分Security配置,隔离REST与Web认证流程
Spring Security支持多套HttpSecurity配置,通过@Order指定优先级,让REST接口和普通Web页面使用完全独立的认证规则,互不干扰。
1. 创建REST接口专用的Security配置
@Configuration @Order(1) // 优先级更高,优先匹配/rest/**路径 public class RestSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private JwtAuthenticationEntryPoint unauthorizedHandler; @Autowired private UserDetailsService userDetailsService; @Autowired private JwtTokenUtil jwtTokenUtil; @Value("${jwt.header}") private String tokenHeader; @Autowired public void configureAuthentication(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override protected void configure(HttpSecurity http) throws Exception { http .antMatcher("/rest/**") // 仅对/rest/**路径生效 .csrf().disable() .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .authorizeRequests() .anyRequest().authenticated(); // 仅在REST路径下添加JWT过滤器 JwtAuthorizationTokenFilter authenticationTokenFilter = new JwtAuthorizationTokenFilter(userDetailsService, jwtTokenUtil, tokenHeader); http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); } }
2. 保留原有Web页面的Security配置
@Configuration @Order(2) // 优先级更低,处理非/rest/**的所有请求 public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private Environment env; private String[] publicMatchers() { // 这里填写你的公共资源路径,比如静态文件、登录页等 return new String[]{ "/css/**", "/js/**", "/images/**", "/login" }; } @Override protected void configure(HttpSecurity http) throws Exception { final List<String> activeProfiles = Arrays.asList(env.getActiveProfiles()); if (activeProfiles.contains("dev")) { http.csrf().disable(); http.headers().frameOptions().disable(); } http .authorizeRequests() .antMatchers(publicMatchers()).permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .defaultSuccessUrl("/bonanza/list") .failureUrl("/login?error") .permitAll() .and() .logout() .permitAll(); } }
3. 简化JWT过滤器代码
现在过滤器只会作用于/rest/**路径,无需再做路径判断,删除相关逻辑:
@Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { logger.info("processing authentication for '{}'", request.getRequestURL()); final String requestHeader = request.getHeader(this.tokenHeader); String username = null; String authToken = null; if (requestHeader != null && requestHeader.startsWith("Bearer ")) { authToken = requestHeader.substring(7); try { username = jwtTokenUtil.getUsernameFromToken(authToken); } catch (IllegalArgumentException e) { logger.info("an error occurred during getting username from token", e); } catch (ExpiredJwtException e) { logger.info("the token is expired and not valid anymore", e); } } else { logger.info("couldn't find bearer string, will ignore the header"); } logger.info("checking authentication for user '{}'", username); if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { logger.info("security context was null, so authorizing user"); UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); if (jwtTokenUtil.validateToken(authToken, userDetails)) { UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); logger.info("authorized user '{}', setting security context", username); SecurityContextHolder.getContext().setAuthentication(authentication); } } chain.doFilter(request, response); } // 删除shouldNotFilter方法
为什么这样能解决问题?
- 拆分配置后,
/rest/**的请求会走无状态的JWT认证流程,普通Web请求走基于Session的formLogin流程,两者完全隔离,不会互相干扰。 - 登录请求不再经过JWT过滤器,formLogin可以正常创建Session并维持用户认证状态,解决了登录时的401问题。
内容的提问来源于stack exchange,提问作者Nunyet Calçada
相关产品推荐
相关产品推荐

