Spring Boot中Cookie认证触发自定义AuthenticationProvider的问题及疑问
场景说明
用户实现的认证流程为:用户通过一次性临时URL获取Cookie,随后使用该Cookie作为认证令牌访问受保护资源。已实现CookieAuthenticationFilter、UserAuthProvider和SecurityConfig三个组件,但UserAuthProvider的authenticate方法未被触发,使用版本为Spring Boot 3.3.1。以下针对三个疑问逐一解答:
1. 遗漏的配置或代码
当前代码中,CookieAuthenticationFilter直接将PreAuthenticatedAuthenticationToken放入SecurityContext,但Spring Security不会自动触发AuthenticationProvider的认证逻辑,需要显式调用AuthenticationManager的authenticate方法来完成认证流程。具体修改如下:
修改CookieAuthenticationFilter,注入并调用AuthenticationManager
public class CookieAuthenticationFilter extends OncePerRequestFilter { private final AuthenticationManager authenticationManager; // 构造方法注入AuthenticationManager public CookieAuthenticationFilter(AuthenticationManager authenticationManager) { this.authenticationManager = authenticationManager; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { Cookie cookie = Stream.of(Optional.ofNullable(request.getCookies()) .orElse(new Cookie[0])) .filter(entry -> "token".equals(entry.getName())) .findFirst() .orElse(null); // 创建认证请求令牌 Authentication authRequest = new PreAuthenticatedAuthenticationToken( cookie != null ? cookie.getValue() : "", null); // 触发认证流程,调用UserAuthProvider的authenticate方法 Authentication authResult = authenticationManager.authenticate(authRequest); // 将认证结果存入安全上下文 SecurityContextHolder.getContext().setAuthentication(authResult); filterChain.doFilter(request, response); } }
修改SecurityConfig,传递AuthenticationManager给过滤器
@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public AuthenticationManager authenticationManager(HttpSecurity http, UserAuthProvider userAuthProvider) throws Exception { AuthenticationManagerBuilder builder = http.getSharedObject(AuthenticationManagerBuilder.class); builder.authenticationProvider(userAuthProvider); return builder.build(); } @Bean // 注入AuthenticationManager,移除无需使用的HttpSession参数 public SecurityFilterChain securityFilterChain(HttpSecurity security, AuthenticationManager authenticationManager) throws Exception { security .csrf(customizer -> customizer.disable()) // 传递AuthenticationManager给过滤器 .addFilterBefore(new CookieAuthenticationFilter(authenticationManager), BasicAuthenticationFilter.class) .authorizeHttpRequests(configurer -> configurer .requestMatchers("/*").authenticated() .anyRequest().permitAll()) .sessionManagement(management -> management.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); return security.build(); } }
优化UserAuthProvider的supports方法(可选但推荐)
将supports方法改为仅支持PreAuthenticatedAuthenticationToken,避免处理无关的认证类型:
@Override public boolean supports(Class<?> authentication) { return PreAuthenticatedAuthenticationToken.class.isAssignableFrom(authentication); }
2. 是否可以在过滤器中解析路径变量?
可以,但存在限制:
- Spring Security过滤器默认执行在
DispatcherServlet之前,此时路径变量尚未被解析,无法直接通过request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE)获取。 - 如果需要在过滤器中获取路径变量,需将过滤器的执行顺序调整到
DispatcherServlet之后,或者手动通过请求路径与匹配模式解析变量(实现复杂)。
更简便的方式是使用Spring MVC的拦截器(HandlerInterceptor),拦截器在DispatcherServlet处理请求后执行,此时路径变量已解析完成,可通过以下代码获取:
import org.springframework.web.servlet.HandlerMapping; // 在拦截器的preHandle方法中 Map<String, String> pathVariables = (Map<String, String>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); String targetVar = pathVariables.get("varName");
3. 在拦截器层实现认证是否为良好实践?
不推荐,原因如下:
- 覆盖范围有限:拦截器仅能处理Spring MVC请求,无法覆盖静态资源、非MVC Servlet请求等所有进入容器的请求,而过滤器可以处理全部请求。
- 安全机制缺失:Spring Security过滤器链集成了CSRF防护、会话固定保护、安全上下文管理等成熟特性,在拦截器中实现认证需要自行处理这些细节,易出现安全漏洞。
- 流程耦合:认证属于安全基础设施层逻辑,放在MVC拦截器中会导致安全逻辑与业务逻辑耦合,不符合分层设计原则。
建议认证逻辑仍放在Spring Security过滤器链中,若需在MVC层做权限校验,可使用Spring Security提供的@PreAuthorize、@PostAuthorize等注解,或自定义方法拦截器。
内容的提问来源于stack exchange,提问作者Anton

