Spring Security 深度

共 41 题
📑 题目列表 41 题
#
★★★

1. DelegatingPasswordEncoder 如何识别旧哈希并在登录后升级,升级写回失败是否应阻断认证

请说明 DelegatingPasswordEncoder 如何识别旧哈希并在登录后升级密码哈希,以及升级写回失败时是否应阻断认证?

  • DelegatingPasswordEncoder 的编码前缀机制
  • 登录后升级哈希的流程
  • 写回失败的容错处理

DelegatingPasswordEncoder 支持同时管理多种密码编码器,通过编码前缀(如 {bcrypt}{sha256}{noop})识别存储的哈希属于哪种算法,从而在密码升级迁移期间兼容新旧哈希。它默认的"编码器 ID 前缀"机制:存储的哈希以 {id} 开头,matches 时根据前缀选择合适的编码器校验;encode 时统一使用默认编码器(如 bcrypt)并加上前缀。登录后升级流程:当用户登录成功时,若检测到存储的哈希前缀不是当前默认算法(如 {sha256}),则用新默认算法重新编码明文密码并写回数据库,实现无感升级。关于写回失败是否阻断认证:不应阻断——认证是否成功应完全取决于 matches 的校验结果,密码升级是"尽力而为"的辅助操作,写回失败(如数据库异常)不应导致已通过认证的用户登录失败,否则会造成可用性事故。正确处理是:先认证成功,再尝试升级写回,写回失败仅记日志、不影响认证结果。可参考 Spring Security 文档中 DaoAuthenticationProviderupgradeEncoding 逻辑与 UserDetailsPasswordService

本题考察 DelegatingPasswordEncoder 的升级机制与容错。核心是"前缀识别算法 + 登录后升级"与"写回失败不阻断认证"。回答应点出认证与升级解耦。

#
★★★

2. FormLogin/HttpBasic/OAuth2 的登录模式

请说明 Spring Security 中 FormLogin、HttpBasic 与 OAuth2 三种登录/认证模式的区别与适用场景?

  • FormLogin 表单登录
  • HttpBasic 基础认证
  • OAuth2 登录(OIDC)

三种模式对应不同的认证方式:FormLogin 基于表单——用户访问受保护资源时被重定向到登录页,提交用户名/密码后认证,通常配合 Session 与 CSRF,适用于浏览器端的人机交互应用(如传统 Web 后台)。HttpBasic 基于 HTTP Basic——浏览器/客户端在请求头携带 Authorization: Basic base64(user:pass),服务端解析校验,无登录页、无会话概念、每次请求都带凭据,适用于简单服务监控、API 测试或内部工具,但凭据明文传输(必须 HTTPS)且缺乏 CSRF 防护。OAuth2 登录(OAuth2Login/OIDC)——用户通过第三方授权服务器(如 Keycloak、Google)认证,遵循 OAuth2 Authorization Code + OIDC 流程,返回 ID Token(JWT)与用户信息,适用于单点登录、第三方账号登录、开放平台。选择上:内部浏览器应用用 FormLogin;简单的服务/自动化用 HttpBasic;需要 SSO/第三方登录用 OAuth2。三者可组合(如 FormLogin 为主 + OAuth2 登录入口)。

本题考察三种登录模式。核心是"表单/基础/第三方"三类认证的流程与适用场景。回答应点出各自凭据处理与适用场景。

#
★★★

3. JWKS 密钥轮换期间 JwtDecoder 如何缓存和刷新公钥,未知 kid 与端点不可用应如何处理

请说明 JWKS 密钥轮换期间 JwtDecoder 如何缓存和刷新公钥,以及遇到未知 kid 或 JWKS 端点不可用时应如何处理?

  • JWKS 公钥缓存与刷新机制
  • 未知 kid 的处理
  • 端点不可用时的降级

JwtDecoder 从 JWKS 端点(withJwkSetUri)加载公钥时会缓存 JWK Set,避免每次验证都请求端点。缓存与刷新:nimbus 的 RemoteJWKSet 会缓存 JWKS,并设置刷新间隔(timeToLive),同时支持"较新公钥"的自动刷新——当遇到未知的 kid 时,会触发一次强制刷新(重新拉取 JWKS),因为很可能密钥轮换后新公钥尚未缓存。处理未知 kid:若刷新后仍找不到该 kid 对应的公钥,则无法验签,JwtDecoder.decode 会抛出 JwtException(如 InvalidSignatureException),应视为认证失败返回 401。端点不可用:若 JWKS 端点拉取失败但本地缓存仍有公钥,可继续用缓存公钥验证(尽量容错);若缓存为空且端点不可用,则无法验证,应返回失败并告警。工程上还应配置重试、超时、限制 JWKS 大小并缓存刷新,避免每次请求都打端点。轮换时授权服务器应保留旧公钥一段时间,覆盖旧 token 剩余有效期。

本题考察 JwtDecoder 的 JWKS 缓存与刷新。核心是"未知 kid 触发刷新,端点不可用时有缓存容错、无缓存失败"。回答应点出轮换与刷新策略。

#
★★★

4. OAuth2 Authorization Code + PKCE 在 Spring Security 6.4 与 Spring Cloud Gateway 协同下的会话保持

请说明 OAuth2 Authorization Code + PKCE 在 Spring Security 6.4 与 Spring Cloud Gateway 协同下的会话保持机制?

  • Authorization Code + PKCE 流程
  • 网关转发与会话保持(Token Relay)
  • 分布式会话与 Cookie

OAuth2 Authorization Code + PKCE 用于浏览器/SPA 客户端,通过授权码换取令牌,PKCE 用 code_challenge/code_verifier 防止授权码被截获。在 Spring Cloud Gateway 架构中,网关充当 OAuth2 客户端(OAuth2Login)或资源服务器,与 Spring Security 6.4 协同。会话保持机制:用户未认证访问网关 → 网关重定向到授权服务器 → 授权码回调 → 网关用 PKCE 换取访问令牌与 ID Token → 存到网关的 Session(HttpSession + 分布式存储)→ 后续请求从 Session 读取令牌,通过 TokenRelay 过滤器把令牌注入转发给下游服务。会话保持关键点:网关的 Session 需分布式共享(如 Redis/Spring Session),Cookie 需配置 SameSite、Secure 与域;PKCE 的 code_verifier 保存在网关 Session 中,回调时比对;Token Relay 使下游服务无需重复 OAuth2 流程。Spring Security 6.4 增强了 OAuth2 客户端配置与 PKCE 支持(authorizationCodeGrant() 配置 PKCE)。同时也需处理会话固定与令牌刷新(Refresh Token 存 Session)。

本题考察网关与 OAuth2+PKCE 的会话保持。核心是"网关做 OAuth2 客户端、TokenRelay 转发令牌、分布式 Session 保持"。回答应点出 PKCE 与会话存储。

#
★★★

5. OAuth2 Resource Server 校验 JWT 时,issuer、audience、时间声明和算法白名单分别防范什么

请说明 OAuth2 Resource Server 校验 JWT 时,issuer、audience、时间声明(exp/nbf/iat)与算法白名单分别防范什么攻击?

  • issuer 校验(防伪造来源)
  • audience 校验(防跨服务)
  • 时间声明与算法白名单(防过期/算法混淆)

Resource Server 校验 JWT 时,各校验项分别防范不同风险:issuer(iss)校验——确保 token 来自可信授权服务器,防止攻击者用其他来源伪造的 token 或误用其他签发者的 token;若配置了 issuer-uri,Spring Security 会从该端点发现并加载 JWKS。audience(aud)校验——确保 token 是发给当前资源服务器的,防止 token 被用于其他服务(token 跨服务复用/滥用),攻击者把某个服务的 token 拿到别的服务用。时间声明(exp/nbf/iat)——exp 防过期 token 长期有效,nbf 防提前生效,iat 防异常未来签发时间,配合时钟偏斜容忍。算法白名单——防算法混淆攻击(如把 RS256 改为 HS256)、alg:none 无签名、以及弱算法(如 HS256 密钥过短),确保只接受配置的强算法。四者配合构成"来源可信 + 受众正确 + 时间有效 + 算法安全"的完整校验链,任一失败即拒绝。

本题考察 Resource Server 的 JWT 校验项。核心是"iss/aud/时间/算法各防一类攻击"。回答应逐项点出对应风险。

#
★★★

6. Spring Security 6.x 的 OpenSAML4 SAML 集成与 Spring Boot 4.0 启动类的依赖兼容性

