You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 10:20:29