面试知识

Spring Security(安全框架)认证、授权与过滤器链

22-Spring生态与后端工程 面试知识整理。

Spring Security(安全框架)认证、授权与过滤器链

本文把安全问题拆成四个不能混淆的判断:请求是谁、凭证是否可信、这个身份能做什么、这个业务对象此刻是否允许被操作。Authentication(认证)回答“是谁”,Authorization(授权)回答“能否做”,领域规则回答“对哪条订单、哪个仓库、什么状态能否做”。网关校验不等于服务领域授权,签名不等于加密,JWT(JSON 网络令牌)无状态不等于系统没有服务端状态,前端隐藏按钮更不等于权限控制。

flowchart LR
    R["HTTP(超文本传输协议)请求"] --> C["凭证、来源、租户和业务对象"]
    C --> A["Authentication(认证):确认身份"]
    A --> Z["Authorization(授权):检查能力"]
    Z --> D["领域授权:检查对象归属和状态"]
    D --> B["执行业务并形成审计事实"]
    A -->|"身份失败"| U["401 未认证"]
    Z -->|"权限不足"| F["403 禁止访问"]
    D -->|"对象越权或状态非法"| F

Spring Security(安全框架)过滤器链时序

判断层主要问题可信输入失败结果不能替代
边界防护请求是否来自允许来源网络、证书、签名、速率拒绝或限流用户身份
Authentication(认证)调用者是谁密码、会话、Token(令牌)、客户端凭证401业务授权
Authorization(授权)身份能调用什么能力权限、角色、作用域403对象归属
领域授权能否操作这条业务数据租户、仓库、货主、订单状态403 或业务拒绝数据库约束
数据约束并发和绕过时仍否正确唯一键、条件更新、状态机事务失败入口安全

1. DelegatingFilterProxy(委派过滤器代理)到 SecurityFilterChain(安全过滤器链)的装配与选择

Servlet(服务器小程序)容器首先调用 DelegatingFilterProxy(委派过滤器代理),它不实现认证逻辑,而是按名称取得 Spring(Java 应用框架)容器中的 FilterChainProxy(过滤器链代理)。FilterChainProxy(过滤器链代理)持有多条 SecurityFilterChain(安全过滤器链),按配置顺序选择第一条请求匹配器命中的链,再按固定顺序执行其中过滤器。静态资源、管理接口、网页接口和 API(应用程序接口)可以有不同链,但匹配范围重叠会导致“看似配置了、实际没执行”。过滤器顺序是协议:上下文必须先加载,认证先于授权,ExceptionTranslationFilter(异常转换过滤器)要包围可能抛安全异常的下游,最后必须保存或清理上下文。

组件所属边界核心职责常见错误
DelegatingFilterProxy(委派过滤器代理)Servlet(服务器小程序)容器桥接容器过滤器与 Spring(Java 应用框架)对象误以为它直接认证
FilterChainProxy(过滤器链代理)安全框架选择安全链、组织防火墙和清理多链顺序错误
SecurityFilterChain(安全过滤器链)单类请求保存匹配器和有序过滤器宽匹配抢先命中
业务 Filter(过滤器)具体协议提取凭证、认证或授权随意插入顺序
flowchart TD
    S["Servlet(服务器小程序)容器"] --> D["DelegatingFilterProxy(委派过滤器代理)"]
    D --> P["FilterChainProxy(过滤器链代理)"]
    P --> M1{"/actuator/** 命中?"}
    M1 -->|"是"| C1["管理端 SecurityFilterChain(安全过滤器链)"]
    M1 -->|"否"| M2{"/api/** 命中?"}
    M2 -->|"是"| C2["令牌 SecurityFilterChain(安全过滤器链)"]
    M2 -->|"否"| C3["网页会话 SecurityFilterChain(安全过滤器链)"]
    C1 --> O["按链内顺序执行"]
    C2 --> O
    C3 --> O