请说明 Spring Security 6.x 的 OpenSAML4 SAML 集成,以及其与 Spring Boot 4.0 启动类的依赖兼容性考量?

  • OpenSAML4 与 Spring Security SAML 集成
  • Spring Boot 4.0 启动类与依赖
  • 兼容性(JRE、版本)

Spring Security 6.x 引入 spring-security-saml2-service-provider 模块,基于 OpenSAML4 实现 SAML 2.0 Service Provider(SP)。它支持通过 SAML2LoginConfigurer 配置 SAML 登录,处理 SP 元数据、IdP 元数据、断言加密与签名验证。OpenSAML4 相比旧版 OpenSAML3 调整了 API(模块化、JAXB 等),Spring Security 6.x 与 OpenSAML4 绑定。与 Spring Boot 4.0 的兼容性:Spring Boot 4.0 基于 Spring Framework 7 与 Java 17+(甚至更高基线),启动类需确保引入的 spring-security-saml2-service-provider 版本与 Spring Boot 4.0 管理版本一致(由 Spring Boot 的 BOM 决定),避免版本冲突;OpenSAML4 依赖的部分库(如 org.apache.santuario:xmlseccom.squareup.okhttp3)需与 Spring Boot 依赖管理兼容。实际工程中,Spring Boot 4.0 启动类(@SpringBootApplication)本身不涉及 SAML 特有依赖,但需通过 spring-boot-starterspring-security 的版本匹配来保证 SAML 模块可装配;若出现类冲突(如 OpenSAML 与 Boot 内部 XML 库冲突),需排除冲突依赖并锁定版本。

本题考察 SAML/OpenSAML4 与 Spring Boot 4.0 兼容性。核心是"版本匹配、依赖冲突管理"。回答应点出 OpenSAML4 与 Boot 版本联动。

#
★★★

7. Spring Security 的测试支持(@WithMockUser/@WithJwt)

请说明 Spring Security 的测试支持,包括 @WithMockUser、@WithAnonymousUser、@WithJwt 等注解的用法?

  • @WithMockUser 模拟认证用户
  • @WithJwt 模拟 JWT 认证
  • 测试安全上下文

Spring Security 提供 spring-security-test 模块,测试方法级安全与控制器时无需真实登录。常用注解:@WithMockUser——在测试方法执行前向 SecurityContext 注入一个模拟的 User(可指定 username、roles、authorities),用于测试方法级安全与认证逻辑;@WithAnonymousUser——模拟匿名用户,测试未认证行为;@WithUserDetails——根据 UserDetails 加载用户;@WithJwt——模拟 JWT 认证,可指定 JWT 声明(subject、claims、authorities),用于 Resource Server 测试;@WithOAuth2User——模拟 OAuth2 用户。这些注解通过 SecurityContextTestExecutionListener 在测试前后注入与清理 SecurityContext(SecurityContextHolderMODE_THREADLOCAL)。也支持 @SecurityTestExecutionListeners 或自定义 @WithSecurityContext。工程上可结合 @WebMvcTest 测试控制器、@SpringBootTest 测试服务层,避免真实认证开销。

本题考察 Spring Security 测试支持。核心是"用注解注入安全上下文模拟认证"。回答应点出 @WithMockUser 与 @WithJwt 的区别与用法。

@SpringBootTest
@AutoConfigureMockMvc
class AdminControllerTest {
    @Test
    @WithMockUser(roles = "ADMIN")
    void accessAsAdmin() throws Exception {
        mockMvc.perform(get("/admin")).andExpect(status().isOk());
    }
    @Test
    @WithJwt(jwt = @Jwt(subject = "u1", claims = @Claim(string = "scope", stringList = "read")))
    void accessViaJwt() throws Exception {
        mockMvc.perform(get("/api")).andExpect(status().isOk());
    }
}
#
★★★

8. 在 Spring Boot 4.0 中使用 Spring Security 6.4 整合 Keycloak 作为 OIDC IdP 的客户端配置

请说明在 Spring Boot 4.0 中使用 Spring Security 6.4 整合 Keycloak 作为 OIDC IdP 的客户端配置要点?

  • OIDC 客户端配置(issuer-uri、client-id)
  • 授权码流程与 PKCE
  • 用户信息与角色映射

在 Spring Boot 4.0 + Spring Security 6.4 中整合 Keycloak 作为 OIDC IdP,配置要点:一是依赖与配置——引入 spring-boot-starter-oauth2-client,在 application.yml 配置 spring.security.oauth2.client.provider.keycloak.issuer-uri(如 http://localhost:8080/realms/myrealm)、registration.keycloak.client-id/client-secretscope(openid/profile/email)、authorization-grant-type=authorization_code;也可用 spring.security.oauth2.client.registrationclient-authentication-method。二是安全配置——httpSecurity.oauth2Login() 启用 OIDC 登录,可自定义 loginPagedefaultSuccessUrluserInfoEndpoint(从 Keycloak 获取用户信息)。三是角色映射——Keycloak 返回的 realm_access.rolesresource_access 需自定义 GrantedAuthoritiesMapper 把 claim 映射为角色,或使用 JwtAuthenticationConverter 映射。四是启动类——@SpringBootApplication 自动装配认证配置,无需额外代码。五是 PKCE 如 SPA 场景需配置。注意版本匹配:Spring Security 6.4 与 Spring Boot 4.0 的 BOM 保持一致,Keycloak 的 OIDC 端点由 issuer-uri 自动发现。

本题考察 Keycloak 作为 OIDC IdP 的客户端配置。核心是"issuer-uri/client-id 配置 + oauth2Login + 角色映射"。回答应点出配置与版本兼容。

#
★★★

9. 在 Spring Cloud 微服务中实现分布式 Session 共享与 Spring Security 的 RememberMe 策略

请说明在 Spring Cloud 微服务中实现分布式 Session 共享的方法,以及 Spring Security 的 RememberMe 策略如何配合?

  • 分布式 Session 共享(Spring Session + Redis)
  • RememberMe 的实现与安全
  • 网关与服务的会话

微服务分布式 Session 共享常用 Spring Session 把 Session 存到 Redis,各服务通过一致的 SessionRepository 读写同一份 Session,实现多实例与前端的会话保持。配置:引入 spring-session-data-redisspring.session.store-type=redis,Session 自动以 Redis 为存储;网关/服务间通过一致的 Session Cookie(JSESSIONID)定位 Session。Spring Security 的 RememberMe 策略:rememberMe() 在用户勾选"记住我"后,签发一个长期有效的 RememberMe Cookie,用户下次访问时自动认证,无需重新登录。实现有两种:一是基于 TokenBasedRememberMeServices(哈希 token,服务端无状态但密钥泄露风险);二是基于 PersistentTokenRepository(JDBC 存储 token 序列,可撤销、更安全)。配合分布式场景:RememberMe Cookie 需在域内共享(Cookie 域与 Secure 配置),且 RememberMe 的密钥需在多个服务一致(否则无法跨服务验证);若用 TokenBasedRememberMe 需统一密钥;Spring Session 可让 RememberMe 的认证结果也存入共享 Session。安全注意:RememberMe Cookie 应加 Secure、HttpOnly 与合理过期,并支持注销时清除。

本题考察分布式 Session 与 RememberMe。核心是"Spring Session 共享 + RememberMe 的两种实现与跨服务一致性"。回答应点出密钥统一与安全属性。

#
★★★

10. 把 JWT claim 映射为 GrantedAuthority 时,如何限制命名空间并防止外部声明直接获得内部角色

请说明把 JWT claim 映射为 GrantedAuthority 时,如何限制命名空间并防止外部声明直接获得内部角色?

  • Claims 到 Authority 的映射
  • 命名空间与白名单
  • 防止角色提升

把 JWT claim 映射为 GrantedAuthority 时,若不设限,攻击者或外部 IdP 传入的任意声明(如 rolesrealm_access)可能直接映射为内部高权限角色,造成垂直越权。防护要点:一是限制命名空间——只映射受信任的特定 claim 路径与命名空间(如 realm_access.roles),且对这些 claim 做格式校验(值必须匹配权限命名规范,如带 ROLE_ 前缀),过滤掉未知/高权限的声明;二是映射白名单——定义"允许映射为内部角色的 claim 值集合",只有出现在白名单内的值才转换为 GrantedAuthority,其余忽略或拒绝;三是用 JwtAuthenticationConverter 自定义 JwtGrantedAuthoritiesConverter,显式解析并校验 claim,而不是把所有 claim 自动转成 authority;四是区分"外部声明"与"内部授权"——内部角色应由服务端权限策略(数据库/角色映射表)决定,JWT 中的声明仅作为参考,关键授权(管理操作)还应二次校验。核心原则:绝不直接信任 token 中的任意 claim 作为权限,权限必须经过白名单映射与服务端授权决策。

本题考察 claim 到权限的映射安全。核心是"命名空间限制 + 白名单 + 不直接信任外部声明"。回答应点出 JwtAuthenticationConverter 自定义映射。

@Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
    Set<String> allowed = Set.of("ROLE_USER", "ROLE_ADMIN");   // 权限白名单
    JwtAuthenticationConverter auth = new JwtAuthenticationConverter();
    auth.setJwtGrantedAuthoritiesConverter(jwt -> {
        List<String> raw = jwt.getClaimAsStringList("realm_access.roles"); // 限定命名空间
        return raw.stream().filter(allowed::contains)
                .map(SimpleGrantedAuthority::new).collect(Collectors.toList());
    });
    return auth;
}
#
★★★

