如何在Spring Security中用Basic Auth/JWT为多租户创建带超时会话
需求与问题背景
- 非登录授权场景,通过
Basic Auth头或JWT令牌验证请求 - API验证授权后转发请求到Prometheus,希望同一已验证来源仅首次验证,后续请求复用会话
- 需要用Spring Security实现带超时的内存会话以提升性能
- 当前仅实现了拦截器(每次请求都重复验证),未配置Spring Security,寻求更优方案
最优实现方案:Spring Security 自定义认证过滤器 + 内存会话管理
无需自行编写拦截器,利用Spring Security原生机制即可实现会话复用与认证缓存,步骤如下:
1. 配置Spring Security 内存会话
Spring Security默认采用内存会话,只需配置会话超时等规则:
@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http // API场景禁用CSRF .csrf(csrf -> csrf.disable()) // 会话管理配置 .sessionManagement(session -> session // 仅在需要时创建会话 .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) // 会话超时后跳转地址(按需调整) .invalidSessionUrl("/invalid-session") .expiredUrl("/session-expired") // 单用户仅允许一个会话 .maximumSessions(1) // 会话固定防护:迁移会话 .sessionFixation().migrateSession() ) // 添加自定义认证过滤器,优先级高于默认用户名密码过滤器 .addFilterBefore(clientKeyAuthFilter(), UsernamePasswordAuthenticationFilter.class) // 所有请求需通过认证 .authorizeHttpRequests(auth -> auth .anyRequest().authenticated() ); return http.build(); } @Bean public ClientKeyAuthenticationFilter clientKeyAuthFilter() { return new ClientKeyAuthenticationFilter(); } // 显式声明内存会话注册表(默认已实现,此处为清晰化配置) @Bean public SessionRegistry sessionRegistry() { return new SessionRegistryImpl(); } }
2. 自定义Client Key认证过滤器
替代原拦截器,首次验证通过后将认证信息存入SecurityContext与会话,后续请求直接复用:
public class ClientKeyAuthenticationFilter extends OncePerRequestFilter { public static final String API_CLIENT_KEY_HEADER = "client-key"; public static final String TENANT_ID = "X-Scope-OrgID"; @Autowired private ApiAuthorizationService apiAuthorizationService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 检查会话中是否已有有效认证信息 Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication != null && authentication.isAuthenticated()) { filterChain.doFilter(request, response); return; } // 跳过已有Tenant ID的请求(如转发自Prometheus的请求) if (StringUtils.isNotBlank(request.getHeader(TENANT_ID))) { filterChain.doFilter(request, response); return; } // 验证Client Key String clientKey = request.getHeader(API_CLIENT_KEY_HEADER); if (StringUtils.isBlank(clientKey) || !apiAuthorizationService.validateClientKey(clientKey)) { response.sendError(HttpStatus.UNAUTHORIZED.value(), "Invalid domain and client-key token"); return; } // 创建认证对象并存入SecurityContext,自动绑定至会话 Authentication auth = new UsernamePasswordAuthenticationToken(clientKey, null, Collections.emptyList()); SecurityContextHolder.getContext().setAuthentication(auth); // 设置Tenant ID响应头(后续替换为实际哈希值) response.setHeader(TENANT_ID, "tenant_1"); filterChain.doFilter(request, response); } }
3. 移除原拦截器
Spring Security过滤器已覆盖认证逻辑,直接删除原ApiValidationTokenInterceptor即可,避免重复验证。
关键说明
- 会话复用:首次认证后,认证信息自动存入HttpSession,后续请求Spring Security会直接从会话读取认证状态,无需重复验证Client Key
- 内存会话:默认使用
SessionRegistryImpl实现内存存储,性能最优,适配你的场景 - 会话超时:通过
sessionManagement配置超时规则,超时后需重新验证Client Key - 过滤器特性:
OncePerRequestFilter确保每个请求仅执行一次验证,比拦截器更适配认证场景
内容的提问来源于stack exchange,提问作者Cristiano Nascimento
相关产品推荐
相关产品推荐