数据演绎 1:多链抢占。 系统配置顺序为 /**/api/**/actuator/**。请求 /api/orders/9 在第一条即匹配,后面的 JWT(JSON 网络令牌)链永远没有机会执行,匿名规则可能放行敏感订单。把专用链按“管理端 -> API(应用程序接口)-> 默认网页”排序,并为每条链建立命中与不命中样本,才能证明配置生效。

热门面试题

  1. 问题(基础题):DelegatingFilterProxy(委派过滤器代理)和 FilterChainProxy(过滤器链代理)有什么区别?

    • 考点:容器桥接与安全链编排。
    • 回答思路:先讲所属容器,再讲是否包含安全规则。
    • 详细答案:DelegatingFilterProxy(委派过滤器代理)注册在 Servlet(服务器小程序)容器中,只负责寻找并调用 Spring(Java 应用框架)对象;FilterChainProxy(过滤器链代理)才属于 Spring Security(安全框架),负责选择 SecurityFilterChain(安全过滤器链)、执行过滤器并在结束时清理安全上下文。前者解决生命周期桥接,后者解决安全协议编排。
    • 进阶追问:为什么不把全部安全逻辑直接写进 DelegatingFilterProxy(委派过滤器代理)?
    • 进阶回答:那会把 Servlet(服务器小程序)容器注册和业务安全配置耦合,失去依赖注入、多链组合、统一生命周期与可测试性。
  2. 问题(原理题):多条 SecurityFilterChain(安全过滤器链)怎样选择?

    • 考点:首个匹配与顺序风险。
    • 回答思路:说明按顺序匹配且不会合并执行。
    • 详细答案:FilterChainProxy(过滤器链代理)按配置顺序检查请求匹配器,使用第一条命中的 SecurityFilterChain(安全过滤器链)。它不会把多条链合并,因此宽范围链放在前面会遮蔽后续专用链。配置验收要覆盖正向、反向和重叠路径,并观察实际命中链与过滤器清单。
    • 进阶追问:两条链都匹配时会执行两遍吗?
    • 进阶回答:不会,正常模型只选第一条;重复执行通常来自重复注册代理或应用自身再次调用链。
  3. 问题(项目题):如何隔离管理端、支付回调和普通用户接口?

    • 考点:不同信任模型与最小权限。
    • 回答思路:按入口协议拆链,再回到服务内领域校验。
    • 详细答案:管理端使用专用网络、强认证和运维权限链;支付回调按渠道路径使用验签、防重放和来源约束,不套用户登录;普通接口使用会话或 JWT(JSON 网络令牌)。三条链各自最小化开放端点,但最终仍由服务方法校验商户、订单、金额和状态,入口通过不等于资金动作自动合法。
    • 进阶追问:网关已经验过 JWT(JSON 网络令牌),服务还要配置安全链吗?
    • 进阶回答:要。网关可做粗粒度拒绝和流量治理,服务仍需防绕过、验证可信转发信息并实施方法和对象级授权。

2. SecurityContext(安全上下文)的加载、保存、清理与匿名身份

SecurityContext(安全上下文)保存当前 Authentication(认证结果),通常由 SecurityContextHolder(安全上下文持有器)绑定到当前线程。请求开始时,安全上下文过滤器从 SecurityContextRepository(安全上下文仓库)加载;认证成功后写入;会话模型可在响应前持久化,纯令牌模型通常每次重建;请求结束必须在 finally 清理。AnonymousAuthenticationToken(匿名认证令牌)不是“已登录用户”,它只是让授权表达式统一处理匿名调用。JWT(JSON 网络令牌)无状态只表示不依赖登录会话保存身份,不代表没有撤销表、刷新记录、密钥、用户状态和审计等服务端状态。

阶段会话模型令牌模型关键风险
请求开始通过会话标识读取上下文解析并校验 Token(令牌)后重建伪造或过期
请求执行线程内读取 Authentication(认证结果)同左异步切线程丢失
响应结束必要时保存到会话通常不保存请求上下文忘记清理串用户
注销失效会话撤销刷新记录或加入拒绝策略访问令牌仍有效
sequenceDiagram
    participant R as 请求
    participant Repo as SecurityContextRepository(安全上下文仓库)
    participant H as SecurityContextHolder(安全上下文持有器)
    participant B as 业务线程
    R->>Repo: 加载会话或空上下文
    Repo-->>H: 绑定当前身份
    H->>B: 提供 Authentication(认证结果)
    alt 认证状态变化
        B->>Repo: 保存新上下文
    end
    B-->>R: 返回响应
    R->>H: finally(最终清理)

数据演绎 2:线程复用串号。 线程池只有 8 个工作线程。请求 A 在 worker-3 写入用户 u100 后未清理;请求 B 恰好复用 worker-3 且本身无有效凭证,如果代码直接读取残留 SecurityContext(安全上下文),就可能以 u100 查询订单。正确链路必须在每个请求建立新上下文,并在所有成功、异常、超时路径清理。

热门面试题

  1. 问题(基础题):SecurityContext(安全上下文)里保存什么?

    • 考点:当前身份与请求作用域。
    • 回答思路:说明 Authentication(认证结果)及其生命周期。
    • 详细答案:SecurityContext(安全上下文)主要保存当前 Authentication(认证结果),其中包含主体、凭证处理后的状态和权限集合。它是当前执行上下文的身份快照,不是用户数据库;请求结束要保存或丢弃并清理线程绑定,不能被业务当成永久用户事实。
    • 进阶追问:Authentication(认证结果)中的凭证应一直保留吗?
    • 进阶回答:不应。成功认证后通常擦除密码等敏感凭证,只保留完成授权所需的主体和权限摘要。
  2. 问题(原理题):为什么必须在 finally 清理安全上下文?

    • 考点:线程池复用和数据泄露。
    • 回答思路:从线程本地存储生命周期解释。
    • 详细答案:服务器线程会复用,而线程绑定的 SecurityContext(安全上下文)不会因业务方法返回自动消失。若异常、超时或异步分支遗漏清理,下一请求可能读到前一用户身份,形成严重越权。安全框架在过滤器链外层统一清理,业务自建线程也必须按捕获、设置、执行、恢复或清理的协议处理。
    • 进阶追问:使用 JWT(JSON 网络令牌)就不会串号吗?
    • 进阶回答:仍可能。JWT(JSON 网络令牌)只改变身份来源,解析后的身份依然会绑定执行上下文,遗漏清理同样危险。
  3. 问题(故障题):偶发用户看到别人的仓库数据,怎么排查?

    • 考点:上下文、租户条件和缓存键证据。
    • 回答思路:同时检查身份传播、数据过滤和缓存隔离。
    • 详细答案:先冻结请求标识、线程名、用户、租户、仓库和 SQL(结构化查询语言)条件,确认是上下文串号、租户条件遗漏还是缓存键缺租户。再检查自定义过滤器异常路径、异步执行器包装和线程本地变量清理。修复后用同一线程交替执行不同租户请求,验证上下文、查询条件、缓存键和审计主体全部切换正确。
    • 进阶追问:只给缓存键加用户编号够吗?
    • 进阶回答:不一定。授权通常由租户、仓库、货主和角色共同决定,应按数据所有权设计键与失效范围,不能以用户编号替代领域边界。

3. AuthenticationManager(认证管理器)、ProviderManager(认证提供者管理器)与密码升级

认证过滤器只负责从请求提取凭证并构造“未认证”的 Authentication(认证请求)。AuthenticationManager(认证管理器)定义认证入口,常见 ProviderManager(认证提供者管理器)按顺序选择 supports(支持)当前凭证类型的 AuthenticationProvider(认证提供者),例如用户名密码、一次性口令或外部身份。提供者加载用户、检查禁用/锁定/过期状态,再用 PasswordEncoder(密码编码器)比较自适应哈希。密码不能明文、可逆加密或无盐快速哈希存储;应使用 BCrypt(自适应密码哈希算法)、PBKDF2(基于密码的密钥派生函数)、scrypt(内存困难密码派生函数)或 Argon2(内存困难密码哈希算法),并通过 DelegatingPasswordEncoder(委派密码编码器)在成功登录时渐进升级成本。

环节输入成功输出失败边界
过滤器请求字段未认证 Authentication(认证请求)格式错误
ProviderManager(认证提供者管理器)凭证对象委派到支持的提供者无提供者支持
用户加载用户标识状态与密码摘要不存在、锁定
密码比较明文输入与摘要常数语义的匹配结果成本过低或资源攻击
摘要升级旧算法标识新摘要更新并发失败
flowchart TD
    F["认证 Filter(过滤器)"] --> T["未认证 Authentication(认证请求)"]
    T --> M["ProviderManager(认证提供者管理器)"]
    M --> P1{"Provider(提供者)支持此类型?"}
    P1 -->|"否"| P2["检查下一个 Provider(提供者)"]
    P1 -->|"是"| U["加载用户与状态"]
    U --> E["PasswordEncoder(密码编码器)比较"]
    E -->|"失败"| X["认证异常"]
    E -->|"成功"| G["已认证 Authentication(认证结果)"]
    G --> R{"摘要成本需要升级?"}
    R -->|"是"| W["重新编码并条件更新"]
    R -->|"否"| O["返回身份"]
    W --> O

数据演绎 3:密码渐进升级。 用户表保存 {bcrypt}$2a$10$...,当前策略为成本 12。用户 u200 登录时先按记录前缀选择旧编码器验证;成功后检测 upgradeEncoding=true,使用新随机盐生成成本 12 摘要,并按“用户编号+旧摘要”条件更新。并发登录只有一个更新成功,另一个无需失败登录;整个过程从不解密旧密码。

热门面试题

  1. 问题(基础题):AuthenticationManager(认证管理器)和 AuthenticationProvider(认证提供者)如何分工?

    • 考点:统一入口与策略实现。
    • 回答思路:用策略链说明一种入口、多种凭证。
    • 详细答案:AuthenticationManager(认证管理器)向上提供统一认证契约,ProviderManager(认证提供者管理器)是常用组合实现;AuthenticationProvider(认证提供者)针对特定 Authentication(认证请求)类型完成用户加载与凭证校验。这样过滤器不依赖密码、一次性口令或外部身份的具体算法。
    • 进阶追问:多个提供者都声明支持会怎样?
    • 进阶回答:按配置顺序尝试,成功即返回;异常是否继续取决于类型和实现,所以支持范围要互斥且顺序需要测试。
  2. 问题(原理题):为什么密码哈希要“慢”和带随机盐?

    • 考点:离线破解成本。
    • 回答思路:区分正常登录成本与攻击者批量猜测成本。
    • 详细答案:数据库泄露后攻击者可离线猜密码。随机盐让相同密码产生不同摘要并破坏预计算彩虹表;自适应的计算或内存成本把每次猜测变贵。成本要按生产硬件压测,使单次认证可接受,同时配合限流,避免攻击者反向制造计算资源耗尽。
    • 进阶追问:还需要额外 Pepper(服务端秘密)吗?
    • 进阶回答:可作为纵深防御,但必须存于独立密钥系统并可轮换;它不能替代强密码算法、随机盐和数据库访问控制。
  3. 问题(项目题):如何平滑升级旧密码算法?

    • 考点:兼容、并发和回退。
    • 回答思路:记录算法标识,登录成功后重编码。
    • 详细答案:摘要带算法前缀,认证时按前缀选择兼容编码器;旧摘要验证成功且成本不足时,用本次明文立即生成新摘要,并以旧摘要作条件更新,避免并发覆盖。升级失败不应把已成功认证改成失败,但要记录指标;长期未登录账户可在下次登录升级或要求重置,绝不能批量“解密迁移”。
    • 进阶追问:算法升级后能立即删除旧编码器吗?
    • 进阶回答:不能,仍有未登录账户依赖旧摘要;应按存量占比、重置策略和回滚窗口分阶段移除。

4. Session(会话)、Remember-Me(记住登录)与并发会话治理

Session(会话)模型把登录状态保存在服务端或共享会话仓库,浏览器只持有不可预测的会话标识。登录后应轮换会话标识防止 Session Fixation(会话固定攻击);退出、改密、冻结账户和高风险操作可以立即失效会话。Remember-Me(记住登录)是长期恢复登录的独立凭据,不等于延长原会话,必须可撤销、轮换并限制权限。集群部署要选择共享会话、粘性路由或令牌模型,并明确故障时的一致性。并发会话限制依赖会话注册信息,不能只在单个节点内计数。

能力Session(会话)Remember-Me(记住登录)风险控制
生命周期分钟到小时天到月分开过期
服务端撤销直接删除或标记删除持久令牌族立即生效
权限强度正常已登录应降权敏感操作再认证
集群要求共享仓库或路由共享持久记录节点一致
stateDiagram-v2
    [*] --> 匿名
    匿名 --> 已认证: 登录成功并轮换会话标识
    已认证 --> 活跃: 请求刷新活跃时间
    活跃 --> 再认证: 支付或改密等敏感动作
    再认证 --> 活跃: 凭证确认
    活跃 --> 已撤销: 退出/冻结/改密
    活跃 --> 已过期: 空闲或绝对期限到达
    已撤销 --> 匿名
    已过期 --> 匿名

数据演绎 4:并发会话。 用户 u300 在上海登录产生会话 s1,一分钟后在深圳登录产生 s2,策略只允许一个会话。若采用“新登录踢旧登录”,共享注册表把 s1 标记过期;s1 下次请求返回重新登录,s2 保持有效。若注册表只在深圳节点,上海节点不知道撤销,限制便形同虚设。

热门面试题

  1. 问题(基础题):Session(会话)认证的核心状态在哪里?

    • 考点:服务端状态与客户端标识。
    • 回答思路:说明浏览器只携带索引,不携带完整身份事实。
    • 详细答案:核心身份状态保存在服务端会话或共享会话仓库,客户端 Cookie(浏览器小型数据)通常只携带随机会话标识。服务端可立即撤销并控制并发,但需要处理共享存储、过期、固定攻击和跨节点一致性。
    • 进阶追问:会话标识需要加密吗?
    • 进阶回答:首要是高熵不可预测、通过 HTTPS(安全超文本传输协议)传输并设置安全属性;即使加密,也不能替代服务端校验和轮换。
  2. 问题(原理题):为什么登录成功要更换会话标识?

    • 考点:Session Fixation(会话固定攻击)。
    • 回答思路:解释攻击者预先控制标识的攻击路径。
    • 详细答案:若匿名阶段的会话标识在登录后继续使用,攻击者可能诱导受害者使用自己已知的标识,等受害者登录后再复用该标识劫持身份。成功认证时轮换标识,同时迁移必要属性,使攻击者掌握的旧标识失效。
    • 进阶追问:只清空旧会话属性可以吗?
    • 进阶回答:不够,攻击者知道的仍是同一个标识;必须改变标识或建立新的会话。
  3. 问题(项目题):后台管理系统为什么更适合会话而非长 JWT(JSON 网络令牌)?

    • 考点:撤销、风险和运维体验。
    • 回答思路:从即时冻结和低并发管理入口决策。
    • 详细答案:后台权限高、用户量相对有限,账户冻结、离职和异常登录需要立即撤销;会话能服务端集中失效并限制并发。JWT(JSON 网络令牌)也能使用,但仍要引入撤销或短有效期,复杂度未必更低。无论选型,WMS(仓储管理系统)仓库和货主权限仍要在服务方法校验。
    • 进阶追问:共享会话仓库故障怎么办?
    • 进阶回答:管理写操作应倾向失败关闭并保留只读降级;同时配置超时、容量、复制和应急重新登录,而不是绕过认证。

5. JWT(JSON 网络令牌)、访问令牌、刷新令牌与撤销轮换

JWT(JSON 网络令牌)是承载声明并由签名保护完整性的 Token(令牌)格式,不天然加密内容,也不是完整登录协议。服务必须验证算法白名单、签名、签发者、受众、到期时间、生效时间、令牌类型和业务必需声明,不能只做 Base64(基础六十四编码)解码。短期 Access Token(访问令牌)用于访问资源,长期 Refresh Token(刷新令牌)只用于授权服务器换取新令牌;刷新时采用 Rotation(轮换),旧刷新令牌一经使用立即失效,若再次出现则撤销整个令牌族。注销、账号冻结和高风险事件需要服务端撤销状态,因此“无会话”不等于“零服务端状态”。签名证明内容未被未知方篡改,不提供保密性,敏感数据仍需 HTTPS(安全超文本传输协议)和最小声明。

对象建议寿命保存位置撤销策略主要风险
Access Token(访问令牌)5 至 15 分钟内存或安全 Cookie(浏览器小型数据)短期等待或拒绝表窃取后重放
Refresh Token(刷新令牌)数天至数周安全存储轮换、令牌族撤销长期控制账户
签名密钥按策略轮换密钥管理系统双密钥窗口泄露或误删
权限声明不超过令牌寿命Token(令牌)内缩短寿命或在线复核权限变更滞后
sequenceDiagram
    participant C as 客户端
    participant A as 授权服务
    participant R as 资源服务
    participant S as 撤销与令牌族存储
    C->>A: 登录凭证
    A-->>C: 短访问令牌 A1 + 刷新令牌 R1
    C->>R: 携带 A1
    R-->>C: 资源响应
    C->>A: 使用 R1 刷新
    A->>S: 原子标记 R1 已使用
    A-->>C: A2 + R2
    alt R1 再次出现
        C->>A: 重放 R1
        A->>S: 撤销令牌族 R1/R2
        A-->>C: 拒绝并要求重新认证
    end

数据演绎 5:刷新令牌重放。 09:00 签发访问令牌 a1,到期 09:10;刷新令牌 r1 到期七天。09:08 客户端用 r1 换得 a2/r2,存储原子记录 r1=used。攻击者在 09:09 再用窃取的 r1,系统识别重复使用,撤销族编号 family-77,使 r2 也失效并触发重新登录;若只返回失败而不撤销 r2,无法判断哪一方持有合法令牌。

flowchart TD
    T["收到 JWT(JSON 网络令牌)"] --> H["解析头部并按白名单选择算法"]
    H --> K["按密钥标识取得可信公钥"]
    K --> S{"签名有效?"}
    S -->|"否"| X["401 未认证"]
    S -->|"是"| C{"签发者、受众、时间和类型有效?"}
    C -->|"否"| X
    C -->|"是"| V{"账户、令牌族或撤销状态允许?"}
    V -->|"否"| X
    V -->|"是"| A["建立最小 Authentication(认证结果)"]

热门面试题

  1. 问题(基础题):JWT(JSON 网络令牌)签名和加密有什么区别?

    • 考点:完整性与保密性。
    • 回答思路:明确签名不能隐藏载荷。
    • 详细答案:签名让接收方验证内容由持有密钥的一方产生且未被篡改,但载荷通常可被任何拿到令牌的人解码;加密才隐藏内容。JWT(JSON 网络令牌)常见签名令牌不能放密码、卡号或隐私明文,仍必须通过 HTTPS(安全超文本传输协议)传输并限制日志输出。
    • 进阶追问:把用户权限写入签名令牌就永远可信了吗?
    • 进阶回答:只在令牌验证和有效期内可作签发时快照;权限已撤销、账户冻结或对象归属变化仍可能要求在线复核。
  2. 问题(原理题):JWT(JSON 网络令牌)为什么仍需要服务端状态?

    • 考点:撤销、轮换和动态权限。
    • 回答思路:区分无会话与无治理状态。
    • 详细答案:资源请求可不读取登录会话,但系统仍保存签名密钥、刷新令牌族、撤销记录、用户冻结状态、客户端配置和审计。完全不保留这些状态就无法立即登出、检测刷新重放或响应密钥泄露,因此所谓无状态只缩小了每次请求的会话依赖。
    • 进阶追问:短访问令牌还需要黑名单吗?
    • 进阶回答:取决于风险和即时撤销要求;低风险可接受短窗口,高风险资金或后台操作可在线检查会话版本或撤销标识。
  3. 问题(项目题):如何设计移动端令牌刷新避免并发风暴?

    • 考点:单飞、原子轮换和失败恢复。
    • 回答思路:客户端合并刷新,服务端保证一次性。
    • 详细答案:客户端同一账户只允许一个刷新请求,其余请求等待结果;服务端按刷新令牌摘要和族编号原子消费,成功后返回新对。并发第二次使用旧令牌要区分同设备网络重试和真实重放,可用极短幂等窗口绑定请求摘要,但不能无限复用。失败后停止业务重试并重新认证,避免把 401 放大成请求风暴。
    • 进阶追问:刷新令牌可以直接调用业务接口吗?
    • 进阶回答:不可以,它只面向严格限定的刷新端点,权限、受众和类型都应与访问令牌隔离。

6. AuthorizationManager(授权管理器)与 URL(统一资源定位符)、方法、对象级权限

AuthorizationManager(授权管理器)把当前 Authentication(认证结果)与被保护对象组合成授权决策。URL(统一资源定位符)规则适合入口粗筛,方法授权适合稳定服务能力,领域对象授权必须校验 tenantIdwarehouseIdownerId、订单归属和状态,并让查询本身带上数据边界。角色是权限集合的组织方式,不应在业务中到处写死。前端隐藏按钮只改善体验,攻击者可以直接构造请求;网关认证只确认入口 Token(令牌),不能知道服务内部订单是否属于该货主。授权规则采用默认拒绝、最小权限和职责分离;资金审核、退款和仓库调整还需要状态机、额度、双人复核与数据库约束。

层级适合规则证据典型反例
网关公开/内部、粗粒度作用域路由和客户端身份代替订单归属
URL(统一资源定位符)端点是否需认证请求方法与路径只隐藏前端按钮
方法是否可执行退款、调整库存权限与参数控制器有校验、任务入口无校验
对象/数据哪个租户、仓库、货主、订单查询条件与领域对象先按主键查再忘记归属
状态机当前状态是否允许动作版本和条件更新有权限即可重复退款
flowchart TD
    R["请求退款 order=O9"] --> G{"网关允许支付服务?"}
    G -->|"否"| X["拒绝"]
    G -->|"是"| U{"URL(统一资源定位符)需认证且身份有效?"}
    U -->|"否"| X
    U -->|"是"| M{"方法权限包含 refund:create?"}
    M -->|"否"| F["403 禁止访问"]
    M -->|"是"| O{"订单属于当前商户且金额在额度内?"}
    O -->|"否"| F
    O -->|"是"| D{"支付状态允许退款且未超额?"}
    D -->|"否"| B["业务冲突"]
    D -->|"是"| C["条件更新、流水和审计"]

数据演绎 6:仓库对象越权。 用户 u401 属于租户 t1,被授权仓库集合 {w1,w2}。攻击者把请求路径从 /warehouses/w1/stocks/88 改为 w9。虽然用户拥有 stock:read,查询必须使用 tenant_id=t1 AND warehouse_id IN(w1,w2) AND stock_id=88;返回零行按无权或不存在统一处理。若先按 stock_id=88 查询再只检查通用权限,就发生 IDOR(不安全直接对象引用)越权。

热门面试题

  1. 问题(基础题):认证和授权为什么不能混为一谈?

    • 考点:身份与能力的不同问题。
    • 回答思路:给出已登录但无权的例子。
    • 详细答案:认证只证明当前主体是谁及凭证是否可信,授权根据主体、资源和动作决定能否访问。已登录仓库员可以通过认证,却未必有退款权限,也未必能查看另一个仓库。混淆两者会把“有效用户”错误扩大为“全部数据可访问”。
    • 进阶追问:匿名用户也有 Authentication(认证结果)是否表示认证成功?
    • 进阶回答:匿名令牌是统一授权模型的占位身份,不代表完成真实登录,受保护资源仍应触发认证入口。
  2. 问题(原理题):为什么网关校验不能替代服务方法授权?

    • 考点:信任边界和领域信息。
    • 回答思路:说明网关只知道路由级信息。
    • 详细答案:网关适合验证令牌、客户端和粗粒度路由,但通常不知道订单归属、仓库授权、金额额度和当前状态;内部调用、任务消费或错误网络配置还可能绕过网关。服务必须验证可信身份来源,并在统一业务方法实施能力和对象授权,使所有入口共享同一规则。
    • 进阶追问:服务间调用怎样传递用户身份?
    • 进阶回答:区分服务身份与用户委托,使用受众受限、短期、可审计的凭据;下游不能盲信普通请求头。
  3. 问题(项目题):WMS(仓储管理系统)怎样做仓库和货主数据权限?

    • 考点:查询约束、写入约束和审计。
    • 回答思路:权限集合进入查询,领域状态进入条件更新。
    • 详细答案:登录后得到租户和允许仓库/货主集合,服务方法先检查动作权限,仓储查询和更新同时带租户、仓库、货主条件,不能查出后再在前端过滤。库存调整还需原因、额度、审批和版本条件,受影响行数为零时重新判定无权、状态变化或并发冲突,并记录操作者与前后值。
    • 进阶追问:超级管理员是否可以跳过租户条件?
    • 进阶回答:应使用独立受控能力、强再认证和完整审计显式扩大范围,不在普通查询里用空租户代表全局。

7. CSRF(跨站请求伪造)、CORS(跨源资源共享)与浏览器安全边界

CSRF(跨站请求伪造)利用浏览器自动携带 Cookie(浏览器小型数据)等凭据,让受害者在不知情时发起状态变更;防护依赖不可由攻击站点获得的 CSRF Token(跨站请求伪造令牌)、SameSite(同站策略)、来源检查和避免用 GET(获取请求)改变状态。CORS(跨源资源共享)只是浏览器是否允许前端脚本读取或发送特定跨源请求的策略,不是服务端身份认证,也挡不住非浏览器客户端。允许凭据时不能使用通配来源,应精确匹配来源、方法和请求头。点击劫持通过透明页面诱导点击,可用 CSP(内容安全策略)的 frame-ancestors(框架祖先策略)或对应响应头限制嵌入。对 Bearer Token(持有者令牌)放在 Authorization(授权请求头)且不自动携带的纯接口,CSRF(跨站请求伪造)风险不同,但一旦放入 Cookie(浏览器小型数据)仍要重新评估。

威胁利用前提主要控制常见误区
CSRF(跨站请求伪造)浏览器自动携带凭据随机令牌、同站策略、来源校验关闭即安全
CORS(跨源资源共享)浏览器跨源读取限制精确来源和方法当成认证
XSS(跨站脚本)页面执行攻击脚本输出编码、CSP(内容安全策略)CSRF(跨站请求伪造)令牌能防全部脚本
点击劫持页面可被第三方嵌入frame-ancestors(框架祖先策略)只隐藏按钮
sequenceDiagram
    participant E as 攻击站点
    participant B as 受害者浏览器
    participant W as WMS(仓储管理系统)
    E->>B: 诱导提交库存调整表单
    B->>W: 自动携带会话 Cookie(浏览器小型数据)
    W->>W: 校验来源与 CSRF Token(跨站请求伪造令牌)
    alt 令牌缺失或不匹配
        W-->>B: 403 禁止访问
    else 令牌、权限和领域规则均通过
        W-->>B: 执行受控操作
    end

数据演绎 7:错误 CORS(跨源资源共享)配置。 管理端允许凭据,配置把来源回显为任意请求的 Origin。攻击站点 evil.example 发起带会话请求,服务返回 Access-Control-Allow-Origin: evil.example 与允许凭据,浏览器便允许脚本读取敏感数据。正确做法是从固定白名单精确匹配协议、主机和端口,不允许字符串后缀或反射任意来源。

热门面试题

  1. 问题(基础题):CSRF(跨站请求伪造)和 CORS(跨源资源共享)分别解决什么?

    • 考点:伪造动作与浏览器读写策略。
    • 回答思路:说明二者攻击目标不同。
    • 详细答案:CSRF(跨站请求伪造)防止攻击站借浏览器自动凭据替用户执行动作;CORS(跨源资源共享)声明哪些来源的浏览器脚本可以跨源交互。正确 CORS(跨源资源共享)不能替代 CSRF(跨站请求伪造)令牌,错误 CORS(跨源资源共享)还可能扩大数据读取面。
    • 进阶追问:Postman(接口调试工具)受 CORS(跨源资源共享)限制吗?
    • 进阶回答:不受浏览器同源策略约束,所以服务端必须独立认证授权,不能把 CORS(跨源资源共享)当访问控制。
  2. 问题(原理题):为什么会话 Cookie(浏览器小型数据)接口需要 CSRF(跨站请求伪造)防护?

    • 考点:自动携带凭据。
    • 回答思路:沿攻击站到目标站的浏览器行为说明。
    • 详细答案:浏览器向目标站请求时会按规则自动带上会话 Cookie(浏览器小型数据),攻击者无需知道其值也能构造表单或请求。服务若只看到有效会话就执行,会把攻击请求当用户本人。随机令牌要求攻击站拿到额外秘密,配合同站属性与来源校验形成纵深防御。
    • 进阶追问:验证码能替代 CSRF Token(跨站请求伪造令牌)吗?
    • 进阶回答:不能作为通用替代,验证码成本高且目标不同;敏感动作可叠加再认证,但基础伪造防护仍应存在。
  3. 问题(项目题):前后端分离项目能直接关闭 CSRF(跨站请求伪造)吗?

    • 考点:凭据传输方式而非架构标签。
    • 回答思路:先看浏览器是否自动携带认证凭据。
    • 详细答案:不能仅凭“前后端分离”判断。若访问令牌只在脚本显式写入 Authorization(授权请求头),跨站页面不能自动取得,传统 CSRF(跨站请求伪造)风险较低;若令牌放在 Cookie(浏览器小型数据)或仍使用会话,浏览器会自动携带,就必须保留相应防护。同时还要治理 XSS(跨站脚本),否则脚本可窃取显式令牌。
    • 进阶追问:SameSite(同站策略)设置为 Strict(严格)就足够吗?
    • 进阶回答:它会影响正常跨站流程且存在兼容与子域边界,应与随机令牌、来源校验和安全请求方法共同使用。

8. OAuth 2.0(开放授权协议)与 OIDC(开放身份连接协议)的角色和边界

OAuth 2.0(开放授权协议)解决客户端如何在资源所有者许可下获得受限 Access Token(访问令牌)访问资源,核心角色是资源所有者、客户端、授权服务器和资源服务器;它本身不是用户登录协议。OIDC(开放身份连接协议)在 OAuth 2.0(开放授权协议)之上增加身份层,通过 ID Token(身份令牌)和 UserInfo(用户信息)端点表达认证结果。Authorization Code(授权码)配合 PKCE(代码交换证明密钥)适用于浏览器和移动客户端,授权码一次性且绑定客户端、重定向地址与代码挑战。ID Token(身份令牌)面向客户端证明登录,Access Token(访问令牌)面向资源服务器授权,两者受众和用途不能互换。服务间 Client Credentials(客户端凭证)代表应用自身,不代表最终用户。

对象面向谁证明什么不应做什么
Authorization Code(授权码)客户端后端一次性换令牌资格直接访问资源
ID Token(身份令牌)客户端用户认证事件与身份声明当资源令牌
Access Token(访问令牌)资源服务器调用作用域与受众当登录会话随意传播
Client Credentials(客户端凭证)授权服务器服务应用身份冒充最终用户
sequenceDiagram
    participant U as 用户浏览器
    participant C as 客户端
    participant A as 授权服务器
    participant R as 资源服务器
    C->>C: 生成 verifier(校验秘密)与 challenge(挑战值)
    C->>U: 跳转授权请求和 challenge(挑战值)
    U->>A: 登录、同意授权
    A-->>C: 一次性 Authorization Code(授权码)
    C->>A: 授权码 + verifier(校验秘密)
    A-->>C: ID Token(身份令牌)+ Access Token(访问令牌)
    C->>R: Access Token(访问令牌)
    R-->>C: 受限资源

数据演绎 8:令牌受众混用。 客户端收到 ID Token(身份令牌),其受众为 web-client;订单资源服务器要求 Access Token(访问令牌)受众 order-api、作用域 order:read。如果资源服务器只验签、不验受众和类型,就可能错误接受 ID Token(身份令牌)。正确校验会因受众和用途不符返回 401,不把“签名有效”扩大成“可访问任意服务”。

热门面试题

  1. 问题(基础题):OAuth 2.0(开放授权协议)和 OIDC(开放身份连接协议)有什么区别?

    • 考点:授权协议与身份层。
    • 回答思路:从目标、令牌和受众说明。
    • 详细答案:OAuth 2.0(开放授权协议)主要授权客户端访问资源,定义访问令牌和多种授权流程;OIDC(开放身份连接协议)增加标准身份声明、ID Token(身份令牌)和登录会话相关语义,使客户端能够验证用户认证结果。仅有 OAuth 2.0(开放授权协议)不能随意把资源声明当登录身份。
    • 进阶追问:OIDC(开放身份连接协议)是否替代 OAuth 2.0(开放授权协议)?
    • 进阶回答:不是,它建立在 OAuth 2.0(开放授权协议)流程之上并增加身份层,两者共同工作。
  2. 问题(原理题):PKCE(代码交换证明密钥)防什么攻击?

    • 考点:授权码被截获后的兑换限制。
    • 回答思路:说明挑战值公开、校验秘密只在客户端。
    • 详细答案:客户端先生成随机 verifier(校验秘密),授权请求只发送其 challenge(挑战值);换令牌时必须提交原 verifier(校验秘密)。即使攻击者截获授权码,没有校验秘密也不能兑换。它仍要配合精确重定向地址、状态参数、一次性授权码和 TLS(传输层安全协议)。
    • 进阶追问:有客户端密钥还需要 PKCE(代码交换证明密钥)吗?
    • 进阶回答:现代授权码流程通常仍建议使用,尤其公共客户端无法安全保存密钥;它对授权码截获提供独立保护。
  3. 问题(项目题):服务间调用和用户委托如何区分?

    • 考点:主体、受众和审计链。
    • 回答思路:服务身份不冒充用户,委托显式受限。
    • 详细答案:后台任务用 Client Credentials(客户端凭证)取得服务身份令牌,只拥有任务所需作用域;需要代表用户时使用明确的令牌交换或委托机制,保留原用户、调用服务、受众和链路。下游审计同时记录“谁发起”和“哪个服务执行”,不能把服务令牌伪装成用户令牌或复制任意用户请求头。
    • 进阶追问:内部网络是否可以不验受众?
    • 进阶回答:不可以,受众限制能阻止一个服务的令牌被横向用于另一个服务,是零信任边界的重要组成。

9. ExceptionTranslationFilter(异常转换过滤器)、401/403 与统一安全失败

ExceptionTranslationFilter(异常转换过滤器)不做认证或授权,它捕获下游 AuthenticationException(认证异常)与 AccessDeniedException(拒绝访问异常),把 Java(编程语言)异常转换为 HTTP(超文本传输协议)安全响应。未建立可信身份而访问受保护资源时,由 AuthenticationEntryPoint(认证入口处理器)发起登录或返回 401;已认证身份权限不足时,由 AccessDeniedHandler(拒绝访问处理器)返回 403。若匿名身份触发拒绝,框架通常仍走认证入口。认证过滤器自身发生的凭证失败可能在更早位置直接调用失败处理器,不能假设全部异常都经过同一个组件。统一响应要保留 TraceId(链路标识)、稳定错误码和服务端根因,但不向客户端泄露“用户不存在”、密钥细节或授权规则。业务对象无权与不存在是否区分,要按信息泄露风险设计。

场景当前身份失败点推荐状态客户端动作
未带凭证访问受保护资源匿名授权阶段401登录或刷新
密码或令牌无效未认证认证阶段401停止重试或重登
已登录但缺权限已认证授权阶段403不应反复刷新
对象不属于当前租户已认证领域授权403 或统一 404不泄露对象
业务状态不允许已认证且有权领域规则409 等业务冲突刷新状态
flowchart TD
    Q["下游抛出安全异常"] --> T{"异常类型?"}
    T -->|"AuthenticationException(认证异常)"| E["AuthenticationEntryPoint(认证入口处理器)"]
    T -->|"AccessDeniedException(拒绝访问异常)"| A{"当前是否可信认证?"}
    A -->|"否或匿名"| E
    A -->|"是"| H["AccessDeniedHandler(拒绝访问处理器)"]
    E --> U["401 + 稳定错误码 + TraceId(链路标识)"]
    H --> F["403 + 稳定错误码 + TraceId(链路标识)"]
    T -->|"业务异常"| B["交给业务异常解析器"]

数据演绎 9:刷新风暴。 前端把所有 403 都当成“访问令牌过期”,50 个并发接口收到 403 后同时刷新,产生 50 次刷新令牌消费;第一次成功轮换,后续被识别为重放并撤销令牌族,正常用户反而被登出。客户端必须只在明确的 401 令牌过期错误上执行一次合并刷新,403 直接展示无权或按业务流程处理。

热门面试题

  1. 问题(基础题):401 和 403 怎样区分?

    • 考点:认证失败与授权失败。
    • 回答思路:用身份是否可信作为分界。
    • 详细答案:401 表示请求没有可接受的认证,客户端通常需要登录、更新凭证或停止使用无效令牌;403 表示服务理解身份,但该身份没有所需权限。匿名身份在授权处被拒时通常仍转成 401。业务状态冲突不应一律包装为 403,否则客户端无法采取正确恢复动作。
    • 进阶追问:账号被禁用应返回 401 还是 403?
    • 进阶回答:登录认证阶段通常按认证失败返回 401 和不泄露细节的错误;已登录后冻结则使凭证失效或在策略层拒绝,具体契约要一致并可审计。
  2. 问题(原理题):ExceptionTranslationFilter(异常转换过滤器)为什么不是全局异常处理器?

    • 考点:过滤器位置与异常范围。
    • 回答思路:说明它只处理特定安全异常且位于控制器之前。
    • 详细答案:它包围安全链下游,只识别 AuthenticationException(认证异常)和 AccessDeniedException(拒绝访问异常),并调用安全失败策略。控制器业务异常通常由 Spring(Java 应用框架) MVC(网页模型视图控制器)的异常解析链处理;认证过滤器早期直接失败也可能不经过它。把所有异常都吞进安全处理器会丢失业务语义和根因。
    • 进阶追问:为什么控制器异常处理器捕不到过滤器异常?
    • 进阶回答:过滤器发生在 DispatcherServlet(前端控制器)之外,异常尚未进入控制器异常解析范围,应在过滤器或安全失败组件内处理。
  3. 问题(故障题):线上大量 401/403 如何快速定位?

    • 考点:分类、证据和止血。
    • 回答思路:按失败阶段、令牌原因、路由和身份聚合。
    • 详细答案:先按状态码、稳定错误码、命中安全链、过滤器、令牌签发者/受众/密钥标识、客户端版本和接口聚合,区分密钥轮换、时钟偏差、权限发布或前端误刷新。保存样本但不记录完整令牌;必要时暂停新密钥签发或回滚权限策略,不放宽全部接口。修复后验证 401 只触发单飞刷新、403 不刷新、对象越权仍被拒绝。
    • 进阶追问:能否临时把接口改为 permitAll(全部放行)止血?
    • 进阶回答:高风险接口不能这样做;应回滚具体配置、恢复旧密钥验证窗口或降级只读,并保留认证授权边界。

10. 线程、异步任务、多租户与 SecurityContext(安全上下文)传播

SecurityContext(安全上下文)默认与当前线程关联,切换到 @Async、线程池、定时任务、消息消费或并行流时不会自动获得正确身份。DelegatingSecurityContextExecutor(委派安全上下文执行器)等包装器在提交任务时捕获快照,工作线程执行前设置、结束后恢复或清理;如果在线程池创建时固定一个管理员上下文,会把后续所有任务提升为管理员。租户上下文、TraceId(链路标识)和日志上下文也要采用同样的快照/恢复协议,但不能因为能传播就默认把用户权限带入长时后台任务。任务应使用最小服务身份,显式记录发起用户作为审计信息,并在执行时重新验证租户、仓库和业务对象权限;消息中的普通用户编号不是可信身份。

场景推荐身份传播方式禁止做法
短异步子任务当前用户快照受控执行器包装共享可变上下文
定时任务最小服务身份任务配置与审计伪造管理员用户
MQ(消息队列)消费服务身份 + 已验证业务主体签名消息或数据库事实信任普通请求头
长时导出作业主体、租户和授权快照/复核作业记录永久复制全部权限
多租户查询可信租户上下文 + SQL(结构化查询语言)条件显式参数和数据约束仅依赖线程变量
sequenceDiagram
    participant R as 请求线程
    participant E as 安全上下文执行器
    participant W as 工作线程
    participant D as 领域服务
    R->>E: 提交任务 + 身份/租户快照
    E->>W: 设置 SecurityContext(安全上下文)
    E->>W: 设置租户和 TraceId(链路标识)
    W->>D: 重新校验对象归属并执行
    D-->>W: 结果与审计
    W->>W: finally(最终清理)全部上下文
    W-->>R: 完成或失败

数据演绎 10:异步导出串租户。 用户 u501/t1 提交作业 job-1,用户 u502/t2 提交 job-2,两者先后在 pool-2 执行。若 job-1 设置租户 t1 后异常退出未清理,job-2 的查询未显式带租户就可能读取 t1。正确作业表保存 job_id、requester、tenant、scope、created_at,执行器每次设置后在 finally 清理,仓储查询仍强制携带 tenant=t2,结果下载再校验当前用户权限。

flowchart TD
    J["异步任务开始"] --> S["读取可信作业记录"]
    S --> C["构建最小服务身份和发起者审计"]
    C --> T["绑定租户、身份和 TraceId(链路标识)"]
    T --> P{"执行时权限与对象归属仍有效?"}
    P -->|"否"| X["停止并记录拒绝"]
    P -->|"是"| B["执行受限业务"]
    B --> F["finally(最终清理)"]
    X --> F

热门面试题

  1. 问题(基础题):为什么 @Async 方法拿不到当前用户?

    • 考点:线程绑定上下文。
    • 回答思路:说明任务在线程池另一线程执行。
    • 详细答案:SecurityContext(安全上下文)通常绑定请求线程,@Async 把方法放到另一个工作线程,线程本地状态不会自动复制。需要由受控执行器在提交时捕获不可变快照,执行前设置、结束后清理;更长期任务应保存明确作业主体,而不是依赖请求线程仍存在。
    • 进阶追问:使用可继承线程本地变量可以解决吗?
    • 进阶回答:线程池线程通常早已创建且长期复用,可继承语义不适合按任务变化的身份,还可能造成泄露;应使用任务级包装。
  2. 问题(原理题):上下文传播为什么还要“恢复”而不只是清空?

    • 考点:嵌套执行和调用方上下文。
    • 回答思路:解释包装器可能运行在已有上下文中。
    • 详细答案:工作线程执行前可能已有合法系统上下文或发生嵌套任务。包装器应保存旧值、设置任务快照,完成后恢复旧值;在线程池请求边界则最终清空。简单覆盖后清空可能破坏外层调用,简单不清理又会污染后续任务,因此生命周期必须成对。
    • 进阶追问:任务能否直接修改捕获的 Authentication(认证结果)?
    • 进阶回答:不应共享可变身份对象;使用不可变快照或独立上下文,变更权限要回到权威服务。
  3. 问题(项目题):Runner(执行器)调度怎样设计最小权限?

    • 考点:服务身份、委托和审计。
    • 回答思路:任务按能力发证,不继承提交人全部权限。
    • 详细答案:任务定义声明租户、资源范围和允许动作,调度器用受众受限的服务身份调用目标,作业记录保存发起者但不复制其长期令牌。执行时重新检查任务状态、租户和对象范围,结果写入审计;取消、重试和接管也使用独立权限。这样人员离职或权限变化可停止未执行任务,不会留下永久管理员令牌。
    • 进阶追问:历史作业是否应按提交时权限继续执行?
    • 进阶回答:取决于业务承诺;高风险写操作通常执行时复核,报表可保存经过批准的范围快照,但要有有效期、审批和撤销机制。

11. 密钥轮换、防重放与支付/WMS(仓储管理系统)安全案例

密钥轮换必须区分签发与验证:新密钥先发布公钥并通过缓存传播,再开始签发;旧公钥保留到所有旧 Token(令牌)过期和时钟偏差窗口结束,最后撤销。密钥标识只用于选取候选密钥,不能让客户端指定任意算法或远程地址。支付回调不是用户登录:它验证渠道签名、时间戳、Nonce(随机数)、报文摘要、商户和金额,并以渠道事件编号、支付单和状态机保证幂等;签名证明来源与完整性,不证明渠道一定扣款成功,必要时主动查单和对账。WMS(仓储管理系统)库存调整则使用员工或服务身份、仓库/货主对象权限、审批和数据库条件更新。两类入口都要限流、审计、最小暴露与失败关闭,但信任根和业务事实不同。

安全对象唯一标识时间窗服务端事实失败恢复
登录 Token(令牌)jti(令牌唯一标识)/会话到期时间密钥、撤销、用户状态刷新或重登
支付回调渠道事件编号 + Nonce(随机数)例如 5 分钟支付单、回调记录、查单重试、查单、对账
库存调整业务请求编号业务幂等期库存流水、版本、审批查流水、补偿
密钥轮换密钥标识双密钥窗口密钥状态和发布时间回滚签发、保留验证
sequenceDiagram
    participant P as 支付渠道
    participant G as 回调安全入口
    participant O as 支付订单服务
    participant Q as 渠道查单
    participant L as 账务与审计
    P->>G: 事件编号、时间戳、Nonce(随机数)、签名、报文
    G->>G: 校验时间窗、签名和重放键
    alt 校验失败或重放冲突
        G-->>P: 拒绝或返回既有处理结果
    else 入口可信
        G->>O: 按事件编号和支付单幂等处理
        O->>O: 校验商户、金额、币种和状态
        alt 结果未知或金额冲突
            O->>Q: 主动查询渠道事实
            Q-->>O: 权威支付状态
        end
        O->>L: 同事务记录状态、流水和待发布事件
        O-->>P: 成功确认
    end

数据演绎 11:支付回调防重放。 渠道事件 evt-900110:00:00 到达,允许时钟偏差 ±300s,Nonce(随机数)为 n77,金额 100.00 USD(美元)。入口先按原始字节验签,再原子写入 (channel, merchant, nonce),有效期十分钟;支付事务以 (channel,event_id) 唯一键写回调,以支付单版本从 PENDING 条件更新到 PAID10:00:03 原报文重放时命中唯一键,返回首次结果而不重复记账;金额变为 1000.00 即使签名来自渠道,也因本地订单不一致进入查单和人工告警。

flowchart LR
    K0["旧公钥 K0 已验证"] --> P1["发布新公钥 K1"]
    P1 --> W["等待资源服务缓存可见"]
    W --> S1["签发端改用私钥 K1"]
    S1 --> D["验证端同时接受 K0/K1"]
    D --> E{"K0 令牌寿命 + 时钟偏差已结束?"}
    E -->|"否"| D
    E -->|"是"| R["撤销 K0 并告警未知密钥标识"]

热门面试题

  1. 问题(基础题):支付回调验签等于用户认证吗?

    • 考点:机器消息与用户身份的不同信任模型。
    • 回答思路:说明签名主体、报文和业务事实。
    • 详细答案:不等于。支付回调验签确认报文很可能由持有渠道密钥的一方生成且未被篡改,主体是渠道或商户集成,不是终端用户。通过后仍要校验事件、商户、支付单、金额、币种和状态,并用查单、幂等和对账确认资金事实,不能直接把“签名正确”当“订单可发货”。
    • 进阶追问:HTTPS(安全超文本传输协议)已经加密,为什么还要验签?
    • 进阶回答:TLS(传输层安全协议)保护传输通道,业务签名提供端到端报文完整性和渠道身份证据,便于经过代理、重试和存储后继续验证。
  2. 问题(原理题):密钥轮换为什么要双密钥窗口?

    • 考点:签发与验证传播时序。
    • 回答思路:从缓存和旧令牌寿命说明。
    • 详细答案:先发布新验证密钥,等资源服务可见后再用新私钥签发;切换后旧令牌仍在有效期内,所以验证端需同时接受旧、新公钥。只有旧令牌最大寿命、缓存延迟和时钟偏差都结束后才能移除旧公钥。直接替换会让尚未过期的合法请求全部 401。
    • 进阶追问:私钥泄露还按普通窗口等待吗?
    • 进阶回答:不能,需紧急停止签发、发布撤销、缩短或拒绝受影响令牌并强制重认证,同时评估可用性影响和审计泄露范围。
  3. 问题(项目题):库存调整接口如何同时防越权、重放和超卖?

    • 考点:安全、幂等与并发约束分层。
    • 回答思路:身份授权、业务键、条件更新三层闭环。
    • 详细答案:入口认证员工或服务身份,方法授权检查调整能力,对象授权限制租户、仓库和货主;请求携带稳定业务编号并以唯一键记录,重复同参数返回首次结果,冲突参数拒绝。库存写入使用版本或数量条件更新并记录流水,审批和额度控制高风险动作。安全链不能替代数据库并发约束,数据库成功也不能替代操作者授权。
    • 进阶追问:前端按钮不可见还需要后端校验吗?
    • 进阶回答:必须。客户端完全可被绕过,后端每个入口和异步路径都要进入同一授权与领域约束。

12. 综合面试题库

  1. 问题:请完整讲一次请求在 Spring Security(安全框架)过滤器链中的执行过程。

    • 回答思路:按容器桥接、链选择、上下文、认证、授权、异常和清理顺序复述。
    • 详细答案:请求先由 DelegatingFilterProxy(委派过滤器代理)桥接到 FilterChainProxy(过滤器链代理),后者选择首个匹配的 SecurityFilterChain(安全过滤器链)。链内加载上下文、提取凭证、委托认证、写入身份、执行授权和异常转换,最后保存并清理上下文;服务方法继续实施对象级权限。
    • 进阶追问:安全链已经授权后,为什么服务方法还要校验订单或仓库?
    • 进阶回答:入口授权只知道路径和通用能力,不掌握对象归属、金额、租户与状态;领域授权是另一层不变量。
    • 口述答案:我会先说明 Spring Security(安全框架)不是控制器里的一次权限判断,而是 Servlet(服务器小程序)请求进入业务前的一套有序协议。Servlet(服务器小程序)容器先调用 DelegatingFilterProxy(委派过滤器代理),它根据名称找到 Spring(Java 应用框架)容器中的 FilterChainProxy(过滤器链代理)。后者按配置顺序检查请求匹配器,只选择第一条命中的 SecurityFilterChain(安全过滤器链),因此多链顺序本身就是安全配置。链开始先从 SecurityContextRepository(安全上下文仓库)加载会话身份或建立空上下文;认证过滤器再从表单、请求头或证书提取凭证,构造未认证 Authentication(认证请求),交给 AuthenticationManager(认证管理器)。常见 ProviderManager(认证提供者管理器)选择支持该凭证类型的 AuthenticationProvider(认证提供者),完成用户状态和凭证校验,成功后得到已认证身份并放入 SecurityContext(安全上下文)。授权过滤器随后通过 AuthorizationManager(授权管理器)判断当前身份能否访问请求;通过后才进入 DispatcherServlet(前端控制器)和业务方法。下游抛认证异常时进入 401,可信身份缺权限时进入 403。响应结束必须保存需要持久化的上下文,并在 finally 清理线程绑定。项目上我还会在服务方法校验租户、仓库、货主或订单归属,因为过滤器链只解决入口能力,不能替代领域对象授权和数据库约束。排查时记录命中链、过滤器位置、认证提供者、身份摘要、授权决策和 TraceId(链路标识),但绝不记录密码或完整 Token(令牌)。
    • 追问 1:为什么只选择第一条安全链?
    • 回答 1:每条链代表一套完整请求协议,首个匹配使职责确定;若合并多链,顺序和重复认证将不可预测。
    • 追问 2:认证过滤器成功是否说明业务一定允许?
    • 回答 2:不说明,它只建立身份;方法权限、对象归属、状态机和数据约束仍需继续判断。
    • 追问 3:怎样证明线上实际执行了预期链?
    • 回答 3:对重叠路径建立正反样本,开启受控安全调试或记录链标识和过滤器清单,再结合 401/403 决策证据验证。
    • 详情:安全过滤器链装配与选择
  2. 问题:如何准确解释 401 和 403,以及它们在框架中的产生位置?

    • 回答思路:以“是否存在可信身份”为分界,再定位异常转换组件。
    • 详细答案:没有可接受认证时返回 401,可信身份存在但权限不足时返回 403。ExceptionTranslationFilter(异常转换过滤器)把认证异常交给认证入口处理器,把已认证用户的拒绝异常交给拒绝访问处理器;业务状态冲突应保留独立错误语义。
    • 进阶追问:为什么前端不能收到 403 就自动刷新令牌?
    • 进阶回答:403 通常不是令牌过期,刷新既不能增加权限,还会制造并发刷新和重放撤销事故。
    • 口述答案:我用“当前是否已有可接受身份”来区分。401 表示请求没有可用认证,可能未带凭证、密码错误、Token(令牌)签名或受众不合法、令牌过期,客户端通常应该登录、刷新或停止使用该凭证。403 表示服务已经接受当前身份,但访问规则拒绝它,例如普通仓库员调用退款接口、用户访问未授权仓库。Spring Security(安全框架)中,ExceptionTranslationFilter(异常转换过滤器)捕获下游 AuthenticationException(认证异常)和 AccessDeniedException(拒绝访问异常);前者交给 AuthenticationEntryPoint(认证入口处理器)产生 401,后者在可信认证存在时交给 AccessDeniedHandler(拒绝访问处理器)产生 403,匿名身份被拒通常仍转到认证入口。要注意认证过滤器本身的凭证失败可能在更早位置直接执行失败处理器,控制器异常处理器也未必能捕获过滤器异常。领域状态冲突如订单已退款、库存版本变化不应伪装成 403,可用稳定业务错误和 409 等契约。统一错误体包括稳定错误码、TraceId(链路标识)和可操作提示,服务端日志保留失败阶段、密钥标识和授权规则版本,但不泄露用户是否存在、完整令牌或内部规则。前端只能在明确的 401 令牌过期上执行一次合并刷新,不能把所有 403 都刷新,否则并发请求会造成刷新风暴,甚至触发刷新令牌重放检测把合法会话撤销。对象越权是否返回 403 或统一 404,则按是否会泄露资源存在性决定,并保持接口一致。
    • 追问 1:登录用户访问不存在的他人订单返回什么?
    • 回答 1:高敏对象可统一返回 404 避免枚举,但服务端应区分不存在与越权并形成审计,不能因此省略授权。
    • 追问 2:403 能通过刷新令牌修复吗?
    • 回答 2:通常不能;刷新只更新认证凭证,权限不足要申请授权或改变业务条件。
    • 追问 3:账号冻结后旧访问令牌怎样处理?
    • 回答 3:高风险系统在线检查账户或会话版本并返回认证失效,短令牌系统可接受有限窗口,但必须有明确风险预算。
    • 详情:安全异常转换
  3. 问题:SecurityContext(安全上下文)的生命周期是什么,为什么容易发生串号?

    • 回答思路:沿请求加载、线程绑定、使用、保存和清理解释,再扩展到异步线程。
    • 详细答案:SecurityContext(安全上下文)承载当前 Authentication(认证结果),请求开始时加载或重建,执行中绑定线程,响应前按模型保存,所有出口在 finally 清理。线程池会复用线程,任何异常分支漏清理都可能让后一任务继承前一身份。
    • 进阶追问:JWT(JSON 网络令牌)模型是否不需要清理上下文?
    • 进阶回答:仍需要;令牌只是身份来源,解析后的认证结果同样绑定执行线程。
    • 口述答案:SecurityContext(安全上下文)是一次执行链当前身份的容器,核心是 Authentication(认证结果),不是用户主数据。请求开始时,安全上下文过滤器从 SecurityContextRepository(安全上下文仓库)按会话标识读取,或者在 JWT(JSON 网络令牌)模型下解析凭证后重建;随后绑定到 SecurityContextHolder(安全上下文持有器),业务、方法授权和审计在当前线程读取它。认证状态改变时,会话模型在响应提交前保存新上下文,纯访问令牌模型通常不保存请求上下文。无论成功、业务异常、超时还是认证失败,外层都必须在 finally 清理线程绑定。风险来自服务器和业务线程池复用:如果请求 A 把用户 u1 写入工作线程后异常退出没有清理,请求 B 正好复用同一线程且代码读取残留上下文,就可能以 u1 执行。JWT(JSON 网络令牌)不消除这个风险,因为令牌解析后依然会形成线程上下文。异步任务也不能简单依赖可继承线程本地变量;应在提交时捕获不可变快照,执行前保存旧值并设置任务身份,结束后恢复或清理。长时任务更适合在作业表保存发起者、租户、范围和审批,执行时使用最小服务身份重新检查对象权限。排查串号时同时关联线程名、请求标识、用户、租户、仓库、缓存键和 SQL(结构化查询语言)条件,避免把缓存未隔离误判为上下文泄漏。回归用同一线程交替执行两个租户请求,覆盖正常、异常和取消路径,证明身份、租户、日志上下文与查询条件都同步切换。
    • 追问 1:匿名身份为什么也放进上下文?
    • 回答 1:它让授权表达式统一处理匿名主体,但不代表真实认证完成,受保护资源仍会要求登录。
    • 追问 2:清空与恢复旧上下文有什么区别?
    • 回答 2:嵌套执行可能已有合法外层上下文,包装器应恢复旧值;真正请求边界结束时则必须清空。
    • 追问 3:能否在消息里直接传序列化 Authentication(认证结果)?
    • 回答 3:不建议,权限会过期且消息可被伪造;应传业务主体标识和签名事实,由消费者按服务身份与当前规则重新授权。
    • 详情:SecurityContext(安全上下文)生命周期
  4. 问题:AuthenticationManager(认证管理器)和多个 AuthenticationProvider(认证提供者)怎样协作?

    • 回答思路:区分请求凭证提取、统一认证入口和具体策略实现。
    • 详细答案:过滤器构造未认证凭证对象,AuthenticationManager(认证管理器)提供统一入口,ProviderManager(认证提供者管理器)按支持类型选择 AuthenticationProvider(认证提供者)。提供者完成用户状态与凭证验证,成功返回最小身份,失败以分类异常结束。
    • 进阶追问:多个提供者都支持同一凭证类型有什么风险?
    • 进阶回答:顺序会决定实际策略,过宽支持可能抢占后续提供者并形成错误降级,因此必须互斥并测试。
    • 口述答案:认证链的设计目标是把“从请求提取凭证”和“验证某类凭证”分离。认证过滤器读取用户名密码、一次性口令或客户端证书,构造尚未认证的 Authentication(认证请求),只包含完成验证所需数据。它调用 AuthenticationManager(认证管理器)统一入口,常见 ProviderManager(认证提供者管理器)遍历 AuthenticationProvider(认证提供者),先用 supports 判断凭证类型,再由匹配者执行认证。用户名密码提供者会加载用户状态,检查禁用、锁定和过期,再调用 PasswordEncoder(密码编码器)比较;外部身份提供者可能验证签发者、受众和远端断言。成功结果包含最小主体和权限,敏感凭证应被擦除;失败抛出分类认证异常,但对外提示通常保持模糊,避免枚举用户。多个提供者的支持范围应尽量互斥,顺序要显式,因为过宽的 supports 可能抢占本应由后续提供者处理的凭证。父 AuthenticationManager(认证管理器)可作为未匹配时的后备,但父子链也会增加诊断复杂度。项目中后台管理员、普通用户和服务客户端应使用不同凭证对象或明确链,不能让服务密钥落入普通密码提供者。观测只记录凭证类型、提供者标识、耗时和结果分类,不记录原始秘密。测试要覆盖支持与不支持、账户状态、错误密码、提供者异常、顺序变化和并发限流。这样新增一种登录方式只增加策略实现,不会把过滤器、用户查询和密码逻辑复制到每个入口。
    • 追问 1:一个提供者认证失败后是否继续下一个?
    • 回答 1:取决于异常类型和实现;账户被锁等确定失败通常不应继续,配置时要用真实版本测试行为。
    • 追问 2:为什么 supports 不能写成支持所有类型?
    • 回答 2:它会抢占其他凭证并把不相干请求解释错,造成错误降级甚至认证旁路。
    • 追问 3:认证提供者能直接做业务对象授权吗?
    • 回答 3:不应,它负责建立身份;订单、仓库和金额规则属于授权与领域层,否则每次登录会绑定易变业务状态。
    • 详情:认证管理与提供者链
  5. 问题:密码为什么不能加密存储,如何实现算法与成本升级?

    • 回答思路:从不可逆验证、离线破解成本和渐进迁移三个层次回答。
    • 详细答案:密码无需恢复原文,应使用带随机盐的自适应密码哈希,而非可逆加密或快速摘要。记录算法与成本,登录成功后判断旧摘要是否需要升级,再用本次明文生成新摘要并做条件更新,从而不批量解密也能平滑迁移。
    • 进阶追问:提高哈希成本是否越高越安全?
    • 进阶回答:不是,成本过高会让正常峰值认证和拒绝服务攻击耗尽资源,必须按硬件和并发压测。
    • 口述答案:密码验证不需要恢复原文,因此不应使用可逆加密;一旦应用密钥泄露,整库密码会被还原。正确做法是使用专门的自适应密码哈希或密钥派生算法,例如 BCrypt(自适应密码哈希算法)、PBKDF2(基于密码的密钥派生函数)、scrypt(内存困难密码派生函数)或 Argon2(内存困难密码哈希算法)。每条密码使用独立随机盐,使相同密码得到不同摘要,破坏彩虹表;计算或内存成本按当前硬件压测,让正常登录延迟可接受,却显著提高数据库泄露后的离线猜测成本。摘要记录算法标识和参数,通过 DelegatingPasswordEncoder(委派密码编码器)兼容旧格式。用户登录时按前缀选择旧编码器验证,成功后若 upgradeEncoding 判断成本不足,就用本次明文和新随机盐产生新摘要,以“用户编号+旧摘要”为条件更新,避免两个并发登录相互覆盖。更新失败不应把已经成功的认证变成失败,但要记录升级指标;长期不登录账户可在下次登录升级或触发密码重置,绝不能批量解密。可选 Pepper(服务端秘密)要放在独立密钥系统并可轮换,但不能替代强算法和随机盐。认证入口还要限流、渐进延迟和风险检测,防止攻击者利用昂贵哈希反向消耗中央处理器。日志不得记录明文、摘要或完整认证请求。变更成本参数前做峰值并发压测,观察认证延迟、线程池、中央处理器和失败率,分批放量而不是一次把成本调到过高。
    • 追问 1:盐需要保密吗?
    • 回答 1:通常不需要,可与摘要一起存储;它的价值是唯一性,保密需求由独立 Pepper(服务端秘密)承担。
    • 追问 2:MD5(消息摘要算法第五版)加盐为什么仍不合适?
    • 回答 2:它计算过快且不是密码专用算法,攻击者仍能大规模并行猜测,无法按硬件提升逐步调高成本。
    • 追问 3:密码升级后何时删除旧编码器?
    • 回答 3:等旧摘要存量降到可接受范围并完成重置、回滚和审计窗口后分阶段移除,不能只看发布时间。
    • 详情:密码存储与升级
  6. 问题:Session(会话)和 JWT(JSON 网络令牌)如何选型,为什么不能只说有状态与无状态?

    • 回答思路:比较即时撤销、调用范围、状态成本、密钥治理和失败窗口。
    • 详细答案:Session(会话)便于服务端立即撤销和并发控制,但依赖会话存储;JWT(JSON 网络令牌)便于资源服务本地验证和跨服务调用,却需要短寿命、刷新轮换、密钥和撤销治理。选型应说明可接受的撤销延迟与恢复方案。
    • 进阶追问:JWT(JSON 网络令牌)为什么仍不是完全无状态?
    • 进阶回答:密钥、刷新令牌族、撤销、用户冻结和审计仍是权威服务端状态。
    • 口述答案:我不会先问哪种“更先进”,而是从撤销时效、调用范围、风险、容量和运维边界选。Session(会话)把身份状态放在服务端,浏览器只带高熵会话标识,优势是退出、冻结、改密和并发限制可以立即生效,权限变化容易在线反映;代价是共享会话仓库、跨节点一致性、容量和故障依赖。JWT(JSON 网络令牌)访问令牌由资源服务本地验证签名和声明,适合跨服务和多资源调用,减少每次请求读取会话;但窃取后在有效期内可重放,权限快照会滞后,密钥轮换、刷新、撤销和受众治理不可省。所谓无状态只是不保存每个登录会话,系统仍有密钥、刷新令牌族、撤销记录、用户状态和审计。后台管理入口权限高、人数有限且需要立即踢下线,我通常倾向会话或短令牌加在线状态;开放 API(应用程序接口)和移动端可采用短 Access Token(访问令牌)加一次性轮换 Refresh Token(刷新令牌)。无论哪种,HTTPS(安全超文本传输协议)、安全 Cookie(浏览器小型数据)、最小权限和对象级授权都相同。迁移时不能让两套身份出现冲突,应定义主体、会话版本、权限来源和注销语义。容量评估比较会话读写量与令牌验证中央处理器成本,事故演练覆盖会话仓库故障、密钥泄露、时钟偏差和撤销延迟。最终选型不是框架标签,而是明确接受哪个失败窗口以及怎样查证和恢复。选定后还要把退出、冻结和权限变更的生效时间写进安全契约。
    • 追问 1:JWT(JSON 网络令牌)比会话性能一定好吗?
    • 回答 1:不一定,签名验证、令牌大小和在线撤销也有成本;会话可本地或高效缓存,必须按真实负载测量。
    • 追问 2:会话能用于服务间调用吗?
    • 回答 2:技术上可传,但耦合用户会话和网页边界;服务间更适合受众受限的服务身份或明确用户委托令牌。
    • 追问 3:两者可以混用吗?
    • 回答 3:可以,例如授权服务器用会话维护登录、资源服务用短令牌,但要明确注销、主体映射和撤销如何贯通。
    • 详情:会话与令牌模型
  7. 问题:资源服务器验证 JWT(JSON 网络令牌)时必须检查哪些内容?

    • 回答思路:按算法与密钥、签名、声明、动态状态和业务授权逐层验证。
    • 详细答案:服务先限制算法并从可信来源取键,再验签、签发者、受众、时间、类型和客户端,随后按风险检查撤销、会话版本和账户状态,只映射白名单声明。通过这些检查后仍要验证作用域、租户与对象归属。
    • 进阶追问:签名有效为什么还可能返回 401?
    • 进阶回答:令牌可能签给其他受众、已经过期、尚未生效、类型错误或处于撤销状态。
    • 口述答案:验证 JWT(JSON 网络令牌)不能停留在“能解码、签名通过”。第一步限制允许的算法,绝不能相信令牌头任意指定算法,也不能把对称密钥误当公开密钥;根据受控的密钥标识从可信配置或缓存取得候选公钥,密钥标识只能选键,不能引导服务器访问客户端给出的地址。第二步验证签名,证明报文未被未知方篡改。第三步验证签发者、受众、Token(令牌)类型和客户端,确保这个令牌由预期授权服务签发、确实面向当前资源服务且用途是 Access Token(访问令牌),不能把 ID Token(身份令牌)拿来调用业务 API(应用程序接口)。第四步校验到期时间、生效时间、签发时间与允许时钟偏差,拒绝过期、尚未生效或异常长寿命。第五步检查 jti(令牌唯一标识)、会话版本、账户冻结、令牌族或撤销状态是否符合当前风险策略。第六步只把白名单声明映射为最小 Authentication(认证结果),不能直接反序列化任意角色或 Java(编程语言)类型。最后在业务层检查作用域、租户和对象归属,签名有效并不表示对所有订单都有权限。密钥发现和公钥缓存要有超时、刷新、旧键保留与未知密钥告警,故障时高风险写操作倾向失败关闭。日志只记录令牌摘要、签发者、受众、密钥标识和失败分类,不保存完整令牌。测试覆盖错误算法、错误受众、过期、未来生效、旧密钥、未知密钥、撤销和权限变更,生产观测按失败原因聚合,避免把所有验证失败都叫“过期”。
    • 追问 1:只验签不验受众有什么风险?
    • 回答 1:攻击者可把面向 A 服务的合法令牌横向拿到 B 服务使用,签名仍正确但授权边界已被突破。
    • 追问 2:允许多大时钟偏差?
    • 回答 2:按基础设施时钟质量和风险设置小窗口,并监控偏差;不能用很大窗口掩盖时间同步故障。
    • 追问 3:公钥服务暂时不可用怎么办?
    • 回答 3:使用有期限的可信缓存和已知旧键继续验证,未知键按风险失败关闭并告警,不能跳过签名。
    • 详情:JWT(JSON 网络令牌)验证流程
  8. 问题:刷新令牌轮换和重放检测怎样设计才完整?

    • 回答思路:把刷新令牌视为一次性高价值凭据,围绕原子消费和令牌族收敛回答。
    • 详细答案:服务端只保存刷新令牌摘要及令牌族状态,刷新时原子把旧值从有效改为已使用并产生新对。旧值再次出现时撤销整个族;客户端做单飞刷新,响应丢失仅允许受控幂等恢复,不能产生平行分支。
    • 进阶追问:为什么检测重放后要撤销新刷新令牌?
    • 进阶回答:系统无法判断旧令牌由合法客户端还是攻击者重放,保留新分支可能把账户继续交给攻击者。
    • 口述答案:我把 Refresh Token(刷新令牌)视为高价值一次性凭据,而不是寿命更长的 Access Token(访问令牌)。授权服务签发时生成随机高熵值,只保存摘要、令牌族编号、客户端、用户、设备、签发时间、到期时间和状态;客户端把它放在系统安全存储或受保护 Cookie(浏览器小型数据)中,不能暴露给业务接口。刷新请求到达后先验证客户端、令牌摘要、族状态、到期时间和风险信号,再以数据库条件更新或原子脚本把旧令牌从 ACTIVE 改为 USED,同时创建新的访问令牌和刷新令牌。这个更新必须只有一个并发请求成功。若已使用的旧令牌再次出现,说明可能是网络重试、客户端并发或令牌被盗;严格模型会撤销整个令牌族并要求重新认证,因为系统无法判断合法方是谁。为了避免正常移动端并发把自己踢下线,客户端做单飞刷新,其余请求等待;服务端可提供极短、绑定请求摘要的幂等响应窗口,但不能让旧令牌长期重复兑换。访问令牌寿命短,注销时撤销刷新族并按风险决定是否在线拒绝当前访问令牌。记录族编号、设备、IP(互联网协议地址)变化、重复使用和轮换延迟,绝不记录原始令牌。故障演练覆盖数据库提交成功但响应丢失、两个数据中心并发刷新、时钟偏差和客户端离线恢复。正确结果是最多一个新分支存活,任何重复都可被检测、审计和收敛,而不是无限生成平行令牌。客户端还必须在失败后停止业务请求风暴。
    • 追问 1:为什么服务端只存刷新令牌摘要?
    • 回答 1:数据库泄露时攻击者不能直接使用原值,验证方式类似随机 API(应用程序接口)密钥。
    • 追问 2:响应丢失后客户端重试如何处理?
    • 回答 2:可用短幂等窗口返回同一轮换结果或要求重新认证,不能再生成第二条分支;策略要和客户端协调。
    • 追问 3:刷新令牌需要 JWT(JSON 网络令牌)格式吗?
    • 回答 3:不需要,随机不透明值更便于服务端撤销与最小泄露;格式选择不改变一次性和轮换要求。
    • 详情:令牌刷新、撤销与轮换
  9. 问题:URL(统一资源定位符)授权、方法授权和领域对象授权怎样分层?

    • 回答思路:按入口粗筛、服务能力、对象归属、状态机和数据库约束逐层收窄。
    • 详细答案:URL(统一资源定位符)规则负责早拒绝,方法授权保护所有调用入口的稳定能力,领域授权检查租户、仓库、订单和金额,状态机判断动作时机,数据库条件更新守住并发。任何一层都不能单独替代其余层。
    • 进阶追问:前端已经按权限隐藏按钮,还需要哪些后端层?
    • 进阶回答:仍需方法、对象和数据约束;客户端不可信,可直接伪造请求。
    • 口述答案:我把授权设计成逐层收窄,而不是在某一层做一次万能判断。网关和 URL(统一资源定位符)规则负责入口粗筛,例如公开接口、管理接口、是否必须认证和大类作用域,优点是早拒绝、减少攻击面,但它不知道订单归属和仓库状态。方法授权保护稳定业务能力,例如 refundadjustStockapproveSettlement,确保控制器、消息消费、定时任务和内部调用最终都进入同一服务边界;角色只作为权限集合管理,业务代码尽量判断明确权限而非到处写角色名。领域对象授权根据当前身份、租户、仓库、货主、订单和金额判断“对哪一条数据能做什么”,查询和更新语句本身要带数据所有权条件,避免先按主键取出再忘记检查。领域状态机继续判断当前状态是否允许动作,例如有退款权限也不能重复退款或超额退款。数据库唯一键、版本和条件更新守住并发与旁路。前端隐藏按钮只表达体验,攻击者可直接构造请求;网关校验也可能被内部入口绕过,所以两者都不能替代后端授权。WMS(仓储管理系统)中,用户先具备 stock:adjust,再要求租户与仓库在授权集合,调整额度和审批满足,最终以仓库、商品、版本和数量条件更新并写流水。审计记录谁、代表哪个服务、对哪个对象、依据哪个规则版本做出何种决策。测试矩阵覆盖不同租户、同租户不同仓库、角色变化、任务入口、对象不存在、并发状态变化和超级管理员受控扩大范围。
    • 追问 1:方法授权是否会因为同类自调用失效?
    • 回答 1:代理式方法安全会,内部 this 调用不重新进入代理;关键授权应放在外部可调用的独立服务边界并配合领域校验。
    • 追问 2:数据权限能只靠 SQL(结构化查询语言)拦截器吗?
    • 回答 2:不应只靠通用改写,它可能漏表、漏别名或重复条件;领域查询明确带边界,拦截器只作纵深防护与检测。
    • 追问 3:超级管理员如何设计?
    • 回答 3:使用独立权限、强再认证、审批、时间窗和全量审计显式扩大范围,不能用空租户隐式代表全部数据。
    • 详情:分层授权与领域权限
  10. 问题:Spring Security(安全框架)方法授权的底层原理和失效边界是什么?

  • 回答思路:复用 AOP(面向切面编程)代理模型说明拦截、决策和旁路。
  • 详细答案:容器为目标 Bean(对象实例)装配方法安全 Advisor(通知器),外部调用经过代理后由 MethodInterceptor(方法拦截器)调用 AuthorizationManager(授权管理器)。手工创建、原始引用、同类自调用、不可代理方法和错误上下文都会旁路或误判。
  • 进阶追问:把安全注解加到 private(私有访问控制)方法能生效吗?
  • 进阶回答:代理无法从外部拦截该内部方法,应把安全边界放在可代理的公共服务入口。
  • 口述答案:方法授权建立在 Spring(Java 应用框架) AOP(面向切面编程)代理模型上。容器识别带方法安全元数据的 Bean(对象实例),通过 Advisor(通知器)和 MethodInterceptor(方法拦截器)在调用前取得当前 Authentication(认证结果)、目标方法、参数和返回对象,交给 AuthorizationManager(授权管理器)计算是否允许;拒绝时抛 AccessDeniedException(拒绝访问异常),再由调用边界转换。它的优点是权限贴近服务能力,控制器、批处理或其他 Bean(对象实例)只要通过代理调用就共享规则。失效边界也与代理一致:对象手工 new、调用者持有原始引用、同类 this 自调用、方法不可代理、初始化阶段调用、切点元数据放错位置,都会让拦截器没有执行机会。即使方法授权生效,也不能把复杂业务不变量全部塞进表达式;仓库、货主、订单状态、金额额度和并发版本应由显式领域服务和查询约束处理。表达式调用自定义权限组件时要控制数据库访问次数,避免列表接口每行做一次远程查询。权限元数据和实际 Advisor(通知器)顺序要可诊断,尤其与事务代理组合时要明确授权在事务外早拒绝,还是为了读取事务内对象而进入事务;不能靠偶然 @Order。测试必须从容器取得真实代理并通过外部接口调用,覆盖允许、拒绝、匿名、同类调用、异步线程和对象状态变化。线上排查先确认最终代理、适用 Advisor(通知器)和安全上下文,再看授权决策,不以“注解存在”证明功能生效。
  • 追问 1:授权应该在事务外还是事务内?
  • 回答 1:静态能力可在事务外早拒绝;依赖一致性数据的对象授权可能需在事务内读取,但要缩短事务并防止信息泄露。
  • 追问 2:如何修复同类自调用?
  • 回答 2:把独立安全边界拆到另一个 Bean(对象实例)并通过代理调用,或在显式领域方法再次校验,不把暴露当前代理作为默认方案。
  • 追问 3:方法授权能替代控制器 URL(统一资源定位符)规则吗?
  • 回答 3:不完全替代;入口规则早拒绝并缩小攻击面,方法授权守住所有调用入口,两层互补。
  • 详情:AOP(面向切面编程)代理链
  1. 问题:CSRF(跨站请求伪造)攻击的条件是什么,如何正确防护?
  • 回答思路:先判断浏览器是否自动携带凭证,再讲随机令牌和纵深控制。
  • 详细答案:攻击成立依赖受害者浏览器自动带上目标站会话或 Cookie(浏览器小型数据)。服务用不可预测且绑定会话的 CSRF Token(跨站请求伪造令牌)校验状态变更请求,并配合同站策略、来源检查、正确请求方法和敏感操作再认证。
  • 进阶追问:前后端分离是否可以直接关闭 CSRF(跨站请求伪造)?
  • 进阶回答:不能按架构标签判断;只要认证凭据由浏览器自动携带,风险仍然存在。
  • 口述答案:CSRF(跨站请求伪造)的关键条件不是前后端是否分离,而是浏览器会不会在攻击者构造的请求中自动携带目标站凭据。受害者已登录 WMS(仓储管理系统),攻击站诱导浏览器提交库存调整表单;浏览器按 Cookie(浏览器小型数据)规则自动带上会话,服务若只检查会话就会把攻击请求当本人。主要防护是服务生成攻击站无法读取和预测的 CSRF Token(跨站请求伪造令牌),要求状态变更请求同时提交并校验;配合 SameSite(同站策略) Cookie(浏览器小型数据)、精确来源或 Referer(来源页)校验、禁止 GET(获取请求)产生副作用,以及敏感操作再认证。令牌要绑定会话或采用可靠双提交设计,随机性足够,失败统一拒绝。若纯 API(应用程序接口)的 Bearer Token(持有者令牌)只由脚本显式放入 Authorization(授权请求头),跨站表单不能自动带上,传统 CSRF(跨站请求伪造)风险较低;但若 Token(令牌)存入自动携带的 Cookie(浏览器小型数据),风险重新出现。关闭 CSRF(跨站请求伪造)前必须记录凭据模型和浏览器行为,不能仅因“无页面”就关闭。CSRF(跨站请求伪造)令牌也不能防 XSS(跨站脚本),同源恶意脚本可能读取令牌并发请求,所以还要输出编码、CSP(内容安全策略)、依赖治理和敏感令牌隔离。回归测试使用真实浏览器验证合法表单、缺令牌、错误令牌、跨来源、同站子域和登录后会话轮换。
  • 追问 1:SameSite(同站策略)能完全替代令牌吗?
  • 回答 1:不能,兼容、子域和正常跨站流程都有边界;高风险操作应采用多层防护。
  • 追问 2:移动应用需要 CSRF(跨站请求伪造)吗?
  • 回答 2:原生客户端不受浏览器自动 Cookie(浏览器小型数据)模型影响,但内嵌网页和共享 Cookie(浏览器小型数据)仍需评估。
  • 追问 3:支付回调需要 CSRF(跨站请求伪造)令牌吗?
  • 回答 3:通常不用浏览器会话,而采用渠道签名、时间戳和防重放;这是不同信任模型,不能混用。
  • 详情:CSRF(跨站请求伪造)防护
  1. 问题:CORS(跨源资源共享)如何配置,为什么它不是安全认证?
  • 回答思路:区分浏览器同源放行与服务端身份、权限验证。
  • 详细答案:CORS(跨源资源共享)控制哪些来源网页脚本可跨源交互,不约束非浏览器客户端。应精确白名单来源、方法和请求头,允许凭据时禁止通配来源;预检可以放行到策略处理,真实请求仍必须认证授权。
  • 进阶追问:完全关闭 CORS(跨源资源共享)能阻止接口攻击吗?
  • 进阶回答:不能,攻击者可绕过浏览器直接请求服务,后端安全仍依赖认证、授权和限流。
  • 口述答案:CORS(跨源资源共享)是浏览器同源策略的受控放行机制,回答“某来源网页脚本能否向本服务发送特定跨源请求并读取响应”,不证明调用者身份。浏览器对非简单请求先发 Preflight(预检请求),服务根据 Origin(来源)、方法和请求头返回允许策略;真实请求仍必须经过认证、授权和领域校验。非浏览器客户端不受同源策略限制,所以即使 CORS(跨源资源共享)完全不允许,攻击者仍可直接调用后端。配置时以固定白名单精确匹配协议、主机和端口,区分生产、测试和本地环境;允许凭据时不能使用通配来源,更不能把任意请求 Origin(来源)原样回显。只允许实际使用的方法和请求头,预检缓存时间按变更速度设置,并确保拒绝响应不泄露敏感信息。多租户自定义域名不能用脆弱字符串后缀判断,应规范化域名并查受控租户绑定。CORS(跨源资源共享)过滤必须位于需要的位置,使预检不会被错误认证阻断,但这不表示真实请求可匿名。项目验收准备允许来源、相似恶意域名、错误协议、错误端口、带凭据、无凭据和非浏览器样本;同时验证响应头没有缓存串来源。若出现跨域故障,先区分预检失败、浏览器阻止读取、真实请求 401/403 和后端业务错误,不能为了“先能用”配置 * 加凭据。安全结论是:CORS(跨源资源共享)限制浏览器脚本能力,认证授权限制服务端资源能力,两者互补但不可替代。
  • 追问 1:为什么允许凭据时不能用 *
  • 回答 1:浏览器规范禁止这种组合,它也无法表达对具体可信来源的限制;应返回经过白名单验证的明确来源。
  • 追问 2:预检请求需要登录吗?
  • 回答 2:通常应允许预检到达 CORS(跨源资源共享)处理并仅返回策略,真实请求再认证;具体链顺序需测试。
  • 追问 3:CORS(跨源资源共享)能防 CSRF(跨站请求伪造)吗?
  • 回答 3:不能完整防护,简单表单请求可能无需预检且浏览器仍会发送;读取限制也不等于阻止副作用。
  • 详情:CORS(跨源资源共享)与浏览器边界
  1. 问题:OAuth 2.0(开放授权协议)和 OIDC(开放身份连接协议)应如何向面试官讲清?
  • 回答思路:先分授权与身份目标,再解释角色、流程、令牌受众和安全参数。
  • 详细答案:OAuth 2.0(开放授权协议)让客户端取得受限 Access Token(访问令牌)访问资源,OIDC(开放身份连接协议)增加 ID Token(身份令牌)和标准身份声明。授权码配合 PKCE(代码交换证明密钥)完成安全兑换,两类令牌必须按受众和用途隔离。
  • 进阶追问:服务身份和最终用户身份怎样同时保留?
  • 进阶回答:使用明确委托或令牌交换,审计中分别记录发起用户、执行服务、受众和权限范围。
  • 口述答案:我先从目标分开:OAuth 2.0(开放授权协议)解决客户端在资源所有者许可下,如何获得受限 Access Token(访问令牌)访问资源服务器;它定义资源所有者、客户端、授权服务器和资源服务器等角色,但本身不是标准登录协议。OIDC(开放身份连接协议)在 OAuth 2.0(开放授权协议)之上增加身份层,通过 ID Token(身份令牌)、标准声明和 UserInfo(用户信息)端点让客户端验证“用户在哪个授权服务器完成了什么认证”。Authorization Code(授权码)流程中,客户端先生成 PKCE(代码交换证明密钥)的 verifier(校验秘密)和 challenge(挑战值),浏览器跳到授权服务器登录并同意,客户端收到一次性授权码,再用授权码、校验秘密、精确重定向地址和自身凭证换取令牌。授权码不能直接访问资源。ID Token(身份令牌)的受众是客户端,用于建立登录;Access Token(访问令牌)的受众是资源服务器,用于授权,不能互换。资源服务仍需验证签发者、受众、时间、类型和作用域。Client Credentials(客户端凭证)代表应用自身,不代表最终用户;需要用户委托时应采用明确的令牌交换或代表机制,并同时审计用户与调用服务。安全边界还包括 state(状态参数)防请求关联攻击、nonce(一次性随机数)防身份响应重放、精确重定向白名单、前后通道隔离和密钥轮换。项目接入第三方登录时,本地账号绑定必须以稳定签发者加主体标识为键,不能只用邮箱;注销还要区分本地会话、授权服务器会话和令牌撤销。这样解释能避免把“拿到一个 JWT(JSON 网络令牌)”误认为协议已经安全完成。
  • 追问 1:为什么不能用 Access Token(访问令牌)当 ID Token(身份令牌)?
  • 回答 1:访问令牌面向资源服务器,声明和受众未必适合客户端认证;混用会破坏令牌用途隔离。
  • 追问 2:PKCE(代码交换证明密钥)替代 state(状态参数)吗?
  • 回答 2:不替代,前者约束授权码兑换,后者关联授权请求和响应并防止请求被替换。
  • 追问 3:服务客户端能否申请用户权限作用域?
  • 回答 3:服务身份只能获得自身被批准能力;需要代表用户时必须有显式委托和可审计主体链。
  • 详情:OAuth 2.0(开放授权协议)与 OIDC(开放身份连接协议)
  1. 问题:异步线程和线程池中怎样安全传播 SecurityContext(安全上下文)?
  • 回答思路:先判断是否需要继承用户,再讲任务级快照、设置、恢复和清理。
  • 详细答案:短异步任务在提交时捕获不可变 SecurityContext(安全上下文)快照,工作线程执行前保存旧值并设置,finally 中恢复或清理。长任务改用作业记录和最小服务身份,执行时重新校验租户与对象权限。
  • 进阶追问:为什么不能依赖 InheritableThreadLocal(可继承线程本地变量)?
  • 进阶回答:线程池线程提前创建且复用,继承时点不是每次任务提交,容易得到旧身份并泄漏。
  • 口述答案:我会先判断任务是否真的需要继承用户身份。短时、请求内异步且语义上仍代表当前用户时,可以用 DelegatingSecurityContextExecutor(委派安全上下文执行器)或等价 TaskDecorator(任务装饰器):在提交任务时捕获当前 SecurityContext(安全上下文)的不可变快照,而不是在线程池创建时固定一个身份;工作线程执行前保存旧上下文、设置任务快照,同时设置租户和 TraceId(链路标识);在 finally 中恢复旧值或清空,确保成功、异常、取消和超时都不泄露。直接使用可继承线程本地变量不可靠,因为线程池线程在请求之前已创建并长期复用,继承时点与任务不一致。长时导出、Runner(执行器)调度和 MQ(消息队列)消费更不应携带用户长期 Token(令牌),而应在作业记录中保存发起者、租户、资源范围、审批和有效期,执行时使用最小服务身份,并重新检查对象归属与任务状态。消息中的用户编号只是业务数据,必须来自可信发布者并受签名或本地事实约束,消费者不能据此伪造管理员 Authentication(认证结果)。上下文传播也不能替代数据库查询条件,WMS(仓储管理系统)读取仍要显式带租户和仓库。测试使用单线程池连续执行不同租户任务,覆盖第一个任务异常、第二个任务无身份、嵌套任务和取消,验证身份、租户、日志上下文与缓存键全部恢复。线上监控上下文缺失、服务身份超权、任务拒绝和跨租户查询,作业审计同时记录发起人和实际执行服务。
  • 追问 1:为什么不能在线程池 Bean(对象实例)创建时捕获上下文?
  • 回答 1:那只会捕获启动线程或某个固定身份,后续所有任务错误复用,甚至永久获得管理员权限。
  • 追问 2:异步任务权限变化怎么办?
  • 回答 2:高风险任务执行时复核;低风险报表可使用经批准的范围快照,但要有到期、撤销和下载再授权。
  • 追问 3:只传播用户、不传播租户行不行?
  • 回答 3:不行,用户在不同租户可能有不同权限;身份、租户、资源范围和审计链必须一致。
  • 详情:线程与异步上下文传播
  1. 问题:多租户系统怎样防止“有权限但串租户”的越权?
  • 回答思路:建立身份、租户选择、数据访问、缓存消息和异步任务的一致边界。
  • 详细答案:当前租户必须从可信身份和受控选择得到,方法检查动作权限,查询与更新显式带租户、仓库和货主条件;缓存键、锁、索引、消息与文件路径也包含租户。线程上下文只是辅助,不能替代数据约束。
  • 进阶追问:业务主键全局唯一时为什么仍要租户条件?
  • 进阶回答:唯一性只解决定位,不证明访问权;租户条件能防编码旁路、形成审计并保持数据边界。
  • 口述答案:多租户安全不能只在登录时把 tenantId 放进 Token(令牌),而要建立从身份、请求、服务到数据库的完整不变量。首先,可信认证结果包含主体和允许租户集合,当前租户必须通过受控切换或域名绑定选择,不能直接相信客户端任意请求头;服务间转发的租户信息要由网关或调用方签名并在下游与令牌主体核对。其次,方法授权判断动作能力,例如能否查看库存;对象授权再判断仓库、货主和订单属于当前租户。第三,查询仓储接口把 tenant_id 作为必填参数和索引前缀,更新使用 tenant_id + business_id + version 条件,不能先按全局主键查出后在内存过滤。缓存键、分布式锁、搜索索引、对象存储路径和消息分区都要包含租户边界,否则数据库正确也会从缓存泄露。后台任务保存租户和授权范围,线程池执行前设置并在 finally 清理,但数据访问仍要求显式租户条件,线程变量只作辅助。超级管理员使用独立权限、强再认证、审批、短时间窗和全量审计显式跨租户,不用空租户表示全部。防御性测试构造相同业务编号在两个租户存在、篡改路径仓库、缓存命中、异步重试和批量导出,确认结果只属于当前租户。线上以请求主体、当前租户、数据租户、查询模板和返回行数做采样审计,发现三者不一致立即阻断。若发生串租户,先关闭受影响查询或降级只读,保存请求、缓存和 SQL(结构化查询语言)证据,再修规则并评估数据泄露范围。
  • 追问 1:全局唯一主键能否省略租户条件?
  • 回答 1:不能,唯一性不代表授权;显式租户条件同时防编码错误、泄露并改善审计和索引设计。
  • 追问 2:租户条件放 MyBatis(持久层框架)插件是否足够?
  • 回答 2:不够,复杂 SQL(结构化查询语言)、绕过路径和非数据库资源可能漏掉;领域接口必须显式表达边界,插件只做纵深防护。
  • 追问 3:跨租户报表怎么做?
  • 回答 3:建立独立分析权限和脱敏数据通道,按批准范围生成,不让普通在线查询通过空条件扩大权限。
  • 详情:多租户与对象级授权
  1. 问题:第三方支付回调怎样完成验签、防重放、幂等与资金一致性?
  • 回答思路:按入口可信、短期重放、长期幂等、状态机、查单对账和履约事件回答。
  • 详细答案:入口对原始报文验签并校验时间窗和 Nonce(随机数),短期重放键原子写入;渠道事件唯一键吸收长期重复。支付事务核对商户、金额、币种与状态,写流水和 Outbox(发件箱);未知结果主动查单,日终对账,下游再幂等履约。
  • 进阶追问:签名通过为什么仍不能直接发货?
  • 进阶回答:签名只证明报文来源和完整性,资金状态还需本地订单核对、渠道事实和状态机确认。
  • 口述答案:支付回调的信任主体是渠道,不是登录用户,所以我会设计独立安全链和领域流程。入口读取原始字节、渠道、商户、事件编号、时间戳、Nonce(随机数)和签名,按渠道协议选择密钥和规范化算法,先校验时间窗、商户和签名;不能解析再重新序列化后验签,否则字段顺序和编码会改变。随后以 (channel,merchant,nonce) 原子写入短期重放表,拒绝时间窗内重复随机数;以 (channel,event_id) 数据库唯一键实现长期幂等,重复同内容返回首次处理结果,事件编号相同但金额或订单冲突则告警,不能静默覆盖。进入本地事务后按支付单号锁定或条件更新,核对商户、金额、币种、渠道状态和当前本地状态;只有允许的状态迁移才能写支付流水、订单状态和 Outbox(发件箱)事件。签名有效只证明报文来源与完整性,不证明渠道资金一定成功;结果未知、金额冲突或高风险事件要主动查单,日终还用渠道账单对账。事务提交后再确认回调,若响应丢失渠道会重试,幂等记录吸收重复。下游履约消费可靠事件,并以支付单和事件编号幂等,不能在回调过滤器里直接发货。密钥保存在密钥管理系统,支持双密钥轮换;日志保存报文摘要和事件编号,不记录完整敏感信息。监控验签失败、时间偏差、重放、幂等冲突、查单差异和对账差异,事故时可停止自动履约但继续接收并落原始回调用于恢复,并保留完整审计链。
  • 追问 1:回调签名正确但金额不一致怎么办?
  • 回答 1:不推进支付成功,记录冲突并主动查单或人工核验;来源可信不代表业务字段符合本地订单。
  • 追问 2:Nonce(随机数)表过期后还能防重复吗?
  • 回答 2:短期重放由随机数表防,长期业务重复由渠道事件唯一键和支付状态机吸收,两层目的不同。
  • 追问 3:为什么不在验签过滤器里直接更新订单?
  • 回答 3:过滤器只建立入口可信性,资金状态、幂等、事务和消息属于支付领域,混合会让失败边界和重试语义失控。
  • 详情:支付回调安全时序
  1. 问题:签名密钥怎样平滑轮换,紧急泄露时又如何处置?
  • 回答思路:区分正常双密钥发布时序和私钥泄露的紧急撤销。
  • 详细答案:正常轮换先发布新公钥并等待资源服务可见,再用新私钥签发;验证端同时保留旧、新公钥直到旧令牌寿命结束。私钥泄露则立即停止签发、切换新键、撤销受影响令牌并评估全量重新认证。
  • 进阶追问:为什么未知密钥标识不能直接远程下载客户端给出的证书?
  • 进阶回答:这会把信任来源交给攻击者并引入服务端请求伪造,密钥只能来自预配置可信发现端点。
  • 口述答案:正常轮换要把“发布验证材料”和“开始签发”分开。先生成新密钥 K1,私钥只进入受控签发服务,公钥和唯一密钥标识发布到可信发现端点;等待资源服务器刷新缓存并确认能识别 K1 后,签发端才从旧私钥 K0 切到 K1。此时验证端同时接受 K0/K1,因为切换前签发的旧 Token(令牌)仍在有效期内。旧公钥至少保留到旧令牌最大寿命、传播延迟和允许时钟偏差都结束,确认没有合法旧流量后再撤销。资源服务只把密钥标识当受控键选择器,拒绝未知算法、任意证书地址和不受信任密钥。缓存要有最大陈旧时间、后台刷新、未知密钥快速刷新一次和失败告警,不能每个请求远程取键,也不能发现服务故障就跳过签名。轮换演练观察按密钥标识的签发量、验证成功率和 401 原因,支持回滚签发到 K0 但不回退权限规则。紧急私钥泄露不同:立即停止受影响密钥签发,发布撤销并切新键,资源服务根据风险拒绝仍在有效期的受影响令牌,可能要求全量重新认证;同时冻结刷新族、轮换相关客户端秘密、审计泄露时间窗和令牌受众。可用性与安全要事先分级,高风险支付写操作失败关闭,低风险只读可采用短暂缓存策略。复盘验证密钥是否出现在镜像、日志、配置仓库或内存转储,并建立自动过期与最小访问权限。整个过程必须按密钥标识统计签发和验证流量,未知密钥、旧键异常增长和公钥刷新失败都要告警;演练还要确认回滚只改变签发键,不会错误放宽算法、受众或权限验证。
  • 追问 1:为什么不能先签发新密钥再发布公钥?
  • 回答 1:资源服务尚不认识新密钥,会把所有新令牌当未知签名并产生集中 401。
  • 追问 2:公钥泄露需要紧急撤销吗?
  • 回答 2:公钥本就用于分发,单独泄露通常不是机密事故;要确认完整性和是否伴随私钥或发现端点被篡改。
  • 追问 3:密钥标识可以重复使用吗?
  • 回答 3:不应,重复会让缓存和审计无法区分材料版本,可能验证到错误密钥。
  • 详情:密钥发布与轮换
  1. 问题:访问令牌疑似泄露时,如何按生产事故流程排查和止血?
  • 回答思路:先识别泄露对象和半径,再止血、保留证据、核查业务动作并修复。
  • 详细答案:区分访问令牌、刷新令牌、客户端秘密和私钥;按对象撤销单令牌、令牌族或密钥。保存令牌摘要、主体、受众、设备和业务流水,暂停高风险写而不关闭验证,追查浏览器、日志、URL(统一资源定位符)或依赖泄露路径。
  • 进阶追问:重置密码为什么不是完整止血?
  • 进阶回答:既有令牌可能仍有效,必须同步撤销会话、刷新族和必要的访问令牌。
  • 口述答案:我按影响、止血、现场、根因、修复、回归和复盘推进。先确认泄露对象是单个 Access Token(访问令牌)、Refresh Token(刷新令牌)、客户端秘密还是签名私钥,因为处置半径完全不同;根据 jti(令牌唯一标识)、用户、客户端、受众、签发时间、IP(互联网协议地址)和设备变化估算影响,但日志中只处理令牌摘要,不传播原值。单访问令牌可加入短期拒绝表、提升会话版本或冻结账户,撤销对应刷新族并要求重新认证;刷新令牌泄露重点检查是否发生旧令牌重复使用并撤销整族;私钥泄露则按密钥级紧急轮换和全局令牌失效。止血期间高风险支付、退款和库存调整可要求再认证、降级只读或暂停,不能简单关闭验证。保存网关、资源服务、授权服务、密钥访问、刷新记录和业务流水,沿时间线区分令牌从浏览器存储、XSS(跨站脚本)、日志、监控、URL(统一资源定位符)还是第三方 SDK(软件开发工具包)泄露。业务层核查可疑令牌实际访问了哪些租户、订单和资金动作,利用幂等、状态机和审计决定是否补偿。修复可能包括改为安全 Cookie(浏览器小型数据)或系统密钥链、日志脱敏、缩短访问寿命、刷新轮换、CSP(内容安全策略)和密钥权限收缩。回归模拟窃取、重放、刷新、注销和轮换,确认旧令牌被拒、新会话可用、合法用户不会无限刷新。最后通知受影响用户和合规团队,量化暴露窗口与数据范围。
  • 追问 1:为什么不能只改用户密码?
  • 回答 1:已签发令牌可能继续有效,必须同时撤销会话或刷新族,并按策略拒绝访问令牌。
  • 追问 2:如何判断是令牌被盗还是用户网络变化?
  • 回答 2:结合设备、刷新重放、地理速度、行为和业务动作做风险判断,不能仅凭 IP(互联网协议地址)变化定罪。
  • 追问 3:事故期间能否延长令牌寿命减少登录压力?
  • 回答 3:不能,这会扩大泄露窗口;应扩容认证链路、分批重登并保护高风险操作。
  • 详情:令牌撤销与安全故障
  1. 问题:升级 Spring Security(安全框架)版本时,怎样避免过滤器顺序和配置 API(应用程序接口)变化导致安全回归?
  • 回答思路:以实际小版本、运行时链基线、契约矩阵和灰度指标控制升级。
  • 详细答案:升级前导出多链匹配器、过滤器顺序、授权规则、会话和浏览器防护配置;按官方稳定文档迁移后,用匿名、过期、无权、对象越权、预检和异步样本比较实际链。灰度按链标识观察 401/403,禁止临时全放行。
  • 进阶追问:为什么相对过滤器位置优于裸排序数字?
  • 进阶回答:相对位置表达安全语义,跨版本更可核对;绝对数字可能随内部默认顺序变化而失真。
  • 口述答案:我不会把博客中的“默认过滤器顺序”当永久事实,而是先从项目依赖树锁定 Spring Boot(快速开发框架)、Spring Security(安全框架)和 Servlet(服务器小程序)技术栈的实际小版本,再阅读对应官方稳定文档和迁移指南。升级前导出每条 SecurityFilterChain(安全过滤器链)的请求匹配器、顺序、过滤器清单、授权规则、会话策略、CSRF(跨站请求伪造)与 CORS(跨源资源共享)配置,形成可比较基线;同时登记自定义 Filter(过滤器)相对于已知框架过滤器的语义位置,避免只使用一个容易变化的绝对数字。配置方式从旧适配器迁移到组件化 SecurityFilterChain(安全过滤器链)时,重点检查多链首匹配、默认拒绝、静态资源、错误分派、异步分派和管理端点是否仍落入预期链。认证管理器、密码编码器、方法授权、会话创建、请求缓存和安全响应头也要逐项核对,不因编译通过就认为行为一致。回归矩阵包含匿名、正常用户、过期令牌、错误受众、无权限、对象越权、CSRF(跨站请求伪造)、预检请求、异步上下文和退出;测试必须通过真实容器和代理执行。灰度时按链标识和错误原因对比 401/403、认证耗时、权限拒绝和业务成功率,并准备回滚配置与兼容旧密钥。任何“临时 permitAll(全部放行)”都必须禁止进入生产。最终把运行时链清单、版本、配置哈希和规则版本暴露到受控诊断,不泄露秘密,使下一次升级能有证据比较,而不是重新猜默认值。
  • 追问 1:编译通过为什么仍可能不安全?
  • 回答 1:默认匹配、过滤器顺序、会话和授权语义可能改变,类型正确不代表请求落在同一安全路径。
  • 追问 2:自定义过滤器应按数字排序吗?
  • 回答 2:优先相对某个语义明确的框架过滤器前后插入,并用实际链测试;裸数字难以跨版本维护。
  • 追问 3:如何发现某路径漏进安全链?
  • 回答 3:建立全路径允许/拒绝契约和默认拒绝兜底,导出命中链,扫描未被任何专用链覆盖的控制器映射。
  • 详情:安全链版本边界
  1. 问题:Remember-Me(记住登录)和并发会话限制怎样做到安全可用?
  • 回答思路:把长期恢复凭据、敏感操作再认证和集群会话注册分开设计。
  • 详细答案:Remember-Me(记住登录)使用可撤销、可轮换的长期凭据恢复降权身份,敏感动作仍需强认证。并发会话限制由共享会话仓库记录活动会话并选择拒绝新登录或踢旧登录,改密、冻结和退出同时撤销两类状态。
  • 进阶追问:节点宕机会不会让会话永久占用名额?
  • 进阶回答:会,因此需要绝对过期、后台清理和登录时核实真实状态,不能只依赖正常关闭事件。
  • 口述答案:Remember-Me(记住登录)不是把普通 Session(会话)无限延长,而是在会话过期后用长期凭据恢复一个受限身份。长期凭据应是高熵随机值或可靠签名结构,服务端保存摘要、用户、序列号、设备、到期和撤销状态;每次使用最好轮换,旧值再次出现按窃取处理。恢复出的身份应被标记为 Remember-Me(记住登录)认证,查看普通页面可以接受,但修改密码、支付、退款、库存批量调整等敏感动作要求输入密码、多因素或其他强再认证。退出、改密、冻结和风险事件要同时撤销当前会话与长期令牌。并发会话限制则依赖共享 SessionRegistry或会话仓库,记录用户到活动会话的映射;选择“拒绝新登录”或“新登录踢旧登录”前要考虑客服、移动端和后台管理场景。集群中只在单节点计数会失效,事件通知或共享存储必须覆盖所有节点;节点宕机和非正常关闭还要通过过期时间清理陈旧记录。登录成功要轮换会话标识防 Session Fixation(会话固定攻击),并发限制不是替代。观测记录会话创建、轮换、失效、记住登录恢复、再认证和异常地理变化,不记录原始 Cookie(浏览器小型数据)。测试两个节点并发登录、旧会话下一请求、浏览器关闭、节点崩溃、长期令牌重放和改密后恢复。对高权限后台,我通常禁用或严格限制 Remember-Me(记住登录),并采用短空闲时间、绝对期限和一次会话策略。
  • 追问 1:Remember-Me(记住登录)恢复后为什么要降权?
  • 回答 1:长期凭据暴露窗口更大且用户未刚刚证明密码,敏感动作需要更强的新鲜认证。
  • 追问 2:会话注册表记录删除失败怎么办?
  • 回答 2:用会话绝对过期和后台清理收敛,登录时核实真实会话状态,避免陈旧记录永久阻止用户。
  • 追问 3:并发会话限制能防账号共享吗?
  • 回答 3:只能增加成本,仍需设备、地理、行为和风险策略;共享者可轮流使用单会话。
  • 详情:会话与长期登录治理
  1. 问题:为什么“前端隐藏按钮”和“网关已验 Token(令牌)”都不等于权限控制?
  • 回答思路:从客户端不可信和网关缺少领域信息两个边界说明。
  • 详细答案:前端代码可被修改,攻击者可直接发请求;网关只能验证外部身份和粗粒度路由,不知道订单、仓库、金额和状态。服务必须验证可信身份来源,实施方法与对象授权,并以数据库约束守住并发和旁路。
  • 进阶追问:内部网络请求头为什么也不能直接信任?
  • 进阶回答:内部服务或错误路由可伪造普通头,应使用工作负载身份、受保护通道和受众受限的委托凭据。
  • 口述答案:前端运行在用户控制的环境,页面代码、网络请求和按钮都可修改,攻击者可以直接构造 HTTP(超文本传输协议)请求,所以隐藏按钮只能改善体验,不能形成可信边界。网关处于服务入口,适合校验 Token(令牌)签名、签发者、受众和粗粒度路由作用域,也能做限流和统一拒绝;但它通常不知道订单属于哪个商户、用户允许哪些仓库、退款额度是多少、库存当前状态能否调整。内部网络误配置、服务间调用、MQ(消息队列)消费、定时任务和测试入口还可能绕过网关,如果服务完全信任 X-User-Id 等普通请求头,就会形成横向越权。正确模型是纵深防御:网关验证外部凭证并删除客户端伪造的身份头,再用受保护通道传递服务身份或用户委托;资源服务重新验证受众和可信来源,在方法层判断动作权限,在领域层判断租户、仓库、货主、订单、金额和状态,数据库用条件更新、唯一键和版本守住并发。前端可以读取后端返回的能力列表决定展示,但后端每次仍重新决策。WMS(仓储管理系统)库存按钮不可见并不能阻止 POST /stock/adjust;服务必须要求 stock:adjust、仓库归属、审批与幂等键,更新语句带租户和版本。测试不仅从浏览器点按钮,还要直接调用接口、篡改路径、从内部地址调用、伪造身份头和通过异步入口触发。审计同时记录外部用户、调用服务、网关链路和领域对象,才能证明真正执行者与授权依据。
  • 追问 1:服务是否需要重复验签?
  • 回答 1:取决于架构,可自行验证终端令牌或验证网关签发的短期内部凭据,但不能盲信可伪造请求头。
  • 追问 2:权限列表由前端缓存多久?
  • 回答 2:只影响展示,可按版本缓存;真正授权以后端实时或受控快照为准,前端缓存不能决定放行。
  • 追问 3:内部服务都在私网能否信任?
  • 回答 3:不能把网络位置等同身份,应认证工作负载、限制受众和最小权限,并审计服务间调用。
  • 详情:授权分层与业务边界
  1. 问题:请设计一套 WMS(仓储管理系统)库存调整的完整安全方案。
  • 回答思路:把认证、能力、对象、审批、幂等、并发、异步和审计组成完整闭环。
  • 详细答案:用户先认证,再检查库存调整权限、租户、仓库和货主;高额度进入再认证与审批。请求编号唯一约束幂等,数据库以数量和版本条件更新,写不可变流水与 Outbox(发件箱)。异步作业保存受控范围并在执行时复核。
  • 进阶追问:合法管理员的两个并发请求为什么仍可能超卖?
  • 进阶回答:授权只说明能操作,不保证并发原子性;必须由数据库条件更新和版本约束裁决。
  • 口述答案:我先把资产和动作分类:查看库存、普通调整、批量调整、跨仓调拨和审批的风险不同。用户登录可采用后台 Session(会话)或短 Token(令牌),成功后只建立身份;方法授权要求明确权限如 stock:readstock:adjuststock:approve,角色只负责聚合。每次请求选择可信租户,仓库和货主必须在当前授权集合,查询与更新都带 tenant_id、warehouse_id、owner_id,不能前端过滤。调整请求携带稳定业务编号、商品、批次、数量、原因和客户端版本;数据库以租户加业务编号唯一约束幂等,同编号同参数返回首次结果,参数冲突拒绝。高额度或批量动作进入审批和强再认证,申请人与审批人职责分离。库存扣减或增加使用 available + delta >= 0、版本号和状态条件更新,受影响行数为零时区分无权、库存不足和并发冲突;成功写不可变库存流水、操作者、调用服务、前后值和审批编号,并通过 Outbox(发件箱)可靠发布下游事件。异步导入把用户、租户、授权范围和文件摘要写作业表,执行时用最小服务身份并复核范围,线程池上下文在 finally 清理。网关做外部认证和限流,服务仍做对象授权;前端隐藏按钮不参与信任。安全上配置 CSRF(跨站请求伪造)防护、会话轮换、敏感字段脱敏和下载再授权。告警覆盖跨租户拒绝、短时大量调整、幂等冲突、审批绕过和负库存条件失败。事故恢复依靠流水重放、审计和补偿,而不是直接改库存余额。
  • 追问 1:如何防止管理员误操作?
  • 回答 1:额度、双人审批、预览差异、强再认证、冷静期与可回滚补偿共同控制,权限本身不能防误操作。
  • 追问 2:批量文件里的仓库需要逐行授权吗?
  • 回答 2:需要,提交时预检、执行时复核,每行写明确结果;文件级通过不能扩大到未授权仓库。
  • 追问 3:为什么还要数据库条件更新?
  • 回答 3:授权不解决并发,两个合法请求仍可能超卖;数据库条件是最终原子约束。
  • 详情:WMS(仓储管理系统)安全案例
  1. 问题:如何系统排查“登录成功但接口偶发无权或串用户”的线上故障?
  • 回答思路:按 401/403/对象越权/上下文泄漏分类,从链路到数据逐层取证。
  • 详细答案:保存命中安全链、线程、主体、租户、权限版本、密钥标识和 SQL(结构化查询语言)条件;检查认证原因、方法代理、对象归属、缓存键和上下文清理。用单线程交替双用户并注入异常复现,修复后覆盖取消和超时。
  • 进阶追问:为什么缓存键也属于安全排查范围?
  • 进阶回答:缓存若缺租户或权限维度,会把正确查询结果返回给另一身份,表象与上下文串号相同。
  • 口述答案:我先按影响、止血和证据推进,不直接放宽权限。确认故障是集中 401、集中 403、特定租户越权、异步任务缺身份还是线程串号;高风险写操作先暂停或降级只读,保留登录和查询以缩小影响。为样本收集 TraceId(链路标识)、线程、节点、命中 SecurityFilterChain(安全过滤器链)、认证提供者、主体摘要、租户、权限版本、令牌签发者/受众/密钥标识、授权规则和最终 SQL(结构化查询语言)条件,不记录密码和完整 Token(令牌)。若是 401,按签名、未知密钥、到期、时钟、受众、撤销和会话仓库分类;若是 403,比较当前权限、方法 Advisor(通知器)、对象归属和前端是否误把状态冲突解释成无权。偶发串用户重点检查自定义 Filter(过滤器)是否所有异常分支都清理 SecurityContext(安全上下文),TaskDecorator(任务装饰器)是否在提交时捕获并在结束时恢复,租户线程变量与日志上下文是否同步清理;同时排除缓存键缺租户和数据库查询漏条件。方法授权还要确认对象来自容器、调用经过代理且不是同类自调用。做一个单线程交替双用户复现,故意让第一个任务异常,观察第二个任务身份、租户和查询。修复后回归正常、异常、取消、超时、密钥轮换、权限发布与跨节点会话,灰度比较错误原因分布和越权拒绝。最后补上运行时链清单、上下文泄漏检测和多租户契约测试,避免靠人工日志再次发现。
  • 追问 1:为什么不先重启服务?
  • 回答 1:重启可能暂时清空线程和缓存却销毁现场,无法区分上下文泄漏、缓存污染和配置错误;应先止血并保存证据。
  • 追问 2:怎样证明不是数据库脏数据?
  • 回答 2:对同一业务键核对权威表、查询条件、缓存命中和审计主体,比较绕过缓存的受控查询结果。
  • 追问 3:如何监控上下文泄漏?
  • 回答 3:任务边界断言开始/结束状态,采样对比请求身份与数据租户,统计无请求上下文却存在用户身份的执行。
  • 详情:安全上下文与异常排查
  1. 问题:从设计思想看,Spring Security(安全框架)为什么采用过滤器链、策略接口和上下文模型?
  • 回答思路:从多步安全协议、可组合策略、执行身份和纵深防御概括。
  • 详细答案:过滤器链以责任链组织请求安全步骤,代理桥接容器,认证与授权接口用策略模式隔离具体算法,SecurityContext(安全上下文)提供一次执行的身份快照。组合带来扩展性,也要求顺序、清理、默认拒绝和可观测性。
  • 进阶追问:框架抽象越多是否越安全?
  • 进阶回答:不是;只有威胁模型明确、边界最小、配置可验证且领域约束完整时,抽象才真正降低风险。
  • 口述答案:我认为它体现了关注点分离、责任链、策略模式、适配器和默认拒绝。Web(万维网)安全不是一个判断,而是请求防火墙、上下文加载、凭证提取、认证、会话、异常转换、授权、响应头和清理等多步协议;Filter Chain(过滤器链)让每一步职责单一、顺序可组合,并允许不同请求选择不同协议。DelegatingFilterProxy(委派过滤器代理)把 Servlet(服务器小程序)生命周期适配到 Spring(Java 应用框架)容器,FilterChainProxy(过滤器链代理)统一选择、执行和清理。AuthenticationManager(认证管理器)与 AuthenticationProvider(认证提供者)把统一入口和具体凭证算法分开,AuthorizationManager(授权管理器)把身份、资源和动作决策抽象出来,SecurityContext(安全上下文)则为一次执行链提供最小身份快照。这些抽象让密码、会话、JWT(JSON 网络令牌)、方法权限和 OAuth 2.0(开放授权协议)可以替换组合,但代价是控制流隐式、顺序敏感和上下文容易泄漏。工程原则是第一,链路默认拒绝且匹配范围明确;第二,认证、授权和领域不变量分层,入口安全不替代对象归属与数据库约束;第三,身份和令牌最小化、短寿命、可撤销、可轮换;第四,任何异步切换都有捕获、设置、恢复和清理协议;第五,关键决策可观测但不泄露秘密;第六,版本升级以运行链和契约测试为证据。更高层看,安全不是一次登录功能,而是围绕信任边界、最小权限、失败关闭、纵深防御和可审计恢复建立的持续系统。
  • 追问 1:过滤器越多是否越安全?
  • 回答 1:不是,重复或顺序错误会产生旁路和复杂度;安全来自明确威胁模型、最小职责和可验证组合。
  • 追问 2:默认拒绝怎样避免阻碍开发?
  • 回答 2:通过显式公开端点清单、开发环境契约和快速诊断,而不是用宽范围放行掩盖配置问题。
  • 追问 3:安全框架能保证业务绝对安全吗?
  • 回答 3:不能,它提供入口和方法机制;业务授权、幂等、状态机、数据约束、密钥运维和事故响应仍由系统设计共同完成。
  • 详情:Spring Security(安全框架)安全模型

13. 线上排查、项目话术与复习清单

线上排查 SOP(标准操作流程):先分类 401/403/对象越权/上下文泄漏,随后保存链标识、规则版本、密钥标识、主体摘要、租户和业务键;再按“命中链 -> 上下文加载 -> 认证提供者 -> 令牌/会话 -> 授权决策 -> 对象归属 -> 数据约束 -> 清理”逐层取证。止血优先回滚具体规则、恢复旧公钥验证窗口、暂停高风险写或降级只读,禁止全局放行。

项目口述模板:在 WMS(仓储管理系统)里,我把认证、方法权限、仓库/货主对象权限、审批和库存条件更新分层;在支付回调里,我把渠道验签、防重放、事件幂等、支付状态机、主动查单和对账分层。两者都使用审计、最小权限和失败关闭,但不会把渠道签名当用户登录,也不会把权限通过当资金或库存事实已经正确。

复习清单

  • 能画出 DelegatingFilterProxy(委派过滤器代理)到业务控制器的完整链路,并解释首链匹配。
  • 能区分 Authentication(认证)、Authorization(授权)、领域对象权限和数据库约束。
  • 能解释 401/403、会话/JWT(JSON 网络令牌)、签名/加密、无会话/无服务端状态的差异。
  • 能演绎密码升级、刷新令牌轮换、密钥双窗口、线程复用串号和多租户越权。
  • 能说明 CSRF(跨站请求伪造)、CORS(跨源资源共享)、OAuth 2.0(开放授权协议)和 OIDC(开放身份连接协议)的边界。
  • 能用支付回调与 WMS(仓储管理系统)库存调整讲清安全、幂等、状态机和审计的协作。