11. 注销 JWT 访问令牌时,短过期、撤销表和密钥轮换三种方案如何权衡状态与响应速度

请说明注销 JWT 访问令牌时,短过期、撤销表与密钥轮换三种方案如何权衡状态与响应速度?

  • 短过期(无状态、快)
  • 撤销表(有状态、精确)
  • 密钥轮换(全局失效)

JWT 注销方案权衡:一是短过期(Short TTL)——无状态,吊销令牌的粒度由 expiration 决定,响应快(无额外存储查询),但无法即时撤销,泄露窗口 = TTL;适合对时效敏感的普通访问令牌。二是撤销表(Blacklist)——把要注销的 token 的 jti(或哈希)存入 Redis/DB,验签时查询,能即时精确撤销单个 token,但引入存储与每次验签的查询开销(响应变慢、需缓存/分布式存储),适合需要精确登出/敏感操作。三是密钥轮换(Secret Rotation)——更换签名密钥使所有用旧密钥签发的 token 失效,粒度是"全部 token",无法定向撤销,但可使大规模泄露快速全局失效;响应快(无查询),但需处理轮换期间新旧密钥并存。权衡原则:按需选择——普通访问用短 TTL 保性能;敏感操作(改密、登出)用撤销表精确撤销;重大泄露用密钥轮换全局重置。可组合:短 TTL + 撤销表(只对需撤销的 token 记黑名单)+ 定期轮换。衡量维度是"状态程度(无状态→有状态)"与"响应速度/撤销粒度"的曲面。

本题考察 JWT 注销方案权衡。核心是"短过期无状态快、撤销表精确有状态、轮换全局失效"。回答应点出三者的状态与粒度权衡。

#
★★★

12. 虚拟线程与异步任务中 SecurityContext 如何传播,直接依赖 ThreadLocal 会造成什么丢失或串扰

请说明虚拟线程与异步任务中 SecurityContext 如何传播,直接依赖 ThreadLocal 会造成什么丢失或串扰?

  • SecurityContext 的 ThreadLocal 存储
  • 异步/虚拟线程的上下文传播
  • DelegatingSecurityContextExecutor 与拷贝

Spring Security 默认把 SecurityContext 存在 ThreadLocal(SecurityContextHolder 的 MODE_THREADLOCAL),只对当前线程可见。在异步任务(@Async、CompletableFuture、ExecutorService)或虚拟线程中,任务在不同线程执行,ThreadLocal 不会自动传播,导致子线程 SecurityContextHolder.getContext() 为空(丢失认证上下文),或若线程池复用线程且未清理,可能把上一个请求的上下文串到下一个请求(串扰/信息泄露)。解决:一是用 DelegatingSecurityContextExecutor/DelegatingSecurityContextCallable 包装任务,在提交时捕获当前 SecurityContext,任务执行时设置、结束后恢复/清理;二是 Spring 的 SecurityContextHolder 支持 MODE_INHERITABLETHREADLOCAL(子线程继承),但虚拟线程/线程池复用场景下仍需谨慎;三是 Spring Security 6.x 支持基于 ScopedValue 的上下文传播(配合虚拟线程),把 SecurityContext 作为 Scoped Value 传递,避免 ThreadLocal 的串扰与泄漏。虚拟线程下应避免直接依赖 ThreadLocal,因为虚拟线程可被挂起/迁移,且 ThreadLocal 在虚拟线程中开销大、易造成上下文泄漏;应显式传播或使用 ScopedValue。

本题考察异步/虚拟线程的 SecurityContext 传播。核心是"ThreadLocal 不跨线程、需显式传播"并指出虚拟线程的坑。回答应点出 DelegatingSecurityContextExecutor 与 ScopedValue。

#
★★

13. @PreAuthorize 的 SpEL 与方法级安全

请说明 @PreAuthorize 注解使用 SpEL 实现方法级安全的作用与用法?

  • @PreAuthorize 的 SpEL 表达式
  • 方法级授权的配置
  • 常见表达式(hasRole/hasAuthority)

