Spring Boot Security:Cookie传JWT替代Authorization头遇认证失败
问题背景
正在从Spring Security会话认证迁移到JWT认证,为避免XSS风险选择将JWT存储在HttpOnly Cookie中,而非localStorage。但集成测试时持续报错,提示找不到Bearer token:
main] .s.r.w.a.BearerTokenAuthenticationFilter : Did not process request since did not find bearer token
核心问题是默认的BearerTokenAuthenticationFilter只会从Authorization请求头读取Bearer格式的token,无法识别Cookie中的JWT。
问题分析
你之前尝试的JwtFilter存在两个关键问题:
- 提取Cookie值错误:代码中通过
map(Cookie::getName)拿到的是Cookie名称而非token值,导致添加到Header的是Cookie名而非实际JWT。 - 过滤器逻辑冗余:无需对所有请求都包装Request,仅当存在目标Cookie时才需要添加Header。
解决方案
提供两种可行方案,推荐第二种更贴合Spring Security规范的实现:
方案一:修复自定义Jwt过滤器
调整过滤器逻辑,正确提取Cookie中的JWT并注入到Authorization头,同时对无需认证的请求直接放行:
@Component @Slf4j public class JwtFilter extends OncePerRequestFilter { @Value("${your.jwt.cookie.name}") // 替换为你的JWT Cookie名称配置 private String JWT_COOKIE_NAME; @Override protected void doFilterInternal( @NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain ) throws ServletException, IOException { // 对公开路径(如登录)直接放行,无需处理 if (isPublicRequest(request)) { filterChain.doFilter(request, response); return; } Cookie[] cookies = request.getCookies(); if (cookies != null) { Optional<Cookie> jwtCookie = Arrays.stream(cookies) .filter(cookie -> JWT_COOKIE_NAME.equals(cookie.getName())) .findFirst(); jwtCookie.ifPresent(cookie -> { // 包装请求,添加Authorization头 HeaderMapRequestWrapper requestWrapper = new HeaderMapRequestWrapper(request); requestWrapper.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + cookie.getValue()); try { filterChain.doFilter(requestWrapper, response); } catch (IOException | ServletException e) { log.error("处理JWT Cookie时出错", e); } return; }); } // 未找到Cookie时继续执行,交由后续过滤器处理认证失败逻辑 filterChain.doFilter(request, response); } // 判断是否为公开请求,可复用你的publicRoutes()逻辑 private boolean isPublicRequest(HttpServletRequest request) { String path = request.getRequestURI(); return path.startsWith("/login") || // 根据实际公开路径调整 path.startsWith("/public"); } }
然后在SecurityFilterChain中启用过滤器,确保它在BearerTokenAuthenticationFilter之前执行:
@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http .csrf(csrf -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())) .cors(Customizer.withDefaults()) .authorizeHttpRequests(auth -> { auth.requestMatchers(publicRoutes()).permitAll(); auth.anyRequest().authenticated(); }) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults())) .exceptionHandling(ex -> ex.authenticationEntryPoint(this.authEntryPoint)) .addFilterBefore(jwtFilter, BearerTokenAuthenticationFilter.class) // 添加过滤器 .logout(out -> out .logoutUrl("/logout") .deleteCookies(COOKIE_NAME, LOGGEDSESSION) .logoutSuccessHandler((request, response, authentication) -> SecurityContextHolder.clearContext() ) ) .build(); }
方案二:自定义JWT Token解析器(推荐)
通过Spring Security提供的JwtBearerTokenResolver扩展,直接从Cookie读取JWT,无需手动修改请求头,更符合框架规范:
@Bean public JwtBearerTokenResolver jwtBearerTokenResolver() { JwtBearerTokenResolver resolver = new JwtBearerTokenResolver(); // 自定义从Cookie提取token的逻辑 resolver.setBearerTokenExtractor(request -> { Cookie[] cookies = request.getCookies(); if (cookies != null) { return Arrays.stream(cookies) .filter(cookie -> COOKIE_NAME.equals(cookie.getName())) // COOKIE_NAME为你的JWT Cookie名 .map(Cookie::getValue) .findFirst() .orElse(null); } return null; }); return resolver; }
然后在SecurityFilterChain中配置该解析器:
@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http .csrf(csrf -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())) .cors(Customizer.withDefaults()) .authorizeHttpRequests(auth -> { auth.requestMatchers(publicRoutes()).permitAll(); auth.anyRequest().authenticated(); }) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .oauth2ResourceServer(oauth2 -> oauth2 .jwt(jwt -> jwt.bearerTokenResolver(jwtBearerTokenResolver())) // 配置自定义解析器 ) .exceptionHandling(ex -> ex.authenticationEntryPoint(this.authEntryPoint)) .logout(out -> out .logoutUrl("/logout") .deleteCookies(COOKIE_NAME, LOGGEDSESSION) .logoutSuccessHandler((request, response, authentication) -> SecurityContextHolder.clearContext() ) ) .build(); }
集成测试验证
你的现有测试代码逻辑正确,只需确保COOKIE_NAME与实际JWT Cookie名称一致,运行测试即可验证认证是否正常通过。
内容的提问来源于stack exchange,提问作者iTchTheRightSpot
相关产品推荐
相关产品推荐