@PreAuthorize 是 Spring Security 的方法级安全注解,在方法执行前通过 SpEL 表达式判断调用者是否具备访问权限,不满足则抛 AccessDeniedException。需启用方法级安全:@EnableMethodSecurity(Security 6.x 替代旧 @EnableGlobalMethodSecurity)。常见 SpEL 表达式:hasRole('ADMIN')hasAuthority('APPROVE_ORDER')hasAnyRole(...)isAuthenticated()permitAll()denyAll();还可引用方法参数(#id#obj.owner)与 Spring Bean(@beanName.method()),实现对象级授权,如 @PreAuthorize("#order.userId == @auth.currentUserId()")。也支持 @PostAuthorize(方法返回后校验,可 returnObject)。方法级安全通过 AOP 代理实现,需注意自调用(同类内调用)会绕过代理。用法:@PreAuthorize("hasRole('ADMIN')") 或结合 @Secured(更简单,但不能用 SpEL)。它是 RBAC 与对象级授权的重要实现。

本题考察方法级安全。核心是"@PreAuthorize 用 SpEL 在方法前授权"。回答应点出启用注解、常用表达式与参数/Bean 引用。

@EnableMethodSecurity
@RestController
public class OrderController {
    @PreAuthorize("hasRole('ADMIN') or #order.userId == @auth.currentUserId()")
    public Order getOrder(@PathVariable Long id, @RequestBody Order order) { ... }
}
#
★★

14. AuthenticationManagerBuilder 与 AuthenticationConfiguration 的差异

请说明 AuthenticationManagerBuilder 与 AuthenticationConfiguration 在 Spring Security 中的差异与使用场景?

  • AuthenticationManagerBuilder 的局部构建
  • AuthenticationConfiguration 的全局获取
  • 自定义认证配置

AuthenticationManagerBuilder 用于在局部(某个 SecurityFilterChain 或局部配置)构建 AuthenticationManager,通过 authenticationProvider(...)userDetailsService(...)inMemoryAuthentication() 等配置认证源;适合在某个 filter chain 内定制认证。AuthenticationConfiguration 是全局的,聚合所有已配置的 AuthenticationManagerBuilder 与全局 Provider,最终暴露全局的 AuthenticationManager;在需要获取全局认证管理器(如自定义 filter、controller 中手动认证)时注入 AuthenticationConfiguration 并调用 getAuthenticationManager()。差异:AuthenticationManagerBuilder 是"构建器 + 局部作用域",AuthenticationConfiguration 是"全局聚合 + 提供者"。工程上:若只需简单内存用户,用 InMemoryUserDetailsManager 或 builder;若自定义 multi-auth(如自定义 Provider、JWT filter),常通过 AuthenticationConfiguration 获取全局 manager 或自行 new AuthenticationManager。Spring Security 6 推荐声明式配置(UserDetailsService/AuthenticationProvider Bean 自动装配),减少手动 builder。

本题考察两者的差异。核心是"局部构建器 vs 全局聚合器"。回答应点出各自作用域与获取方式。

#
★★

15. AuthorizationFilter 与基于权限的 URL 访问

请说明 AuthorizationFilter 与基于权限/角色的 URL 访问控制机制?

  • AuthorizationFilter 的作用
  • requestMatchers 授权规则
  • 与旧 FilterSecurityInterceptor 的对比

AuthorizationFilter 是 Spring Security 6.x 中负责 URL 级授权的过滤器,取代了旧的 FilterSecurityInterceptor。它通过 authorizeHttpRequests() 配置基于 URL 的授权规则,例如 .requestMatchers("/admin/**").hasRole("ADMIN").requestMatchers("/api/**").authenticated().anyRequest().permitAll()。其核心是 AuthorizationManager(如 AuthorityAuthorizationManagerAuthenticatedAuthorizationManager),对每个请求的 HttpServletRequest 做授权决策。差异:AuthorizationFilter 基于 AuthorizationManager 抽象,更灵活、可组合(可自定义 AuthorizationManager),且支持 anyRequest().authenticated() 等;旧 FilterSecurityInterceptor 基于 AccessDecisionManager/FilterInvocationSecurityMetadataSource。配置时注意规则顺序——先匹配的先生效,anyRequest() 应放最后。AuthorizationFilter 在过滤器链中会先做认证(若未认证返回 401/重定向),再判断权限。它只做 URL 级授权,方法级授权仍由 @PreAuthorize 处理。

本题考察 AuthorizationFilter。核心是"基于 URL 的授权、AuthorizationManager、规则顺序"。回答应点出与旧 FilterSecurityInterceptor 的演进。

#
★★

16. AuthorizationManager 如何组合 RBAC 与基于资源属性的 ABAC,拒绝决策需要记录哪些审计信息

请说明 AuthorizationManager 如何组合 RBAC 与基于资源属性的 ABAC,以及拒绝决策需要记录哪些审计信息?

  • AuthorizationManager 的抽象
  • RBAC 与 ABAC 组合
  • 拒绝决策的审计记录

AuthorizationManager 是 Spring Security 6 的授权抽象,authorize() 接收 Authentication 与上下文对象,返回 AuthorizationDecision。RBAC 基于角色的 AuthorityAuthorizationManager 判断用户是否拥有角色/权限;ABAC 基于资源属性(如资源 owner、部门、数据敏感度)结合请求上下文做决策。组合方式:自定义 AuthorizationManager 实现"先 RBAC 校验(是否具备角色),再 ABAC 校验(资源属性匹配)",或通过 AuthorizationManagers.allOf(...)/anyOf(...) 组合多个 manager;在授权规则中可注入 Spring Bean 引用资源属性做评估。拒绝决策的审计信息:审计日志应记录——操作人(username/subject)、被拒绝的请求(URL/method/参数)、资源标识(resourceId/owner)、请求上下文(IP、user-agent、时间)、所需权限与当前权限、决策结果(拒绝)与原因(如无角色/资源不匹配)、以及决策所用 AuthorizationManager 类型。这样既能追溯越权尝试,又能定位授权策略问题。可用 AuthorizationEventPublisher 发布 AuthorizationDeniedEvent 供审计。

本题考察 RBAC+ABAC 组合与审计。核心是"组合 manager 做 RBAC+ABAC、拒绝决策记录完整审计上下文"。回答应点出审计字段。

#
★★

17. CORS 预检请求为什么应在认证决策前处理,允许凭证时为何不能使用通配来源

请说明 CORS 预检请求(OPTIONS)为什么应在认证决策前处理,以及允许凭证时为何不能使用通配来源?

  • 预检请求的处理时机
  • 认证决策与 CORS 的先后
  • 通配来源与凭证冲突

CORS 预检请求(Preflight)是浏览器在跨域请求实际发送前发起的 OPTIONS 请求,用于探测服务端是否允许该跨域请求(方法、头、来源)。预检请求不应携带业务凭据、也不应触发认证,因此 CORS 过滤器(CorsFilter)应放在认证过滤器之前,先处理预检请求并直接返回允许的响应头,否则预检请求会被认证过滤器拦截(未认证返回 401),导致浏览器无法完成预检、实际请求被阻断。Spring Security 中通过 cors() 配置,CorsFilter 在过滤器链中优先于认证。允许凭证时不能使用通配来源的原因:当 allowCredentials(true) 时(需携带 Cookie/Authorization),Access-Control-Allow-Origin 不能是 *,因为浏览器规范要求携带凭证的跨域响应必须返回具体来源,否则浏览器拒绝该响应;且 * 意味着任何站点都能携带凭证访问,产生 CSRF/凭证泄露风险。因此必须用具体 Origin 白名单或 allowedOriginPatterns(回显具体 Origin)。

本题考察 CORS 预检与凭证来源。核心是"预检先于认证、凭证不能用通配来源"。回答应点出过滤器顺序与浏览器规范。

#
★★

18. CorsConfigurationSource 的细粒度配置

请说明 CorsConfigurationSource 的细粒度配置,以及如何针对不同路径配置不同 CORS 策略?

  • CorsConfigurationSource 的作用
  • 基于路径的 CORS 配置
  • 默认配置与自定义

CorsConfigurationSource 是 Spring 中提供 CORS 配置的抽象,通过 getCorsConfiguration(request) 根据请求返回对应的 CorsConfiguration。细粒度配置:可注册 UrlBasedCorsConfigurationSource,通过 registerCorsConfiguration(pathPattern, config) 为不同路径(如 /api/**/public/**)配置不同的 CORS 策略(允许的来源、方法、头、是否允许凭证、maxAge 等)。也支持自定义 CorsConfigurationSource 实现,基于请求动态决定(如按 tenant、header 返回不同配置)。在 Spring Security 中,http.cors(cors -> cors.configurationSource(source)) 把该 source 接入过滤器链。配置示例:/api/** 允许特定前端域名并允许凭证,/public/** 允许所有来源但不允许凭证。细粒度配置的价值在于:把开放接口与受限接口的 CORS 策略区分开,避免"一刀切"造成的安全或兼容问题。注意 allowedOriginPatternsallowedOrigins 的区别,以及 allowCredentials 与通配来源的冲突。

本题考察 CorsConfigurationSource 细粒度配置。核心是"按路径注册不同 CORS 策略"。回答应点出 UrlBasedCorsConfigurationSource 与动态 source。

#
★★

19. DelegatingSecurityContextExecutor 包装任务时捕获的是哪一时刻的上下文,任务结束后怎样清理

请说明 DelegatingSecurityContextExecutor 包装任务时捕获的是哪一时刻的 SecurityContext,以及任务结束后如何清理?

  • 提交时捕获上下文
  • 任务执行与恢复
  • 结束后清理

DelegatingSecurityContextExecutor 包装一个 Executor,在提交任务时execute() 被调用时刻)从 SecurityContextHolder 捕获当前线程的 SecurityContext,并把 SecurityContext 与任务关联(通过 DelegatingSecurityContextRunnable)。任务在目标线程执行时,会先设置捕获的 SecurityContext 到执行线程的 SecurityContextHolder,使任务内能访问认证上下文;任务完成后,会清理(restore)执行线程原有的 SecurityContext——即恢复任务执行前该线程的上下文,避免把任务的上下文泄漏到线程池中其他后续任务(防串扰)。其内部用 SecurityContextHolder.createEmptyContext() 或保存的旧上下文做 restore。使用场景:@Async 自定义 executor、CompletableFuture、手动线程池提交任务时,用 DelegatingSecurityContextExecutor 包装以传播认证上下文。注意:捕获的是提交时上下文,任务执行期间若 Session 过期/上下文变化,任务内仍用提交时的上下文。

本题考察 DelegatingSecurityContextExecutor。核心是"提交时捕获、执行时设置、结束后恢复清理"。回答应点出时机与防串扰。

#
★★

20. LogoutFilter 与 LogoutSuccessHandler

请说明 Spring Security 的 LogoutFilter 与 LogoutSuccessHandler 的作用与用法?

  • LogoutFilter 的登出流程
  • LogoutSuccessHandler 自定义登出后处理
  • 登出时的清理(Session、Cookie、RememberMe)

LogoutFilter 处理登出请求(默认匹配 POST /logout,Security 6 默认要求 POST+CSRF)。流程:匹配登出请求 → 依次调用 LogoutHandler(如 SecurityContextLogoutHandler 清除 SecurityContext、CookieClearingLogoutHandler 清除 cookie、RememberMeServices 的 logout 清除 RememberMe)→ 调用 LogoutSuccessHandler 处理成功后的跳转/响应。SecurityContextLogoutHandler 使 Session 失效并清空 SecurityContext;LogoutSuccessHandler(如 SimpleUrlLogoutSuccessHandler 重定向到指定页面,或自定义 LogoutSuccessHandler 返回 JSON)决定登出后的响应。配置:.logout(logout -> logout.logoutUrl("/logout").logoutSuccessHandler(...).invalidateHttpSession(true).deleteCookies("JSESSIONID"))。自定义 LogoutSuccessHandler 常用于前后端分离场景返回 JSON 或做清理(如清除 Redis 中的会话、撤销 refresh token)。注意:登出后应使 Session 失效、清除 RememberMe Cookie,并清理任何服务端状态(如 Spring Session 的 Redis 会话)。

本题考察登出机制。核心是"LogoutHandler 清理 + LogoutSuccessHandler 响应"。回答应点出登出时的清理项。

#
★★

21. Passkey 基于 WebAuthn 的挑战、来源和 RP ID 校验分别防止什么攻击,凭据如何绑定用户

请说明 Passkey 基于 WebAuthn 的挑战、来源和 RP ID 校验分别防止什么攻击,以及凭据如何绑定用户?

  • WebAuthn 的挑战(challenge)防重放
  • 来源(origin)与 RP ID 校验
  • 凭据绑定用户

Passkey 使用 WebAuthn 协议,基于公钥密码实现无密码认证。关键校验:一是挑战(challenge)——服务端生成随机 challenge,客户端用私钥签名,服务端校验签名与 challenge 一致,防止重放攻击与中间人(每次认证 challenge 不同,旧签名无法复用)。二是来源(origin)——客户端(浏览器)在签名时把 origin 纳入,服务端校验 origin 是否在允许列表,防止跨站/跨源攻击(攻击者伪造的站点 origin 不匹配被拒绝)。三是 RP ID(Relaying Party ID)——RP ID 是应用标识(通常为域名),客户端在创建/认证时把 RP ID 纳入,服务端校验 RP ID 匹配,防止攻击者用其他 RP 的凭据、或跨域名滥用。凭据绑定用户:注册时服务端生成 challenge,客户端生成密钥对,公钥与用户 ID 关联保存;认证时用户通过同一私钥签名,服务端用存储的公钥验证,从而把凭据绑定到该用户(公钥即用户凭证)。WebAuthn 的核心是"私钥不出设备、服务端只存公钥",抗钓鱼(origin 绑定)与抗重放(challenge)。

本题考察 WebAuthn/Passkey 的安全机制。核心是"challenge 防重放、origin 防跨源、RP ID 防跨域、公钥绑定用户"。回答应点出各校验的攻击面。

#
★★

22. RememberMe 与 PersistentTokenRepository(JDBC)

请说明 RememberMe 的实现,以及基于 PersistentTokenRepository(JDBC)的持久化方案?

  • RememberMe 两种实现
  • PersistentTokenRepository 的原理
  • 安全性与撤销

RememberMe 让用户在会话过期后仍能自动登录,有两种实现。TokenBasedRememberMeServices:把用户名、过期时间、密钥等哈希成 token 存于 Cookie,服务端无状态(用密钥校验),但密钥泄露则 token 可伪造,且无法逐个撤销。PersistentTokenRepository(基于 JDBC):更安全——服务端在数据库(如 persistent_logins 表)保存 token 序列(series)与使用者 token(token),Cookie 中存 series+token;每次自动登录时服务端比对,并更新 token(轮换),若检测到 token 不匹配(被窃取重放)则删除该 series(强制重新登录)。PersistentTokenRepository 需实现 JdbcTokenRepositoryImpl,配置 createTableOnStartup 或用 SQL 建表。优点:可撤销(删除数据库记录)、可检测重放、无需在 Cookie 中放敏感信息。工程上强推荐用 PersistentTokenRepository 而非 TokenBased。注意 RememberMe Cookie 本身是敏感凭证,应加 Secure、HttpOnly、合理过期,并配合登出时清除。

本题考察 RememberMe 持久化。核心是"TokenBased 无状态 vs PersistentTokenRepository 有状态可撤销"。回答应点出 series/token 与重放检测。

#
★★

23. SecurityFilterChain 的多链配置

请说明 SecurityFilterChain 的多链配置,以及如何针对不同路径配置不同的安全策略?

  • 多 SecurityFilterChain 的匹配
  • 不同路径不同安全策略
  • 匹配优先级(精确优先)

Spring Security 支持配置多个 SecurityFilterChain,每个链通过 securityMatcher 匹配特定路径,从而为不同路径应用不同安全策略(如 /api/** 用 JWT 无状态、/admin/** 用表单登录+Session、/public/** 放行)。匹配规则:请求按配置顺序与各链的 securityMatcher 匹配,第一个匹配的链生效(只使用第一条匹配链),因此需把更精确/更特殊的链放在前面。配置方式:SecurityFilterChain Bean 中 securityMatcher("/api/**")requestMatchers(...)http.securityMatcher(...) 指定该链匹配路径;anyRequest() 的链应放最后兜底。用途:把一个应用内的不同模块(如对外 API、后台管理、静态资源)用不同过滤器链分工,避免全局配置互相影响。注意:多链时每个链各自有独立的过滤序列,@Order 控制链顺序。

本题考察多 SecurityFilterChain。核心是"按 securityMatcher 匹配、第一条匹配链生效、精确链放前面"。回答应点出匹配优先级。

#
★★

24. Spring Security 6.4 的 AuthorizationEventPublisher 在审计日志(Audit)的工程价值

请说明 Spring Security 6.4 的 AuthorizationEventPublisher 在审计日志(Audit)中的工程价值?

  • AuthorizationEventPublisher 的作用
  • 授权事件(授权成功/拒绝)
  • 审计日志的落地

AuthorizationEventPublisher 用于发布授权相关的事件(如 AuthorizationDeniedEventAuthorizationGrantedEvent),是 Spring Security 审计的基础。工程价值在于:通过监听授权事件,可以把每一次授权决策(成功/拒绝)写入审计日志,实现可追溯的权限审计。AuthorizationDeniedEvent 携带被拒绝的 Authentication、请求对象与授权决策信息,可用于:记录越权尝试(谁、何时、访问什么资源被拒)、为安全运营提供告警(如高频 403 可能预示攻击)、以及满足合规审计需求。使用时需确保 AuthorizationEventPublisher 正确配置(默认通过 AuthorizationFilter 发布),并实现 ApplicationListener<AuthorizationDeniedEvent>@EventListener 记录。它补充了传统"只记登录日志"的不足,把"授权层"也纳入监控。审计日志应包含操作人、资源、结果、时间、IP 等,并集中存储(如日志系统/审计库)。

本题考察授权事件发布与审计。核心是"通过 AuthorizationEventPublisher 发布授权事件并监听记录"。回答应点出 AuthorizationDeniedEvent 与审计价值。

#
★★

25. Spring Security 6.x 的 CORS 配置与 Spring Cloud Gateway 全局 CORS 的冲突解决策略

请说明 Spring Security 6.x 的 CORS 配置与 Spring Cloud Gateway 全局 CORS 的冲突解决策略?

  • 两层 CORS 的作用位置
  • 冲突来源(重复响应头)
  • 解决策略(统一配置或分层)

在 Spring Cloud Gateway 架构中,CORS 可能同时在网关层(Spring Cloud Gateway 的 GlobalCorsProperties)与服务层(Spring Security 的 cors())配置。冲突来源:两层都尝试添加 CORS 响应头(Access-Control-Allow-Origin 等),可能重复添加或互相覆盖,导致预检请求处理异常或浏览器报错。解决策略:一是统一配置——通常只在网关层配置 CORS(因为浏览器只与网关交互,网关是唯一入口),服务层的 Spring Security 配置 cors() 时使用与网关一致的 configurationSource,或直接让服务层不重复配置(因为下游服务不直接面对浏览器请求);二是分层明确——网关负责 CORS 预检与响应头,服务层 Spring Security 只做认证授权,禁用重复 CORS(或配置为与网关一致);三是若网关透传,服务层需正确配置 CorsConfigurationSource 并避免与网关重复头。实践中,最稳妥的是在网关统一处理 CORS,Spring Security 的 cors() 配置与网关属性保持一致,避免头重复;若服务层也需 CORS(如直连场景),要确保 Access-Control-Allow-Origin 等头不重复添加。

本题考察网关与服务层 CORS 冲突。核心是"统一在网关配置,避免两层重复头"。回答应点出冲突机理与解决策略。

#
★★

26. Spring Security 7 的 AuthorizationManager 在 RBAC/ABAC 权限模型的演进

请说明 Spring Security 7 中 AuthorizationManager 在 RBAC/ABAC 权限模型上的演进方向?

  • AuthorizationManager 的统一抽象
  • RBAC/ABAC 的组合支持
  • 演进方向(统一授权模型)

Spring Security 7 进一步强化了 AuthorizationManager 作为统一授权抽象的地位,把 URL 级、方法级、对象级授权统一到同一抽象上。演进方向:一是统一授权模型——AuthorizationManager 接收 Authentication 与上下文(如 MethodInvocationHttpServletRequest、域对象),返回 AuthorizationDecision,使 RBAC(基于角色/权限的 AuthorityAuthorizationManager)与 ABAC(基于资源属性与上下文的自定义 manager)能在同一框架内组合;二是可组合性——AuthorizationManagers.allOf/anyOf 支持组合多个决策器,天然支持"先角色后属性"的混合策略;三是更细粒度——支持对象级授权(@PreAuthorize 的 SpEL 引用资源属性)与数据权限,向 ABAC 演进;四是审计与事件——结合 AuthorizationEventPublisher 记录授权决策。对 RBAC/ABAC 的意义:旧的以 AccessDecisionManager 投票或 @Secured 静态角色为主,演进后更强调"基于上下文与属性的动态授权",框架层面提供更一致的扩展点,方便团队实现数据权限与属性感知的权限模型。注意 Spring Security 7 的实际 API 以官方发布为准,但其方向是统一授权抽象与可组合决策。

本题考察 Spring Security 7 授权演进。核心是"统一 AuthorizationManager 抽象、支持 RBAC+ABAC 组合"。回答应点出演进方向与扩展点。

#
★★

27. Spring Security 与 Spring Modulith 模块间事件总线传递认证上下文时的安全边界如何设计

请说明 Spring Security 与 Spring Modulith 模块间事件总线传递认证上下文时的安全边界如何设计?

  • Spring Modulith 事件总线
  • 认证上下文跨模块传递
  • 安全边界与信任

Spring Modulith 强调模块间通过事件总线(ApplicationEventPublisher)通信,模块间不直接访问对方内部。当事件需要携带认证上下文(如谁发起了操作)时,需设计安全边界:一是传递最小化——事件中只携带必要的认证信息(如 subject/userIdauthentication.principal),不传递完整 Authentication 对象或敏感凭据(如密码、token),降低扩散面;二是事件模型防护——事件 payload 应定义为不可变 DTO,只含审计所需字段,避免把整个 SecurityContext 拷入事件;三是信任边界——事件消费方不应盲目信任事件携带的权限信息,涉及敏感操作时消费方仍应基于自己的权限策略二次校验(事件不是授权边界,仅作审计/上下文);四是传播机制——用 SecurityContext 的显式传播(DelegatingSecurityContextRunnable 或 ScopedValue)在事件处理线程设置上下文,避免通过事件传递丢失;五是审计——记录事件来源模块、操作人、时间,保证可追溯。核心原则:事件总线传递"身份标识"而非"授权凭据",授权决策始终在消费方基于服务端策略执行。

本题考察模块事件与认证上下文的安全边界。核心是"传递最小身份、不传递凭据、消费方独立授权"。回答应点出最小化与信任边界。

#
★★

28. Spring Security 与 springdoc-openapi 的安全定义

请说明 Spring Security 与 springdoc-openapi 的安全定义,以及如何让 OpenAPI 文档展示认证方式?

  • OpenAPI 安全方案定义
  • 与 Spring Security 的整合
  • 文档中的安全头显示

springdoc-openapi 自动生成 OpenAPI 文档,可与 Spring Security 整合展示安全方案。配置方式:在 @OpenAPIDefinitionOpenAPI Bean 中定义 SecurityScheme(如 bearerAuthHttpAuthenticationScheme,类型为 SECURITY_SCHEMEscheme = "bearer"bearerFormat = "JWT";或 ApiKey 用于 header 认证),并设置 addSecurityItemSecurityRequirement)把该方案应用到接口。这样 Swagger UI 会显示"Authorize"按钮,用户可输入 JWT,接口的 lock 图标标明受保护。注意:springdoc 的配置只是文档层,不执行认证;实际认证仍由 Spring Security 过滤器链完成。整合要点:受保护接口(有 @PreAuthorize 或在 SecurityFilterChain 中受限)在文档中标记安全;公开接口(permitAll)不标记。若接口需区分不同认证(如 OAuth2 的多个 scope),可定义多个 SecurityScheme。工程上需保证文档端点(如 /swagger-ui/v3/api-docs)在 Spring Security 中放行或做受限访问(生产环境建议受保护)。

本题考察 OpenAPI 安全定义。核心是"在文档中声明 SecurityScheme,仅文档展示,实际认证仍由 Spring Security 执行"。回答应点出 bearer/token 配置。

#
★★

29. Spring Security 如何防止会话固定攻击,认证成功后更换 session id 会影响哪些会话属性

请说明 Spring Security 如何防止会话固定攻击,以及认证成功后更换 session id 会影响哪些会话属性?

  • sessionFixation 配置
  • 更换 session id 的影响
  • 会话属性迁移

Spring Security 通过 sessionManagement().sessionFixation() 配置防止会话固定攻击,默认策略是 changeSessionId()(Servlet 3.1+ 容器支持时)——认证成功后更换 Session ID 并保留会话属性;也可配置 migrateSession() 新建会话并迁移属性(如购物车、CSRF token、OAuth 状态),保证功能不中断。相关策略:changeSessionId()(仅更换 ID,保留属性)、migrateSession()(新建会话并迁移属性)、newSession()(新建空会话,不迁移属性)、none()(不处理,不安全)。更换 session id 的影响:旧 Session ID 失效,攻击者持有的旧 ID 无法复用;会话属性——若用 changeSessionId/migrateSession,属性(存于 Session 的对象)被保留或迁移;若用 newSession,属性清空(需重新设置,如重新生成 CSRF token)。注意:Spring Security 的 CSRF token 默认存于 Session(HttpSessionCsrfTokenRepository),更换 session id 时若迁移失败,CSRF token 可能失效需重新获取;SecurityContext 本身也存于 Session,认证成功后需确保新会话中正确设置。因此配置时需考虑功能依赖性(如购物车、OAuth 状态、CSRF token)在迁移后是否仍可用。

本题考察会话固定防护。核心是"认证后更换 session id + 属性迁移"。回答应点出各策略与属性影响。

#
★★

30. Spring Security 的 SecurityContextHolder 与 Scoped Values 协同的多线程上下文传播机制

请说明 Spring Security 的 SecurityContextHolder 与 Scoped Values 协同的多线程上下文传播机制?

  • SecurityContextHolder 的存储模式
  • ScopedValue 的虚拟线程支持
  • 多线程上下文传播

SecurityContextHolder 默认用 ThreadLocal 存储 SecurityContextMODE_THREADLOCAL),只对当前线程可见,跨线程/异步传播需显式处理。Spring Security 6.x 引入对 Scoped Valuesjava.lang.ScopedValue,JDK 虚拟线程时代的线程局部变量替代)的支持,作为虚拟线程下上下文传播的更优方案。协同机制:把 SecurityContext 作为 ScopedValue 绑定,在任务/虚拟线程中通过 Scope 传递,子线程(虚拟线程)能读取到父作用域的上下文,且不会像 ThreadLocal 那样泄漏/串扰(ScopedValue 有明确作用域,退出作用域即释放)。SecurityContextHolder 支持 MODE_INHERITABLETHREADLOCAL 让普通线程池子线程继承,但虚拟线程下推荐使用 ScopedValue 机制。工程上:虚拟线程 + 异步任务时,用 DelegatingSecurityContextExecutor 或把上下文放入 ScopedValue/显式传递,避免 ThreadLocal 在虚拟线程中的丢失、串扰与资源消耗。两者协同:SecurityContextHolder 提供统一 API(getContext/setContext),底层存储可切换为 ThreadLocal 或 ScopedValue,具体值由 SecurityContextHolderStrategy(如 ThreadLocalSecurityContextHolderStrategyScopedValueSecurityContextHolderStrategy)决定。

本题考察 SecurityContextHolder 与 ScopedValue 协同。核心是"ThreadLocal 不跨线程,ScopedValue 提供虚拟线程下的作用域传播"。回答应点出策略切换。

#
★★

31. Spring Security 的过滤器链顺序与扩展

请说明 Spring Security 的过滤器链(FilterChain)顺序与扩展方式?

  • 过滤器链的顺序
  • 自定义过滤器插入位置
  • 常见过滤器(认证、授权、CSRF)

Spring Security 通过 SecurityFilterChain 组织一组过滤器,按固定顺序执行(如 CsrfFilterUsernamePasswordAuthenticationFilterAuthorizationFilter 等)。顺序由 @Order 或框架内置顺序决定,关键原则:认证、CSRF、CORS 等前置过滤器先于授权过滤器,授权(AuthorizationFilter)在认证之后、最终执行。扩展方式:一是通过 http.addFilterBefore(filter, Class)/addFilterAfter/addFilterAt 把自定义过滤器插入指定位置(如自定义 JWT 过滤器放在 UsernamePasswordAuthenticationFilter 之前);二是实现 OncePerRequestFilter 保证一次请求只执行一次;三是通过 http.securityContext()http.csrf()http.cors() 等 DSL 配置内置过滤器。常见自定义:JWT 认证过滤器(自行解析 token 设置 SecurityContext)、日志过滤器、限流过滤器。扩展时注意:认证过滤器应放在授权之前,避免在未认证时执行授权;自定义过滤器要处理异常(设置 SecurityContext 或抛 AuthenticationException),并确保过滤器链顺序正确(多个自定义过滤器之间的先后)。

本题考察过滤器链顺序与扩展。核心是"内置过滤器顺序 + addFilterBefore/After 插入自定义过滤器"。回答应点出认证在授权前。

#
★★

32. UserDetailsService 的数据库实现

请说明 UserDetailsService 的数据库实现,以及如何加载用户信息用于认证?

  • UserDetailsService 接口
  • 数据库加载用户
  • 与 PasswordEncoder 配合

UserDetailsService 是 Spring Security 用于加载用户信息的接口,核心方法 loadUserByUsername(String username) 返回 UserDetails。数据库实现:自定义类实现 UserDetailsService,在 loadUserByUsername 中通过 JPA/MyBatis 查询数据库用户表,把用户实体映射为 UserDetails(可用 org.springframework.security.core.userdetails.User 或自定义 UserDetails 实现,包含用户名、加密密码、可用状态、角色/权限)。异常处理:用户不存在时抛 UsernameNotFoundException(Spring Security 认证流程捕获)。配合使用:UserDetailsServicePasswordEncoder 一起提供给 DaoAuthenticationProvider,认证时用 UserDetailsService 加载用户、用 PasswordEncoder.matches 校验密码。Spring Security 6 中,把 UserDetailsServicePasswordEncoder 声明为 Bean 即可自动装配。注意:loadUserByUsername 应返回完整权限(角色/权限),以便后续授权;数据库查询需考虑效率与缓存(如 Redis 缓存用户)。

本题考察 UserDetailsService 数据库实现。核心是"自定义 loadUserByUsername 查询数据库并映射 UserDetails"。回答应点出与 PasswordEncoder 配合。

@Service
public class DbUserDetailsService implements UserDetailsService {
    private final UserRepository repo;
    public UserDetails loadUserByUsername(String username) {
        User user = repo.findByUsername(username)
            .orElseThrow(() -> new UsernameNotFoundException("not found"));
        return org.springframework.security.core.userdetails.User
            .withUsername(user.getUsername())
            .password(user.getPassword())
            .roles(user.getRoles().toArray(String[]::new))
            .build();
    }
}
#
★★

33. WebSecurityCustomizer 与静态资源放行

请说明 WebSecurityCustomizer 的作用,以及如何放行静态资源?

  • WebSecurityCustomizer 的作用
  • 静态资源放行配置
  • 与安全过滤链的区分

WebSecurityCustomizer 是 Spring Security 用于对 WebSecurity 做"全局"配置的接口,常用来放行静态资源(css/js/image)等不需要认证的请求。用法:定义一个 WebSecurityCustomizer Bean(或 lambda),调用 web.ignoring().requestMatchers("/css/**", "/js/**", "/images/**", "/favicon.ico"),这些请求将完全绕过安全过滤器链(不经过认证/授权/CSRF)。其中 ignoring() 是该 customizer 最常用的场景。注意区分:WebSecurityCustomizerignoring() 是"完全绕过过滤器链",而 SecurityFilterChain 中的 requestMatchers(...).permitAll() 是"进入过滤器链但放行"(仍会经过部分过滤器,如 securityContext、CSRF 可能处理)。对静态资源一般用 ignoring() 提升性能;但若静态资源需要 HTTP 级保护(如缓存头、安全头),放行时需权衡。Spring Security 6 中推荐用 WebSecurityCustomizer 或直接在 SecurityFilterChain 中放行静态资源,两者都支持。

本题考察 WebSecurityCustomizer 与静态资源放行。核心是"ignoring() 完全绕过过滤器链放行静态资源"。回答应点出与 permitAll 的区分。

#

34. 使用 Spring Security 6.x 的 SecurityMatcher 与多套 FilterChain 时请求匹配优先级的判定

请说明使用 Spring Security 6.x 的 SecurityMatcher 与多套 FilterChain 时请求匹配优先级的判定?

  • SecurityMatcher 的匹配
  • 多 FilterChain 的优先级
  • 匹配算法(MVC/ant)

多套 SecurityFilterChain 时,请求按链的注册顺序逐个与各链的 SecurityMatcher 匹配,第一个匹配的链生效,后续链不执行。SecurityMatcher 是匹配器抽象(如 MvcRequestMatcherAntPathRequestMatcherPathPatternRequestMatcher),决定请求是否命中该链。优先级判定:通过 @Order 或 Bean 注入顺序控制链的顺序,更精确/更特殊的匹配器应放在前面,最通用的(anyRequest())放最后兜底。若第一个匹配的链不匹配,则尝试下一个链,直到找到匹配链;若所有链都不匹配,则默认拒绝(或无链处理)。Spring Security 6 在启动时要求链顺序固定,避免歧义。匹配时注意 MvcRequestMatcher 与 Spring MVC 路由的匹配语义(基于 HandlerMappingIntrospector),对带路径变量的 URL 需匹配正确。设计上:先写对特殊路径(如 /api/**/admin/**)的链,再写兜底链。

本题考察多链匹配优先级。核心是"第一条匹配链生效、特殊链在前"。回答应点出 SecurityMatcher 与 @Order。

#

35. 在 K8s 中通过 Spring Security 6.4 读取 ServiceAccount Token 实现工作负载身份认证的机制

请说明在 K8s 中通过 Spring Security 6.4 读取 ServiceAccount Token 实现工作负载身份认证的机制?

  • ServiceAccount Token 的注入
  • 读取与验证 token
  • 工作负载身份认证

K8s 中每个 Pod 挂载一个 ServiceAccount Token(JWT,默认位于 /var/run/secrets/kubernetes.io/serviceaccount/token),由集群签名。工作负载身份认证机制:Spring 应用读取该 token 文件,向 K8s API Server 或信任该 token 的服务验证,从而识别其身份(ServiceAccount)。实现方式:Spring Security 6.4 可配置 JwtDecoder 用 K8s 集群的公钥(JWKS 端点)验证 ServiceAccount Token 的签名,把 token 解析为 Authentication;或集成 Spring Cloud 的 K8s 工作负载身份(如 Azure 工作负载身份、GCP 工作负载身份)通过 token 交换获取云凭证。关键点:读取 token 文件(java.nio.file.Files.readString(Path.of("/var/run/secrets/kubernetes.io/serviceaccount/token"))),配置验签密钥(K8s 的 service-account-signing-key 对应公钥),校验 isshttps://kubernetes.default.svc)、audexp。工程价值:服务间无需共享密钥,用 K8s 提供的身份凭证实现服务间认证,替代传统 client secret。也可用于从集群内访问云服务(工作负载身份联合)。

本题考察 K8s ServiceAccount token 认证。核心是"读取挂载的 token、验签认证工作负载身份"。回答应点出读取与验签流程。

#

36. 多个 SecurityFilterChain 同时匹配请求时为何只使用第一条链,securityMatcher 与 requestMatchers 如何分工

请说明多个 SecurityFilterChain 同时匹配请求时为何只使用第一条链,以及 securityMatcher 与 requestMatchers 如何分工?

  • 只使用第一条匹配链的原因
  • securityMatcher 与 requestMatchers 的分工
  • 设计意图

当多个 SecurityFilterChain 同时匹配一个请求时,Spring Security 只使用第一条(按顺序最先匹配的)链,原因是:每个链代表一组完整的、独立的安全策略(过滤器集合),若执行多条链会导致过滤器重复执行、授权决策冲突、CSRF/CORS 处理重复,语义混乱。因此框架设计为"单链匹配"——请求命中最先匹配的链后,其余链不再参与。securityMatcherrequestMatchers 的分工:securityMatcher()http.securityMatcher(...))用于决定整个 chain 是否匹配该请求(属于哪条链),是"链级选择器";requestMatchers(...) 在授权 DSL(authorizeHttpRequests())中用于在该链内为具体路径配置授权规则,是"请求级授权规则"。前者管"用哪条链处理",后者管"请求在链内如何授权"。所以设计时:用 securityMatcher 划分不同链的职责范围,用 requestMatchers 细化链内规则。

本题考察单链匹配与匹配器分工。核心是"只执行第一条链避免重复/冲突,securityMatcher 选链、requestMatchers 定规则"。回答应点出分工。

#

37. 多租户系统如何按 issuer 或请求域选择 AuthenticationManager,同时阻止租户间密钥混用

请说明多租户系统如何按 issuer 或请求域选择 AuthenticationManager,同时阻止租户间密钥混用?

  • 按租户选择认证管理器
  • issuer/域路由
  • 密钥隔离

多租户系统中,不同租户可能有独立的 issuer/IdP/密钥,需按请求选择正确的 AuthenticationManager。实现:根据请求的 Host(域)或 token 的 iss 声明路由——自定义 AuthenticationManagerResolverRequestMatcherDelegatingAuthenticationManagerResolver 或自定义 AuthenticationManagerResolver<HttpServletRequest>),根据请求域/路径返回对应租户的 AuthenticationManager;对 JWT 场景,按 iss 选择对应的 JwtDecoder(每个租户一个 decoder,配置各自租户的 JWKS/密钥)。阻止租户间密钥混用:每个租户的密钥/decoder 严格隔离,路由时只使用该租户的密钥验证 token,绝不 fallback 到其他租户的密钥;iss 校验确保 token 的签发者与该租户匹配(防止用 A 租户签发的 token 访问 B 租户资源);aud 校验限定服务。实现上可用 Map<tenantId, AuthenticationManager> 配合 resolver,或 JwtIssuerAuthenticationManagerResolver(按 iss 自动选择 decoder)。密钥存储也需隔离(不同 KMS key/租户),避免共享密钥。

本题考察多租户认证。核心是"按域/iss 路由选择 manager/decoder,密钥严格隔离"。回答应点出 resolver 与 iss 校验。

#

38. 异常响应如何区分未认证与无权限,又避免向攻击者泄露账号、租户或权限策略细节

请说明异常响应如何区分未认证与无权限,又避免向攻击者泄露账号、租户或权限策略细节?

  • 401 vs 403 的区分
  • 异常信息脱敏
  • 避免泄露细节

未认证(AuthenticationException)与无权限(AccessDeniedException)应区分响应:未认证返回 401AuthenticationEntryPoint 处理,提示需登录),无权限返回 403AccessDeniedHandler 处理,提示无权限)。同时要避免向攻击者泄露细节:一是泛化错误信息——返回给客户端的 401/403 消息应是通用提示(如"未认证"/"无权限"),不包含具体账号是否存在、用户是否被锁定、租户配置、权限策略(如"需要 ROLE_ADMIN")等内部细节;二是不要泄露账号枚举——错误提示不要区分"用户名不存在"与"密码错误"(统一为"用户名或密码错误"),避免探测合法账号;三是日志与响应分离——详细的内部错误(堆栈、具体原因)写入服务端日志,客户端只收到通用消息;四是统一异常处理——用 @ControllerAdvice 或自定义 AuthenticationEntryPoint/AccessDeniedHandler 统一格式,避免默认异常堆栈泄漏。避免泄露的目的:防止攻击者利用差异做账号枚举、租户探测、权限策略探测。

本题考察异常响应的信息泄露防护。核心是"401/403 区分 + 客户端消息泛化 + 细节只进日志"。回答应点出泛化与脱敏。

#

39. 方法级安全依赖代理时,自调用为何可能绕过 @PreAuthorize,接口与实现注解应如何统一

请说明方法级安全依赖代理时,自调用为何可能绕过 @PreAuthorize,以及接口与实现注解应如何统一?

  • 代理机制与方法级安全
  • 自调用绕过问题
  • 注解位置(接口 vs 实现)

方法级安全(@PreAuthorize)通过 AOP 代理实现:调用方通过代理对象进入方法时,代理会先执行授权检查。但自调用this.method() 在同一个类内部调用)走的是 this 引用,而不是代理对象,因此会绕过代理@PreAuthorize 不生效,造成授权被绕过。解决:一是避免自调用(通过注入自身代理或拆分类);二是用 AopContext.currentProxy() 获取代理再调用;三是把方法调用放到不同 Bean 中。关于注解位置:Spring 官方建议把方法级安全注解放在具体实现类上而不放在接口上(或接口与实现都放但保持一致),因为基于 JDK 动态代理时,注解在接口上可能不被代理方法正确识别(Spring AOP 基于实现类方法)、且实现类注解更明确;若接口与实现都加了注解,需保持一致,避免规则冲突或遗漏。工程上:注解统一放在实现类方法上,并确保方法通过代理调用(避免自调用),保证授权可靠执行。

本题考察自调用绕过与注解位置。核心是"自调用绕过代理、注解放实现类并保持一致"。回答应点出代理机制与注解规范。

#

40. PasswordEncoder 的升级与历史密码

请说明 PasswordEncoder 的升级与历史密码的处理方式?

  • 算法升级策略
  • 历史密码兼容
  • DelegatingPasswordEncoder 与迁移

密码哈希算法升级(如从 SHA-256 换到 BCrypt/Argon2)时,历史密码已用旧算法存储,直接切换会导致旧密码无法校验。处理方式:用 DelegatingPasswordEncoder 支持多算法并存——默认编码器用新算法(如 {bcrypt}),同时保留旧算法编码器(如 {sha256})用于校验历史哈希;存储的哈希带 {id} 前缀标识算法。matches 时根据前缀选择对应编码器校验,encode 时统一用新默认算法。升级流程:用户登录成功时,若检测到存储哈希不是新算法(前缀不是默认),则用新算法重新编码明文密码并写回(配合 UserDetailsPasswordService),实现"登录即升级"——旧密码在校验通过后逐步迁移到新算法,无需强制重置。历史密码的兼容期:在迁移完成前,旧算法编码器必须保留;迁移完成后可逐步移除旧编码器。注意:matches 校验必须成功,升级写回失败不应阻断认证。工程上这正是为何推荐一开始就用 PasswordEncoderFactories.createDelegatingPasswordEncoder()

本题考察密码算法升级。核心是"DelegatingPasswordEncoder 多算法并存 + 登录即升级"。回答应点出前缀与迁移流程。

#

41. Spring Security 6.x 中基于 GraalVM Native Image 反射配置需要哪些元数据声明

请说明 Spring Security 6.x 中基于 GraalVM Native Image 反射配置需要哪些元数据声明?

  • GraalVM Native Image 的反射限制
  • Spring Security 的反射元数据
  • 需要声明的类/方法

GraalVM Native Image 在构建时做静态分析,运行时反射、动态代理、JNI 等默认不可用,需通过元数据声明(reflect-config.jsonproxy-config.jsonresource-config.json)显式注册。Spring Security 6.x 依赖反射与动态代理(如 @PreAuthorize 的 AOP 代理、MethodSecuritySecurityMetadataSourceUserDetails 的反射、Crypto 的算法类),在 Native Image 下需声明:一是反射元数据——UserDetails/自定义实现类、GrantedAuthority 实现、Authentication 实现、PasswordEncoder 实现(如 BCryptPasswordEncoder)、SpringSecurity 内部通过反射访问的类(MethodInvocationMethodSecurityMetadataSource 相关)需注册 reflect-config.json;二是动态代理元数据——@Configuration 的 AOP 代理、securityProxy 接口需注册 proxy-config.json;三是资源元数据——META-INF/services 的 Provider 声明、spring-security 内部的资源配置(如 messages.properties)需 resource-config.json。工程上常用 spring-boot-starter 的 GraalVM 支持(AOT 处理)自动生成部分元数据,或用 @RegisterReflectionForBinding/@Aot 处理,剩余手工补充。核心是"把反射/代理/资源涉及的类显式声明"。

本题考察 Native Image 的反射配置。核心是"声明反射/动态代理/资源元数据"。回答应点出 Spring Security 涉及的类类型。