Skip to content

模块导航: 总入口 · 总纲与方法 · 后端场景 · AI与算法工程 · 国企与金融 · 网络问答


第八章 微服务·安全·工程化(第185-200题) ​

本章导览(元问题与表面主题逐字取自 《核心考点与元理论》§六)

  • 本章真正考的那一个问题: 边界如何划,变更如何不炸全量,信任如何不外泄?
  • 表面主题: 拆分、注入、JWT、灰度
  • 题号与题量: 第 185–200 题,共 16 题;刷题时不要按知识点记,要按上面那一个元问题记。

185. 单体应用 50 万行代码,要怎么拆微服务(微服务拆分) ​

【考察内容】服务拆分是微服务第一问

【题目】一个 50 万行代码的单体应用,团队 30 人开发效率越来越低,决定拆微服务。按什么原则拆(业务域/团队/数据)?哪些该拆哪些不该拆?拆错会有什么后果?分几步走?

【参考答案】

  1. 拆分维度:

    • 按业务域拆分(DDD 限界上下文):订单、库存、支付、用户各自成服务——按“业务能力边界”拆,是最主流的依据;
    • 按组织架构(康威定律):团队负责的服务边界与组织对齐;
    • 按变化频率/稳定性:频繁变化与稳定模块分离;
    • 按性能/资源需求:热点模块独立(秒杀服务独立部署);
  2. 拆分原则:

    • 高内聚低耦合:一个服务内的数据与逻辑内聚,服务间只通过 API/事件交互;
    • 数据独立:每个服务拥有自己的数据库(数据不共享是微服务关键特征,也是事务拆分的代价来源);
    • 避免“分布式单体”:拆分后仍强同步调用+共享表=伪微服务;
  3. 拆分决策:

    • 先单体后拆分(过早拆分成本高):模块化单体 → 按痛点逐步拆(团队规模/部署频率/性能瓶颈驱动);
    • 评估拆分代价:分布式事务、调用链、运维复杂度 vs 独立部署/弹性/技术异构收益;
  4. 常见误区:按“技术层”拆(DAO 服务/工具服务,无业务边界);拆太细(服务间大量调用,延迟与事务灾难)。

  5. 容量估算: 拆分不是按代码行数,按领域与变更频率。经验:单体 50 万行往往对应 3–8 个限界上下文较合适;服务数超过团队两个披萨(约 6–10 人/服务域)会运维失控。调用链预算:一次用户请求串行微服务建议 ≤3–5 跳,否则 P99 累积爆炸。数据:先逻辑拆,库后拆,避免一开始分布式事务满天飞。

  6. 失败与降级: 拆分过程中必须有防腐层与双写/回放验证;新服务挂了要能兜底回老逻辑(开关切换)。每个新依赖引入超时、熔断、重试预算。禁止 Big Bang 一次拆完。

【原理溯源】

  • 为什么按业务域而不是技术层拆? 技术层拆分会把“完成一个业务动作”横切到多个服务(Controller 层/Service 层/DAO 层各成服务),一次下单要跨 3 次网络调用,延迟与故障面同时放大,且没有服务能独立理解业务规则。业务域拆分让“订单规则”完整落在订单服务内,变更局部化——改订单只发订单服务,这是微服务提升研发效率的根本机制。
  • 康威定律为什么成立? 系统架构会映射沟通结构:两个模块若由同一团队维护,边界会自然内聚;若跨团队,接口协商成本会倒逼耦合。所以拆服务时必须同步调整组织,否则会出现“服务边界对了、人还在跨边界改代码”的假微服务。
  • 为什么“数据独立”是微服务的分水岭? 共享数据库意味着任何服务都能绕过 API 改别人的表,耦合在数据层重新粘合。数据独立强制所有交互走 API/事件,才有真正的部署独立与故障隔离。代价是跨服务事务无法再用本地 ACID,必须接受最终一致(TCC/Saga/本地消息表)——这是拆分的主要成本,必须事先认账。
  • 为什么“分布式单体”比单体更糟? 它同时承担两边的代价:既有网络延迟、超时重试、分布式事务的复杂度,又因强同步依赖导致无法独立部署——改一个服务仍要全链路联调发布。判断标准:若服务间调用链路深度 >5 且强同步占比过高,就是分布式单体。
  • 为什么“先单体按痛点再拆”更务实? 业务早期领域边界尚未稳定,过早拆分会反复重构边界。模块化单体(模块间只通过接口交互、禁止跨模块直连表)是“可拆未拆”的中间态:先用代码边界练习解耦,等某个模块的变更频率/性能需求真正成为瓶颈时再物理拆出——拆分是演进动作,不是一次性设计。

【选型判断树】

text
要不要拆、按什么拆?
1. 当前痛点是什么?
   ├─ 发布互相阻塞/冲突多 → 按变更频率拆(热模块独立)
   ├─ 某模块流量/资源特异 → 按性能拆(秒杀独立部署)
   ├─ 团队 >15 人且互相等待 → 按业务域+组织拆(康威)
   └─ 只是"代码乱" → 先模块化单体,不急着物理拆
2. 这个模块该不该独立成服务?
   ├─ 数据与逻辑内聚,对外接口清晰 → 拆
   ├─ 与其他模块共享表/强事务依赖 → 先理清边界,或保留同库
   └─ 变更频率低且无独立伸缩需求 → 不拆
3. 拆了之后事务怎么走?
   ├─ 强一致必需 → TCC / 本地事务+对账
   └─ 可最终一致 → 事件驱动 / 本地消息表

判断口诀: 先问痛点再定边界;数据不共享才是真微服务;先认事务代价再动手。

【口述骨架】(5 分钟;框架 A,骨架共性见文首第二节,本题只写特定内容)

时间任务内容
0:00–0:30定性“拆微服务的核心是按业务域划边界,并认下事务与运维的代价”
0:30–1:30拆分原则业务域/限界上下文为主;康威定律对齐组织;数据独立是分水岭
1:30–2:30不该拆的情况强事务、团队小、边界未稳——先模块化单体
2:30–3:30代价与后果分布式事务、调用链、运维复杂度;拆太细=分布式单体
3:30–4:30落地路径模块化单体 → 按痛点逐步拆 → 每步验证独立部署收益
4:30–5:00收尾“一句话:拆的是业务边界和数据所有权,不是代码文件夹”

【关键数字】

参数经验值说明
触发拆分的团队规模约 15–20 人以上单团队两披萨法则之上,协作成本陡增
健康服务间调用深度≤3–5 层超过 5 层强同步要警惕分布式单体
单服务代码量参考数万~十几万行的内聚域过大说明边界漏了,过小说明拆碎了(50 万行÷3~8 个上下文=约 6~17 万行/服务,按 3 域拆就落不进「数万行」)
数据独立原则一服务一库(或逻辑隔离)共享表=假微服务
演进节奏一次拆 1–2 个边界清晰的服务批量大拆风险高

【追问链】(三层)

L1|“拆完之后跨服务事务怎么办?” → 接受最终一致为主:核心资金/库存用 TCC(Try-Confirm-Compensate);长链路用 Saga 补偿;一般业务用本地消息表/事务消息保证“本地事务+消息”双发。对账系统做兜底。这是拆分的主成本,不能回避。

L2|“按业务域拆了,但查详情要聚合 5 个服务的数据,接口很慢怎么办?” → 三种手段:① 聚合层(BFF/网关)并行调用+超时裁剪;② 读模型冗余(CQRS/事件投影到查询库);③ 关键数据在入口服务缓存一份概要。原则是读路径可以冗余,写路径必须边界清晰。

L3|“已经拆成 30 个微服务,运维扛不住了怎么办?” → 这是拆过头的典型症状。收敛手段:① 合并低价值/强耦合服务(逆向微服务化);② 引入 Service Mesh/统一网关降低治理成本;③ 平台化(统一 CI/CD、日志、监控);④ 重新按业务域梳理,收敛到与团队规模匹配的数量:30 人 ÷(同题“6–10 人/服务域”)≈ 3–5 个域,而 50 万行单体本身通常只对应 3–8 个限界上下文。

【评分标准】

档位答案特征
60 分能说出“按业务拆”;「一个服务一个库」写成「先逻辑隔离、库后拆」同样给分(正文口径)
80 分讲清限界上下文/康威定律、数据独立与事务代价、避免分布式单体,并给出“先单体按痛点再拆”路径
95 分能从变更局部化论证业务域拆分;指出分布式单体比单体更糟的原因;给出资源回收/服务聚合/合并的收敛方案;能讨论何时不该拆

【关联题】

【自测】

  1. 判断对错:微服务拆分应尽量按技术分层(接入层服务、业务层服务、数据层服务)拆。 参考答案:错。按技术层拆会导致一次业务动作横跨多服务,延迟与故障面放大。应按业务域/限界上下文拆。
  2. 为什么说“共享数据库”是伪微服务的标志? 参考答案:共享表允许绕过 API 互相改数据,耦合在数据层粘合,无法独立部署与故障隔离。
  3. 拆微服务最大的隐性成本是什么? 参考答案:跨服务事务从本地 ACID 变为分布式最终一致,需要 TCC/Saga/本地消息表+对账,复杂度显著上升。

186. 公司要自研 RPC 框架,核心组件有哪些(RPC 设计) ​

【考察内容】设计 RPC 是分布式基础高阶题

【题目】公司要自研内部 RPC 框架(对标 Dubbo),从零设计。需要哪些核心组件(通信、序列化、注册发现、负载均衡、容错、代理)?一次完整的 RPC 调用从客户端发起服务端返回,流程是怎样的?

【参考答案】

  1. 核心组件:

    • 通信协议:TCP(Netty)上自定义协议(魔数、版本、序列化方式、请求 ID、数据长度、数据),解决粘包拆包;
    • 序列化:JSON/Protobuf/Hessian,选型看性能与跨语言(Protobuf 性能好、二进制紧凑);
    • 服务注册与发现:服务提供者启动注册(ZK/Nacos),消费者订阅获取实例列表+负载均衡;
    • 负载均衡:随机/轮询/一致性哈希/最少活跃调用;
    • 代理层:客户端动态代理(接口→远程调用),对业务透明;
    • 超时与重试:超时控制、失败重试(幂等接口)、熔断;
    • 连接管理:连接池、心跳保活、断线重连;
  2. 调用流程:客户端调用接口(代理)→ 序列化(接口名+方法+参数)→ 连接池选连接 → 发送 → 服务端反序列化 → 反射调用实现 → 序列化返回 → 客户端反序列化;

  3. 高级能力:泛化调用、异步调用(Future/回调)、优雅停机(摘流量再关连接)、链路透传(TraceID/隐式参数)、限流;

  4. 对比 HTTP:RPC 性能高(长连接+二进制)、服务治理完善;HTTP 跨语言/防火墙友好。生产:内部 Dubbo/gRPC,外部 REST。

  5. 容量估算: RPC 框架核心指标:单连接吞吐、连接数、序列化/反序列化开销、超时精度。经验:内网 RPC 连接超时 200–500ms,读/业务超时按接口 RT 设且必须 ≥ 连接超时+一次服务端处理;切勿把业务超时配得比连接超时还短(那样建连还没成功就被判超时,等于全部失败);连接池与线程模型(Netty IO 线程 vs 业务线程池)要隔离。注册中心 QPS、元数据大小都要预留。压测:慢调用放大场景(一个下游 10% 慢)下,线程池是否被打满。

  6. 失败与降级: 注册中心不可用时本地缓存服务列表;失败重试默认关闭或只对幂等接口开启;熔断半开状态探测。框架自身要做成“可降级”:无治理能力时退回直连。

【原理溯源】

  • 为什么 RPC 要在 TCP 上自定义协议头? HTTP 的文本头冗余大、解析成本高。自定义协议用固定结构(魔数 2B + 版本 + 序列化类型 + 状态 + 请求 ID + 长度 + body)让解码器 O(1) 定位边界。魔数用于快速识别“这是不是我的协议”(防连错端口);请求 ID 让一个连接上多路复用(request-response 配对),否则只能串行等待。
  • TCP 粘包拆包为什么必然存在? TCP 是字节流协议,没有消息边界。发送方两次 write 可能被合并成一次到达(粘包),一次 write 也可能被拆成两次(拆包)。解法只有三种:固定长度、分隔符、长度字段(主流,Netty 的 LengthFieldBasedFrameDecoder)——先读头里的 length,再读满 length 字节形成完整帧。
  • 为什么需要注册发现而不是写死 IP? 写死 IP 在扩缩容、故障转移、多机房场景全部失效。注册中心让提供者上线自动注册、下线自动摘除,消费者通过订阅获得实例列表并本地缓存——发现是控制面,调用是数据面,注册中心宕机时本地缓存仍可继续调用(可用性优先)。
  • 为什么客户端要动态代理? 业务代码应写 orderService.create(order),而不是“序列化参数、选连接、发包、等响应”。动态代理(JDK Proxy/CGLIB)把远程细节藏在接口后面,让调用方像调本地方法。这是 RPC“透明化”的关键——透明的是调用形式,不透明的是失败模式(网络会失败、会超时),所以必须配超时与重试。
  • 为什么负载均衡要放在客户端? 客户端直连提供者,少一跳网关转发,延迟更低、网关不成瓶颈。客户端从注册中心拿到全量实例列表后本地选择(随机/轮询/最少活跃),也便于实现会话粘滞、灰度路由。服务端负载均衡(LVS/Nginx)适合南北向流量,东西向服务调用主流是客户端 LB。

【选型判断树】

text
自研/选型 RPC,先定四个问题:
1. 调用双方语言是否统一?
   ├─ 统一 Java → Dubbo/Hessian(生态成熟)
   └─ 多语言 → gRPC/Protobuf
2. 是否需要治理能力(熔断/限流/灰度)?
   ├─ 需要 → Dubbo/Spring Cloud + 注册中心
   └─ 只要高性能通信 → gRPC 裸用
3. 连接模型?
   ├─ 高并发短调用 → 长连接池 + 多路复用(request ID)
   └─ 低频管理接口 → HTTP/REST 即可
4. 失败模式?
   └─ 必配:超时(连/读分开)→ 重试(仅幂等)→ 熔断

判断口诀: 协议解决边界,注册解决发现,代理解决透明,超时重试解决失败。

【口述骨架】(5 分钟;框架 A,骨架共性见文首第二节,本题只写特定内容)

时间任务内容
0:00–0:30定性“RPC 框架 = 通信 + 序列化 + 注册发现 + 负载均衡 + 容错 + 代理”
0:30–1:30协议与序列化自定义协议头字段含义;长度字段解决粘包;Protobuf vs JSON
1:30–2:30调用全流程代理→序列化→选连接→发送→反射调用→返回,画一遍
2:30–3:30注册发现与 LB提供者注册、消费者订阅、客户端 LB;注册中心宕机靠本地缓存
3:30–4:30容错超时/重试(幂等前提)/熔断/优雅停机
4:30–5:00收尾“内部 Dubbo/gRPC,外部 REST;核心是把远程调用做成可治理的本地体验”

【关键数字】

参数经验值说明
协议头魔数1–2 字节快速识别协议
超时分级连接超时 200–500ms;读超时按接口 RT 设连接超时应显著小于业务超时
重试次数(RPC 幂等接口)1~2 次 + 指数退避(仅幂等)非幂等禁止自动重试
连接池大小每提供者实例 2–8 条长连接多路复用下单连接可承载较高 QPS
心跳间隔15–60s比 TCP keepalive 更可控
熔断错误率阈值50%+ 持续窗口触发具体按业务 SLA 调

【追问链】(三层)

L1|“为什么不用 HTTP 做内部调用?” → 可以用(gRPC 基于 HTTP/2),但传统 HTTP/1.1 文本头冗余、无多路复用、短连接开销大。内部高频调用选长连接+二进制 RPC 吞吐更高、延迟更低。跨语言/跨防火墙/对外 API 用 HTTP 更合适。

L2|“注册中心挂了,RPC 还能调通吗?” → 能。消费者本地缓存实例列表,注册中心宕机时继续用缓存调用(可能包含已下线实例,靠超时+重试其他实例兜底)。这是“控制面与数据面分离”的设计——与第 113 题一致。

L3|“重试会不会把故障放大?” → 会。无节制重试会在下游过载时形成流量放大(每失败一次多一次调用)。正确做法:仅幂等接口重试、重试次数 ≤1、配合熔断(错误率高时快速失败)、重试退避(避免惊群)。

【评分标准】

档位答案特征
60 分能列出通信、序列化、注册发现三件套
80 分讲清协议头/粘包、客户端代理透明、负载均衡与超时重试;能画调用流程
95 分解释 request ID 多路复用、控制面/数据面分离、重试放大与熔断配合;能对比 HTTP/gRPC 适用边界

【关联题】

【自测】

  1. TCP 粘包有哪三种解法?RPC 主流用哪种? 参考答案:固定长度、分隔符、长度字段。主流是长度字段(先读头中 length,再读满 body)。
  2. 为什么 RPC 要在客户端做负载均衡而不是服务端? 参考答案:客户端直连少一跳,延迟更低;便于会话粘滞和灰度路由;网关/服务端 LB 易成瓶颈。
  3. 自动重试的前提条件是什么? 参考答案:接口必须幂等;重试次数有限(通常≤1);最好配合熔断与退避。

187. 内部服务间调用,怎么确认调用方可信(服务间认证) ​

【考察内容】微服务安全体系

【题目】微服务 B 要调用内部服务 C,怎么确认请求真的来自 B 而不是外部攻击者伪造的?方案有哪些(内部网关、mTLS、签名+时效、token)?各自的强度和成本?

【参考答案】

  1. 问题背景:微服务内网也不是绝对可信(横向渗透、SSRF、误调用),需要服务间认证;

  2. 方案:

    • mTLS(双向 TLS):每个服务有证书,调用时双向校验对方证书——最标准(Service Mesh/Istio 内置);成本:证书管理(PKI);
    • 内部 Token/密钥:服务间共享密钥(或每服务一对密钥),调用方签名请求(HMAC,含时间戳+随机数防重放),被调方验签——简单;
    • OAuth2 Client Credentials:服务以客户端身份向认证中心换取 token(JWT),调用时带 token,被调方验签(验 JWT 签名+权限 scope);强度:默认弱于 mTLS——发的是 bearer token,被窃取即可在有效期内原样重放,既无密钥持有证明也无通道绑定(要接近 mTLS 得叠加 DPoP 或 mTLS 绑定);且依赖中心签发,必须短时效+jti 吊销列表+scope 收窄;成本:要自建/接入认证中心并维护签发、续签、轮换,四方案里改造量与运维成本最高;
    • 网关统一鉴权:内部调用统一走网关(或服务网格),在网关层做认证——集中管控;强度:前提是「绕不过去」,只要有服务能被内网直连,鉴权就等于没有,须配网络策略禁直连;成本:网关成为性能与可用性瓶颈(多一跳 RT、需集群与自保限流),且服务间调用也要出门再回来,链路成本不低;
  3. 纵深防御:内网隔离(VPC/命名空间/网络策略)+ 服务间认证 + 最小权限(每个服务只授权需要的接口)+ 审计日志(谁调了谁);

  4. 注意事项:不要把用户的 Token 直接当服务间凭证(用户与服务的信任模型不同);SSRF 防护(外部输入不可用于内部调用地址)。

  5. 容量估算: 内网认证开销应可忽略:mTLS/JWT 校验 p99 目标 <5ms。证书/密钥轮换周期:自建 PKI 常见 90 天内并自动化;服务网格下 mTLS 证书生命周期是小时~天级(见【关键数字】),两个口径别混报。服务数量增长时,密钥分发与吊销列表要能水平扩展;审计日志量按调用量估算(每请求一条审计可能很大,可采样+关键操作全量)。

  6. 失败与降级: 认证中心故障时:只允许「缓存已验签公钥/last-known-good token+短期宽限」,敏感操作一律 fail-closed,不可完全裸奔。不要把降级写成「按网段/内网放行」——那正是本题口诀「零信任不看网段看身份」否掉的前提,认证中心一抖就退回边界信任,横向渗透窗口立刻打开;若确有历史系统只能按网络策略临时放行,必须限时(小时级)、白名单到具体源 IP、全量审计并走应急审批,事后立即回收。紧急运维通道与业务通道隔离。吊销列表拉取失败要明确 fail-closed / fail-open 的作用域:读取与低危接口可用已验签缓存+限时宽限(fail-open 但限窗),资金、改密、管理面一律 fail-closed——不加分级地“一律 fail-closed”会把认证中心抖动放大成全链路不可用。

【原理溯源】

  • 为什么“内网默认可信”是错误假设? 内网一旦有主机被攻破(钓鱼、漏洞、供应链),攻击者可在内网横向移动。若服务间无认证,攻击者可直接伪造“来自订单服务”的调用删除数据。安全模型必须是零信任:每个调用都要证明身份,不因来源 IP 在内网而放行。
  • 为什么用户 Token 不能直接当服务间凭证? 用户 Token 的主体是终端用户,权限是用户权限;服务间调用的主体是“服务身份”,权限是服务权限。混用会导致:① 攻击者拿到用户 Token 后可冒充服务;② 服务被迫持有用户权限,违反最小权限。正确做法是服务以自己的身份(mTLS 证书/JWT Client Credentials)再叠加用户上下文透传(user_id header,由可信网关注入)。
  • mTLS 为什么是服务网格的标准答案? mTLS 在传输层完成双向证书校验,对应用代码零侵入(Sidecar 代理处理),且提供加密+身份双重保障。PKI 成本(证书签发/轮换/吊销)由平台统一承担后,业务只感知“调用通了”。Istio/Linkerd 内置 mTLS 就是这个原因。
  • HMAC 签名为何要带时间戳+nonce? 纯签名防篡改但不防重放——攻击者原样重发合法请求依然验签通过。时间戳限制“只接受近期请求”,nonce 防“时间窗内重放”(与第 192 题同理)。缺任一环,签名方案都有漏洞。
  • 为什么最小权限比认证更重要? 认证解决“你是谁”,授权解决“你能做什么”。若服务 C 对所有已认证服务开放全部接口,攻破任意一个服务就等于攻破 C。按服务分配 scope(B 只能调 C 的查询接口,不能调删除)可把横向移动的爆炸半径压到最小。

【选型判断树】

text
服务间认证怎么选?
1. 是否已有 Service Mesh?
   ├─ 有 → mTLS(Istio 内置)+ AuthorizationPolicy
   └─ 无 → 继续问
2. 团队 PKI 运维能力?
   ├─ 强 → 自建 mTLS / SPIFFE
   └─ 弱 → JWT Client Credentials(统一认证中心)
3. 调用模型?
   ├─ 同步 HTTP/RPC → Authorization header / 证书
   └─ 异步 MQ → 消息签名 / 队列 ACL
4. 永远叠加:内网隔离 + 最小权限 + 审计

判断口诀: 零信任不看网段看身份;传输层 mTLS,业务层 JWT;认证之后必须授权。

【口述骨架】(5 分钟;框架 A,骨架共性见文首第二节,本题只写特定内容)

时间任务内容
0:00–0:30定性“内网不可默认可信,服务间要做身份认证+最小权限”
0:30–1:30四方案mTLS / HMAC 签名 / Client Credentials / 网关统一鉴权,各一句强度与成本
1:30–2:30为何不用用户 Token主体不同、权限模型不同;用户上下文应另行透传
2:30–3:30纵深网络隔离 + 认证 + 最小权限 + 审计
3:30–4:30落地Mesh 优先 mTLS;无 Mesh 用认证中心发 JWT
4:30–5:00收尾“零信任:每次调用证明身份,按服务分配 scope”

【关键数字】

参数经验值说明
JWT 服务间 Token 有效期5–30 分钟短效+自动续签
HMAC 时间戳窗口±1–5 分钟与 192 题重放窗口一致
mTLS 证书轮换自动,生命周期小时~天级Mesh 可自动轮换
授权粒度服务×接口至少到服务级,关键接口到方法级
审计留存不少于 6 个自然月(出处是网安法,不是等保)谁调了谁、何时、结果

【追问链】(三层)

L1|“mTLS 和 JWT 能一起用吗?” → 能,且推荐分层:mTLS 管传输层身份与加密(你是哪个服务实例),JWT 管业务层权限(这个服务能调哪些接口)。Istio 中 mTLS + RequestAuthentication + AuthorizationPolicy 就是这种组合。

L2|“服务间认证会不会增加很多延迟?” → mTLS 握手在长连接上只发生一次,稳态额外延迟可忽略(μs 级加解密)。JWT 验签(本地验签,不查库)也是微秒级。真正成本在证书运维与配置复杂度,不在运行时性能。

L3|“攻击者已经攻破服务 B,还能怎么防?” → B 的身份是真的,认证拦不住。靠:① 最小权限(B 只能调 C 的有限接口);② 审计告警(B 的调用模式异常);③ 网络策略(B 只能访问 C 的特定端口,不能扫内网);④ 运行时防护(异常调用频率限流)。认证是第一道,不是唯一道。

【评分标准】

档位答案特征
60 分知道“内网也要认证”,能说出 Token 或证书
80 分讲清 mTLS/ HMAC/ Client Credentials/网关统一鉴权四方案及取舍(网关方案要点出「绕不过去」前提);知道用户 Token 不能混用
95 分从零信任模型论证;分层(传输层/业务层)组合方案;最小权限+审计纵深;讨论攻破后的残余风险

【关联题】

【自测】

  1. 为什么不能把内网当作可信边界? 参考答案:内网主机被攻破后可横向移动;零信任要求每次调用证明身份,不因 IP 在内网放行。
  2. mTLS 分别提供了哪两重保障? 参考答案:传输加密(防窃听篡改)+ 双向身份认证(防冒充)。
  3. HMAC 服务间签名为什么必须带时间戳和 nonce? 参考答案:纯签名不防重放;时间戳拒历史包,nonce 防时间窗内重放。

188. 几十人并行开发,怎么保证合入质量和发布稳定(CI/CD) ​

【考察内容】研发效能与工程质量体系

【题目】团队 50 人并行开发同一个大仓,经常出现“合并冲突”“改坏了别人代码”“上线即事故”。怎么通过代码评审、CI(静态检查/单测/构建)、CD(灰度/回滚)、分支策略(主干开发/特性分支)来保障质量?

【参考答案】

  1. 代码质量门禁(CI):

    • 静态检查(Checkstyle/SpotBugs/ESLint)+ 单元测试(覆盖率卡点)+ 代码规范(格式化统一);
    • Code Review(必过):合入主分支前必须 review(小 PR、双人复核);
    • 依赖/安全扫描(漏洞依赖检测);
  2. 构建与集成:CI 流水线(GitLab CI/Jenkins)——提交即构建、跑测试、产出制品(镜像),失败阻断合入;

  3. 发布流水线(CD):

    • 环境递进:dev → test(功能/回归)→ staging(预发,连生产配置)→ prod;
    • 灰度发布:按实例/用户比例分批(1%→10%→50%→100%),每批看监控(错误率/RT/业务指标)卡点;
    • 快速回滚:一键回滚到上一版本(镜像 tag 管理);
    • 发布前自动化:健康检查、冒烟测试、配置比对;
  4. 测试体系:单元测试 + 接口测试 + 集成测试 + 全链路压测(大促前)+ 故障演练;

  5. 治理:分支模型(主干开发/特性分支)、发布窗口(非高峰)、变更记录与审计。

  6. 容量估算: CI/CD 容量:并行构建 runner 数≈团队规模/2;单次流水线目标 <10 分钟(核心库可放宽)。测试分层:单元分钟级,集成 <15 分钟,E2E 不挡合并可夜间跑。制品仓库容量与构建缓存命中率影响很大(命中率从 40%→80% 可砍掉约一半时间——前提是「可被缓存命中的环节约占流水线 2/3」,miss 率从 60% 降到 20% 才折得出这个数)。

  7. 失败与降级: 流水线挂了不应阻塞 hotfix:提供紧急发布通道(仍要基本门禁)。生产发布失败自动回滚;金丝雀不达标自动停止。禁止跳过测试长期合并主干。

【原理溯源】

  • 为什么门禁必须左移到合入前? 缺陷发现越晚,修复成本指数上升(编码时 1x,联调 5x,上线后 20x+)。CI 把静态检查、单测放在 PR 阶段,坏代码根本进不了主干,而不是等集成环境才暴露。门禁失败即阻断合入——没有阻断力的检查等于没有检查。
  • 为什么主干开发比长期特性分支更稳? 长期分支与主干漂移数周后合并,冲突面巨大且难 review。主干开发要求小步提交、频繁集成,冲突在“刚写完”时解决成本最低。特性开关(feature flag)替代特性分支:未完成功能开关关闭,代码却每天合入主干——代码持续集成,功能按需开放。
  • 为什么 Code Review 不能只靠工具? 工具能抓规范与已知模式缺陷,抓不住业务语义错误(如“这里该用余额而不是优惠券”)、设计腐化、边界条件遗漏。人工 review 的核心价值是知识传递与设计把关,小 PR(<400 行)让 review 真正有效——大 PR 的 review 往往流于形式。
  • 为什么灰度必须按用户/流量维度而不是只按实例? 按实例灰度(新老实例各半)下,同一用户的请求会随机落到新旧版本,若新版本数据格式不兼容,用户会看到“时好时坏”的灵异现象。按用户染色(同一用户固定版本)保证体验一致性(与第 199 题全链路灰度同理)。
  • 为什么回滚能力比发布速度更重要? 发布一定会出错,能否分钟级回滚决定事故半径。回滚依赖:不可变制品(镜像 tag,不重新构建)、配置与代码分离(回滚代码不回滚配置时要能兼容)、数据变更可逆(加列可回滚代码,删列不可)。先证明能回滚,再谈持续交付。

【选型判断树】

text
质量保障体系怎么搭?
1. 分支模型
   ├─ 追求持续交付 → 主干开发 + feature flag
   └─ 发布节奏固定(双周) → 特性分支 + 集成分支
2. CI 门禁卡什么
   ├─ 必卡:编译 + 单测 + 静态检查 + review
   └─ 按需:覆盖率阈值、安全扫描、契约测试
3. 发布策略
   ├─ 无状态服务 → 按流量/用户比例灰度
   ├─ 有状态/数据变更 → 金丝雀 + DB 先行兼容
   └─ 任何情况 → 预案一键回滚
4. 出事了
   └─ 先回滚止血 → 再排查根因 → 复盘改门禁

判断口诀: 坏代码进不了主干;功能用开关不用长分支;先能回滚再谈发布快。

【口述骨架】(5 分钟;框架 A,骨架共性见文首第二节,本题只写特定内容)

时间任务内容
0:00–0:30定性“质量 = 门禁拦住缺陷 + 灰度控制爆炸半径 + 回滚兜底”
0:30–1:30CI 门禁静态检查/单测/review/安全扫描,失败阻断合入
1:30–2:30分支策略主干开发 vs 特性分支;feature flag 替代长分支
2:30–3:30CD 灰度与回滚环境递进、按用户灰度、一键回滚的前提
3:30–4:30测试体系单测/集成/契约/压测分层
4:30–5:00收尾“工具链是手段,门禁规则和回滚纪律才是灵魂”

【关键数字】

参数经验值说明
PR 大小≤400 行(理想 <200)大 PR 的 review 质量断崖下降
单测覆盖率卡点增量代码 ≥60–80%不要死磕全量 100%
CI 时长目标<10 分钟超过则开发者会绕过
灰度批次1%→10%→50%→100%每批观察 15–60 分钟
回滚时间目标<5–10 分钟演练过才算数
发布窗口非高峰、避开周五晚变更事故率统计规律

【追问链】(三层)

L1|“灰度卡点看什么指标?” → 错误率(对比基线是否突增)、RT P99、核心业务转化(下单成功率、支付成功率)、资源(CPU/内存/连接数)。任一超阈值即暂停扩大,严重则回滚。业务指标往往比技术指标更早暴露问题。

L2|“50 人共仓,合并冲突太多怎么办?” → ① 模块边界清晰(CODEOWNERS)减少物理冲突;② 主干开发+小步提交缩小冲突面;③ 共享代码用接口而非改同一文件;④ 定期重构公共层。冲突是架构耦合的症状,治标(工具)不够,要治本(边界)。

L3|“数据库表结构变更怎么灰度?” → 遵循“加列可共存、删列要两步”:先加新列(可空/默认值)→ 发布读写兼容的代码(双写或读旧写新)→ 历史数据回填 → 观察 → 下个版本再删旧列。禁止“上线即删列”。工具:gh-ost/pt-online-schema-change 在线改表。

【评分标准】

档位答案特征
60 分能说出“要 CI、要 review、要灰度”
80 分讲清门禁规则、主干开发+feature flag、灰度与回滚配合;CI 失败阻断
95 分论证缺陷左移成本曲线;数据变更的兼容发布;回滚的前提条件;把冲突当架构问题治理

【关联题】

【自测】

  1. 为什么 Code Review 必须在合入主干前而不是合并后? 参考答案:坏代码进主干会污染集成环境并影响他人;缺陷越晚发现修复成本越高;门禁失败应阻断合入。
  2. 主干开发如何处理未完成的功能? 参考答案:用 feature flag(特性开关)让代码每天合入但功能未开放,避免长期特性分支与主干漂移。
  3. 回滚能力的前提有哪些? 参考答案:不可变制品(镜像 tag)、配置与代码兼容、数据变更可逆(加列可回滚代码,删列要两步)。

189. 登录接口被注入了,后台数据被拖(SQL 注入) ​

【考察内容】SQL 注入是安全第一必考题

【题目】你们登录接口用字符串拼接 SQL,被安全测试发现存在注入漏洞:输入 ' or '1'='1 能绕过登录。怎么从根上修复?为什么预编译/参数化能防注入?还有哪些补充手段?

【参考答案】

  1. 注入原理:用户输入拼接进 SQL,改变了 SQL 语义(如 ' OR '1'='1),攻击者可绕过校验/拖库/删库;

  2. 防护手段(核心):

    • 预编译 + 参数化查询(首选):PreparedStatement/MyBatis #{}(占位符)——SQL 结构与参数分离,参数永远只作为值,无法改变语义;严禁 ${} 拼接(除非白名单校验后的动态表名/排序字段);
    • 输入校验:白名单校验(类型、长度、格式),特殊字符转义(作为辅助,不能替代预编译);
    • 最小权限:数据库账号只给业务所需权限(禁止 DBA 权限给应用),即使注入也降低危害;
    • 框架层:ORM(MyBatis/JPA)默认预编译;WAF 拦截明显攻击特征(辅助);
  3. 检查与治理:安全扫描(SAST/DAST)、代码审查重点搜 ${} 拼接、SQL 审计平台;

  4. 事故场景:用户输入“1; DROP TABLE users--”直接拼接——预编译后该字符串只是无意义的值,不执行。

  5. 容量估算与防护: 注入防护:100% 参数化查询/ORM 绑定;WAF 规则作纵深防御不是根治。账号权限:应用账号最小权限,禁 DROP/GRANT。日志与审计:登录与敏感查询可追溯。被拖库评估:表行数×敏感字段价值。应急顺序:止血(WAF/限流/下线问题入口)与吊销凭证(重置密码、轮换 API Key、Token 全失效)要并行,修代码紧随其后——只「先改密码不挡注入」会被二次拖库,只「先修码」则要等发布周期,已泄露的凭证在窗口期内仍可被拿去撞库与横向访问;追问链 L3 给的是同一顺序(① 止血与吊销并行、② 修漏洞并发布,实操中挡口子和重置凭证同期做),别答成两条互斥的先后。

  6. 失败与降级: 确认注入后:立即限流/下线接口、吊销相关 Token、强制改密;评估数据泄露合规报告义务。短窗口内可网关 SQL 特征拦截(union/sleep 等)应急。修复必须参数化,不能只“过滤关键字”。

【原理溯源】

  • 为什么拼接 SQL 能改变语义? SQL 是解释执行的语言,数据库解析器无法区分“开发者写的 SQL 骨架”和“用户填的数据”——都是一段文本。' OR '1'='1 闭合了前面的字符串字面量并追加永真条件,解析器把它当成 SQL 逻辑的一部分。根因是代码与数据边界消失。

  • 预编译为什么能从根上防注入? 预编译分两步:① 数据库先解析/编译 SQL 结构(占位符 ? 处只有“值位置”语义);② 参数绑定时,驱动以值类型传入(字符串就整段作为字符串字面量,含引号、分号、注释符都不参与解析)。结构在绑定前已固化,参数永远改不了语义——这是“代码与数据分离”的工程实现。

  • 为什么 MyBatis 的 #{} 安全而 ${} 危险? #{} 生成 PreparedStatement 占位符并参数绑定;${} 做字符串替换进 SQL 文本——等价于手工拼接。动态表名、排序字段无法参数化时只能用 ${},但必须白名单校验(只允许预定义枚举),绝不能把用户输入直接放进 ${}。

  • 为什么“过滤特殊字符”不够? 黑名单过滤总能被绕过(编码变体、注释符变体、宽字节、二阶注入)。二阶注入尤其隐蔽:第一处存储时已过滤,取出后再拼接仍会注入。参数化不依赖“输入长什么样”,是结构性防御。

  • 为什么最小权限是关键纵深? 即使应用有注入点,若 DB 账号只有 SELECT 权限,攻击者无法 DROP/UPDATE;若限定库表,拖库范围也受限。防御分层:参数化(根治)→ 输入校验(减噪)→ 最小权限(降损)→ WAF/审计(发现)。

  • 元理论对应: 原理 7「对抗理性与可观测的认识论」(题群见《核心考点与元理论》§7.7)

【选型判断树】

text
发现/防御 SQL 注入:
1. 代码层(根治)
   ├─ 普通查询 → PreparedStatement / MyBatis #{}
   └─ 动态表名/排序 → ${} + 白名单枚举(绝不接用户原串)
2. 输入层(辅助)
   └─ 类型/长度/格式白名单校验
3. 权限层(降损)
   └─ 应用账号非 DBA,限库限表
4. 发现层
   ├─ SAST 扫 ${} 和拼接模式
   ├─ WAF 拦明显 payload
   └─ SQL 审计平台看异常语句

判断口诀: 参数化根治,校验辅助,权限降损,审计发现;禁 ${} 是铁律。

【口述骨架】(5 分钟;框架 D,骨架共性见文首第二节,本题只写特定内容)

时间任务内容
0:00–0:30定性“注入根因是代码与数据边界消失,根治靠预编译参数化”
0:30–1:30原理拼接改变语义;预编译先固化结构,参数只当值
1:30–2:30工程落地#{} vs ${};动态 SQL 白名单
2:30–3:30纵深输入校验、最小权限、WAF、SAST
3:30–4:30案例' or 1=1 与 DROP TABLE 在参数化后为何无效
4:30–5:00收尾“任何拼接用户输入进 SQL 的写法都是漏洞”

【关键数字】

参数经验值说明
MyBatis 安全写法#{}${} 仅限白名单动态标识符
WAF 误报率需调优,不能替代参数化只能拦已知特征
二阶注入存储后再拼接过滤也挡不住,仍要参数化
应用 DB 账号权限按业务最小集禁止 DDL 权限给应用
SAST 关键词${、字符串拼接 SQL、executeQuery(变量)PR 门禁可扫

【追问链】(三层)

L1|“为什么预编译能防注入而转义不能?” → 转义是在文本层尝试“转成无害字符”,依赖编码与上下文正确,存在绕过面。预编译在协议/驱动层把参数作为值类型绑定,数据库解析器根本不把参数内容当 SQL 语法——结构上不可能改变语义。

L2|“排序字段、表名必须动态,怎么办?” → 这类标识符无法参数化。正确做法:白名单映射——前端传 sort=create_time,后端映射到固定列名;拒绝一切不在映射表中的值。绝不能 ORDER BY ${userInput}。

L3|“已经发生拖库,怎么应急?” → ① 止血与吊销并行:WAF/限流/下线问题入口挡住继续拖库,同期重置凭证(用户密码加盐重哈希、API Key 轮换、Token 全失效)——只挡不吊销,已泄露凭证在修码发布周期内还能用;只吊销不挡,会被二次拖库;② 修漏洞(参数化)并发布;③ 排查影响范围(审计日志看异常查询);④ 评估泄露数据,按个保法履行告知义务;⑤ 复盘把参数化纳入 PR 门禁扫描。

【评分标准】

档位答案特征
60 分知道“不要拼接 SQL,用参数化”
80 分讲清预编译两步机制、#{} vs ${}、输入校验与最小权限
95 分从代码/数据边界论证;二阶注入;动态标识符白名单;应急处置闭环

【关联题】

【自测】

  1. MyBatis 中 #{} 和 ${} 的本质区别是什么? 参考答案:#{} 是 PreparedStatement 参数绑定,参数只当值;${} 是字符串拼接,会改变 SQL 语义。
  2. 过滤用户输入中的单引号,能防住注入吗? 参考答案:不能完全防住。存在编码绕过、二阶注入等。根治必须预编译参数化。
  3. 应用连接数据库的账号应该给什么权限? 参考答案:按业务最小集(如仅 SELECT/INSERT/UPDATE 指定表),禁止 DDL 和跨库权限。

190. 用户评论里藏了脚本,其他用户一打开就中招(XSS) ​

【考察内容】XSS 是 Web 安全必考题

【题目】有用户在其他人的页面上传了带 <script> 的评论,其他用户一打开页面就弹窗/被窃取 Cookie。这是存储型 XSS。怎么防护(输入过滤、输出转义、CSP、HttpOnly)?各自在什么层面做?

【参考答案】

  1. XSS 原理:用户输入被当作 HTML/JS 执行(存储型:存到服务端,其他用户访问时执行;反射型:URL 参数反射到页面;DOM 型:前端 DOM 操作引入);

  2. 防护:

    • 输出转义(核心):渲染时对用户内容做 HTML 转义(<→&lt;),富文本用白名单过滤器(只允许安全标签,如 b/img,过滤 script/onerror);
    • CSP(内容安全策略):HTTP 头限制脚本来源(script-src 'self'),即使注入也不执行;
    • HttpOnly Cookie:Cookie 带 HttpOnly,JS 读不到(防窃取登录态);
    • 输入侧校验(辅助):限制长度/内容类型,但不能只靠输入过滤(绕过多);
    • 前端框架(React/Vue)默认转义;危险 API(innerHTML/v-html)禁用或消毒;
  3. 检测:安全扫描(XSS payload 自动化测试)、WAF、CSP 上报监控;

  4. 分级:存储型危害最大(影响所有访问者),必须重点防护。

  5. 容量估算: XSS 防护分层:输出编码(上下文相关:HTML/JS/URL/Attr)是根治;CSP 头可显著降低爆炸半径。富文本必须白名单过滤(如 DOMPurify),黑名单必被绕过。监控:CSP report-uri 上报量作为攻击面指标。

  6. 失败与降级: 发现存储型 XSS:下线内容、清 CDN/缓存、刷新受影响用户会话;临时加强 CSP default-src 'self'。事后对 UGC 做二次扫描。

【原理溯源】

  • 为什么 XSS 的本质是“信任边界混淆”? 浏览器无法区分“开发者写的 HTML/JS”和“用户提交的文本”——都从服务端作为 HTML 返回。当用户输入未经转义进入 HTML 上下文,浏览器就把它当代码执行。根因与 SQL 注入同构:代码与数据边界消失,只是发生在浏览器侧。

  • 为什么输出转义是核心而不是输入过滤? 同一段用户输入可能进入不同上下文(HTML 体、属性、JS、URL),各上下文转义规则不同。输入侧无法预知将来用在哪;输出侧可以按上下文正确转义。输入过滤的黑名单总可绕过(事件处理器 onerror、javascript: 伪协议、SVG 等)。

  • 富文本为什么必须白名单而不是黑名单? 黑名单(删 script)总漏(<img src=x onerror=alert(1)> 没有 script 标签仍可执行)。白名单只允许预定义安全标签和属性(<b><i><a href> 且 href 限 http/https),其余一律剥离——默认拒绝比默认放行安全得多。

  • CSP 为什么是有效纵深? 即使某处输出转义被绕过,CSP 可以禁止内联脚本执行、限制脚本只能来自本站——注入的 payload 根本跑不起来。代价是要能去掉内联脚本(用 nonce/hash),改造有成本。

  • HttpOnly 为什么能降低损失? XSS 能执行 JS 后,首要目标是 document.cookie 窃取会话。HttpOnly 让 JS 读不到 Cookie,会话劫持难度大增。但 HttpOnly 拦不住“以用户身份发请求”(CSRF 面),所以要配合 SameSite 与 CSRF Token(见 191 题)。

  • 元理论对应: 原理 7「对抗理性与可观测的认识论」(题群见《核心考点与元理论》§7.7)

【选型判断树】

text
XSS 防护分层决策:
1. 输出上下文是什么?
   ├─ 纯文本 → HTML 实体转义(核心)
   ├─ 富文本 → 白名单过滤器(bleach/sanitize)
   ├─ HTML 属性 → 属性转义
   └─ JS/URL → 禁止用户数据进入,或 JSON 序列化
2. 框架层
   ├─ React/Vue 默认转义 → 禁用 dangerouslySetInnerHTML / v-html
   └─ 必须用 HTML → 先消毒再渲染
3. 纵深
   ├─ CSP:禁内联脚本 / script-src 'self'
   ├─ HttpOnly + Secure + SameSite Cookie
   └─ WAF + CSP 违规上报

判断口诀: 输出转义是核心,富文本用白名单,CSP/HttpOnly 做纵深;输入过滤只是辅助。

【口述骨架】(5 分钟;框架 D,骨架共性见文首第二节,本题只写特定内容)

时间任务内容
0:00–0:30定性“XSS 是用户输入被浏览器当代码执行,存储型危害最大”
0:30–1:30三型存储/反射/DOM,一句各是什么
1:30–2:30核心防护输出转义 + 富文本白名单;为何输入过滤不够
2:30–3:30纵深CSP、HttpOnly、框架默认行为
3:30–4:30危险点innerHTML/ v-html/ dangerouslySetInnerHTML
4:30–5:00收尾“按输出上下文转义,默认拒绝”

【关键数字】

参数经验值说明
富文本白名单标签≤10 个常见安全标签b/i/u/a/img/br/p 等
CSP 推荐script-src 'self' 'nonce-...'禁 inline,用 nonce
Cookie 属性HttpOnly; Secure; SameSite=Lax/Strict三件套缺一有损
存储型 XSS危害等级最高所有访客都中招
React 安全默认默认转义危险 API 需显式调用

【追问链】(三层)

L1|“React/Vue 还会 XSS 吗?” → 会。框架默认转义插值,但 dangerouslySetInnerHTML、v-html、innerHTML、href="javascript:"、new Function、不安全的第三方组件都会绕过默认保护。使用这些 API 时必须先消毒。

L2|“DOM 型 XSS 和存储型有什么不同?” → 存储型:payload 存在服务端,响应 HTML 里已包含,服务端转义可防。DOM 型:数据在前端从 URL/storage 读出后用危险 API 插入 DOM,服务端转义无效——必须在前端源头做校验/转义,并避免把不可信数据传给执行点。

L3|“CSP 部署后业务大量白屏怎么办?” → 先用 Report-Only 模式收集违规(不阻断),根据报告补 nonce/hash 或把内联脚本外置,逐步收紧。常见阻力是第三方统计/广告脚本——可单独放行域或改异步加载。安全改造要可观测地推进,不能一刀切。

【评分标准】

档位答案特征
60 分知道“过滤 script 标签、转义 HTML”
80 分讲清输出转义为核心、富文本白名单、CSP/HttpOnly 纵深
95 分按输出上下文讨论转义差异;DOM XSS 前端防护;框架危险 API;CSP 渐进部署

【关联题】

【自测】

  1. 存储型 XSS 和反射型 XSS 的主要区别是什么? 参考答案:存储型 payload 落库,所有访问者中招;反射型在 URL/请求中即时反射,需诱骗点击。存储型危害更大。
  2. 为什么富文本不能用黑名单过滤? 参考答案:黑名单总被绕过(onerror、javascript: 协议等)。必须白名单:只允许预定义安全标签属性,其余剥离。
  3. HttpOnly 能防住什么、防不住什么? 参考答案:防 JS 窃取 Cookie(会话劫持);防不住 XSS 以用户身份发请求,也防不住 CSRF,需配合其他措施。

191. 用户登录着银行网站,点了个链接钱被转走(CSRF) ​

【考察内容】CSRF 是 Web 安全三件套必考题

【题目】用户登录着银行系统(Cookie 未过期),在恶意网站上点了个按钮,浏览器自动带上银行的 Cookie 发起了转账请求——钱被转走了。CSRF 攻击的原理是什么?怎么防御(Token 校验、SameSite、Referer 校验)?

【参考答案】

  1. 原理:攻击者诱导已登录用户的浏览器,自动携带 Cookie 发起跨站请求(img/form 自动提交),服务端只验证 Cookie 无法区分是否用户本意;

  2. 防护:

    • CSRF Token(核心):页面渲染时服务端下发随机 Token(存 session),提交请求必须携带,服务端比对——攻击者无法读取 Token(同源策略);
    • SameSite Cookie:Cookie 设 SameSite=Lax/Strict——跨站请求不携带 Cookie(现代浏览器默认 Lax,效果明显);
    • 校验 Referer/Origin:服务端校验请求来源域名(有绕过场景,作辅助);
    • 二次验证:敏感操作(转账/改密)要求输入密码/验证码——最可靠兜底;
    • 自定义 Header:要求请求带自定义头(如 X-Requested-With),跨站表单无法添加(辅助);
  3. 与 XSS 关系:XSS 可窃取 CSRF Token——两者配合防护(HttpOnly、CSP);

  4. 现代实践:SameSite=Lax + 敏感操作二次验证为主,CSRF Token 用于高风险页面。

  5. 容量估算: CSRF 防护:所有状态变更接口必须二次校验(SameSite Cookie + CSRF Token 或自定义 Header)。金融/资金接口建议再加敏感操作二次认证。Token 有效期短,绑定会话。压测不影响 Token 校验逻辑正确性。

  6. 失败与降级: 事故时吊销会话、暂停可疑转账通道、保留审计。无法立刻改客户端时,先在网关对无 Header 的写请求拦截/验证码。

【原理溯源】

  • 为什么 CSRF 是“身份被借用”而不是“身份被窃取”? 攻击者不知道用户的 Cookie,也无法读取响应。他只是构造了一个跨站请求,利用浏览器会自动附带 Cookie 的行为让服务端误以为是用户本意。同源策略阻止的是“读”,阻止不了“写”(发请求)——这是 CSRF 的根本可乘之机。

  • CSRF Token 为什么攻击者拿不到? Token 随页面 HTML 返回,存在表单/JS 中。攻击者的恶意站点在另一个源,同源策略禁止它读银行页面的 DOM,因此无法获取 Token。提交时 Token 必须出现在请求体或自定义头里(不能放 Cookie——放 Cookie 就会自动带上,失去意义)。

  • SameSite 为什么有效? SameSite 是 Cookie 属性,告诉浏览器“这个 Cookie 只在同站请求时发送”。Lax 下,跨站的 POST 表单、img、fetch 都不会带 Cookie;顶级 GET 导航仍会带(兼容链接跳转)。现代浏览器默认 Lax,已经挡掉大部分 CSRF。Strict 最严但影响从外站点入已登录站的体验。

  • 为什么 Referer 校验只能当辅助? Referer 可能被隐私设置剥离,也可能被某些跳转链伪造/丢失。校验 Origin(现代浏览器更可靠)稍好,但仍不适用于所有客户端。它们适合做低成本粗筛,不能当主防线。

  • 为什么敏感操作要二次验证? 二次验证(重新输密码/短信/人脸)引入了攻击者无法自动完成的步骤,把“浏览器自动带凭证”的攻击面直接打断。这是资金操作的最后一道闸,即使前面全被绕过也拦得住。

  • 元理论对应: 原理 7「对抗理性与可观测的认识论」(题群见《核心考点与元理论》§7.7)

【选型判断树】

text
CSRF 防御选型:
1. Cookie 属性(第一道)
   └─ SameSite=Lax(默认);极高危接口用 Strict
2. 请求校验(主防线)
   ├─ 表单应用 → 同步 CSRF Token
   └─ SPA/API → 自定义 Header(X-Requested-With)或双提交 Cookie
3. 来源校验(辅助)
   └─ 校验 Origin/Referer 白名单
4. 资金/改密等高危操作
   └─ 二次验证(密码/OTP)——最可靠兜底
5. 有 XSS 则 Token 会失效 → 先修 XSS(190 题)

判断口诀: SameSite 打底,Token/自定义头做主防,高危操作上二次验证;有 XSS 则全盘皆输。

【口述骨架】(5 分钟;框架 D,骨架共性见文首第二节,本题只写特定内容)

时间任务内容
0:00–0:30定性“CSRF 是借用户之名发请求,不是偷 Cookie”
0:30–1:30原理浏览器自动带 Cookie + 同源策略只防读不防写
1:30–2:30核心防御CSRF Token 机制;为何 Token 不能放 Cookie
2:30–3:30SameSiteLax/Strict 行为;现代浏览器默认
3:30–4:30高危兜底二次验证;与 XSS 的关系
4:30–5:00收尾“SameSite+Token+二次验证三层”

【关键数字】

参数经验值说明
SameSite 默认现代浏览器 Lax已挡大部分跨站 POST
CSRF Token每会话或每表单随机高危可每请求一次
敏感操作转账/改密/绑卡强制二次验证最后一道闸
GET 不做变更规范要求敏感操作必须 POST/PUT
XSS 与 CSRFXSS 可偷 Token必须先修 XSS

【追问链】(三层)

L1|“为什么 Token 不能放在 Cookie 里?” → CSRF 攻击中浏览器会自动带上 Cookie。若 Token 存在 Cookie,恶意请求会自动携带 Token,校验形同虚设。Token 必须放在请求体或自定义 Header,由页面 JS 读取后主动加入——跨站页面的 JS 读不到(同源策略),也加不上自定义头(表单不能设自定义头)。

L2|“纯 API/移动端没有页面,怎么防 CSRF?” → 移动端原生 App 不受浏览器自动带 Cookie 机制影响,CSRF 风险低。SPA 用自定义 Header(X-Requested-With)或双提交 Cookie(Cookie 与 Header 中各一份 Token 比对)。Bearer Token 放 Header 而非 Cookie 的 API 天然免疫 CSRF。

L3|“SameSite=Lax 还有什么场景会漏?” → 顶级 GET 导航仍带 Cookie(如恶意站做 <a href="bank.com/transfer?to=hacker">,若是 GET 变更接口就危险)。所以:① 敏感操作禁止 GET;② 极高危接口用 Strict;③ 资金操作加二次验证。Lax 是强基线,不是万能药。

【评分标准】

档位答案特征
60 分知道“要 Token 验证”
80 分讲清自动带 Cookie 原理、Token 同源保护、SameSite、二次验证
95 分区分“借身份 vs 窃身份”;Token 放置位置的原因;API/SPA 适配;Lax 残余风险

【关联题】

【自测】

  1. CSRF 和 XSS 的本质区别是什么? 参考答案:XSS 是把恶意脚本注入页面以用户身份执行(窃身份);CSRF 是诱导浏览器自动携带凭证发请求(借身份),不注入脚本。
  2. 为什么 CSRF Token 要放在请求体/自定义头而不是 Cookie? 参考答案:Cookie 会被浏览器自动携带,恶意请求会自动带上 Token 导致校验失效。请求体/自定义头跨站页面无法伪造。
  3. SameSite=Lax 下还有什么攻击残留? 参考答案:顶级 GET 导航仍会带 Cookie,因此敏感操作必须用 POST,极高危用 Strict + 二次验证。

192. App 接口被抓包,请求被原样重放(重放攻击) ​

【考察内容】接口安全与反欺诈

【题目】攻击者抓包拿到你 App 的转账请求,原样重放 100 次,钱被转走 100 次。怎么防重放?请求签名怎么设计(参数+时间戳+nonce)?nonce 怎么去重?时间戳窗口怎么设?

【参考答案】

  1. 重放攻击:截获合法请求后原样重发,绕过业务校验(重复下单/转账);

  2. 防护体系:

    • 时间戳校验:请求带客户端时间戳,服务端校验与当前时间差(如 ±5 分钟),超时拒绝——防“历史包”重放;
    • 随机数 nonce:请求带随机数,服务端记录已用 nonce(Redis SET nonce:{v} 1 NX EX 窗口全宽),同 nonce 拒绝——防时间窗内重放;
    • 签名:参数+时间戳+nonce+密钥做 HMAC 签名,服务端验签(防篡改);
    • 业务幂等(最终兜底):即使重放成功,业务幂等键/唯一索引保证只生效一次(转账单号唯一)——最可靠的最后防线;
  3. 组合流程:请求(参数+时间戳+nonce+签名)→ 验时间窗 → 验签名 → 查 nonce 去重 → 业务处理(幂等)→ 返回;

  4. 密钥管理:App 端密钥做混淆/动态下发(客户端密钥不是绝对安全,配合服务端风控:频控、设备绑定、行为校验);

  5. 进阶:防篡改(签名)、防批量(频控)、HTTPS(防中间人窃听——但 HTTPS 不防重放,仍需上述机制)。

  6. 容量估算: 防重放:请求时间戳窗口常见 30s–5min;nonce 缓存容量≈QPS×窗口全宽(±5 分钟的窗全宽是 600s:2000 QPS×600s=120 万条;按 300s 只算了一半,与【关键数字】「TTL=窗口全宽」同一口径,Redis 可轻松承载)。签名算法与密钥轮换。幂等键存储按业务峰值估算。

  7. 失败与降级: 疑似重放攻击:收紧时间窗、强制 nonce、临时限流该接口/设备。资金类接口失败要 fail-closed,不能“验签失败仍放行”。

【原理溯源】

  • 为什么 HTTPS 防不了重放? HTTPS 保证传输过程加密且完整,但攻击者若在客户端侧(恶意代理/已 root 设备/抓包工具)拿到的是解密后的明文请求,原样再发一次在服务端看来完全合法。HTTPS 防的是链路上窃听,防不了“合法请求被复制再用”。

  • 时间戳单独为什么不够? 时间戳只能拒绝“窗口外”的历史包。攻击者在 ±5 分钟内重放同一请求,时间戳校验照样通过。所以时间戳把攻击面从“无限”压到“5 分钟窗口”,但窗口内仍需 nonce 消重。

  • nonce 为什么必须服务端存储? 服务端必须记住“这个 nonce 用过了”。常用 Redis:SET nonce_key 1 NX EX 600(不能写成 SETNX nonce_key 1 EX 300——SETNX 只接受 key 与 value 两个参数,带不了过期时间),返回 OK = 首次,返回 nil = 重放。TTL 取窗口全宽:双向窗 ±5 分钟就是 600s,只设 300s 等于锁了一半——时钟超前的合法请求在自己的 nonce 被淘汰后仍在窗内,可被原样重放(单向窗 [now−N, now] 才取 N)。窗口之外的重复请求由时间戳先拒。存储成本可控:只需存窗口内的活跃 nonce。

  • 为什么签名不能单独防重放? 签名证明“请求未被篡改且来自持密钥方”,但重放的正是未被篡改的合法请求——签名依然有效。签名解决防篡改,时间戳+nonce 解决防重放,两者正交,必须组合。

  • 为什么业务幂等是最终防线? 以上都是接口层防御,可能被绕过(密钥泄露、内部调用)。业务层唯一约束(转账单号/幂等键)保证“同一笔业务只扣一次钱”,即使前四层全失守,资金仍安全。安全必须纵深,业务正确性是最后一道。

  • 元理论对应: 原理 7「对抗理性与可观测的认识论」(题群见《核心考点与元理论》§7.7)

【选型判断树】

text
防重放分层:
1. 传输层
   └─ HTTPS(防窃听,不防重放)
2. 接口层(本题核心)
   ├─ 时间戳 ±N 分钟 → 拒历史包
   ├─ nonce + `SET nonce_key 1 NX EX 窗口全宽` → 拒窗口内重复(SETNX 带不了 TTL,键会永不过期)
   ├─ HMAC 签名 → 防篡改
   └─ 组合顺序:时间窗 → 验签 → nonce → 业务
3. 业务层(最终兜底)
   └─ 幂等键/唯一索引
4. 风控层
   └─ 频控、设备指纹、异常行为

判断口诀: HTTPS 防偷看不防重播;时间戳圈窗口,nonce 查重播,签名防篡改,幂等保资金。

【口述骨架】(5 分钟;框架 D,骨架共性见文首第二节,本题只写特定内容)

时间任务内容
0:00–0:30定性“重放是合法请求被复制再用,HTTPS 挡不住”
0:30–1:30三件套时间戳/nonce/签名各自防什么
1:30–2:30处理流程时间窗→验签→nonce→业务幂等
2:30–3:30nonce 存储Redis SET … NX EX,TTL=窗口全宽(±5min→600);SETNX 带不了 TTL
3:30–4:30纵深业务幂等最终防线;频控/设备绑定
4:30–5:00收尾“每层防一种攻击,缺一层漏一类”

【关键数字】

参数经验值说明
时间戳窗口±1–5 分钟按客户端时钟误差权衡
nonce TTL= 时间窗全宽(±5 分钟=10 分钟窗,TTL 取 10 分钟;取 5 分钟会让窗口后半段的请求被重放)单向窗 [now−N, now] 时 TTL=N 即可;窗口外由时间戳先拒
签名算法HMAC-SHA256含参数+时间戳+nonce+密钥
高危接口幂等转账单号/请求唯一键DB 唯一索引兜底
频控单设备/单 IP 阈值防批量重放

【追问链】(三层)

L1|“nonce 存 Redis 怎么存?” → SET nonce:{value} 1 NX EX {ttl}。返回成功=首次使用,放行;返回失败=已用过,拒绝。TTL 取窗口全宽(±5 分钟=600s;单向窗 [now−N, now] 才取 N),与【关键数字】同一口径。注意 nonce 要足够随机(UUID/128bit),防止碰撞。

L2|“客户端时钟不准怎么办?” → ① 窗口放宽到 ±5 分钟容忍常见误差;② 首次请求返回服务器时间,客户端计算偏移量后续校正;③ 高危接口可要求先同步时间。窗口过大会增加重放面,过小会误杀正常用户——按业务调。

L3|“App 密钥被打包进客户端,被抓出来怎么办?” → 客户端密钥无法绝对保密。缓解:代码混淆、白盒密码、动态下发、设备绑定(设备指纹参与签名)、服务端风控(行为/频控/异地)。最终靠业务幂等与风控兜底——客户端签名只能提高攻击成本,不能作为唯一防线。

【评分标准】

档位答案特征
60 分知道“加时间戳和随机数”
80 分讲清三件套分工、处理顺序、Redis nonce 存储、业务幂等兜底
95 分论证 HTTPS 为何不够;窗口与 TTL 的耦合;客户端密钥的局限;纵深设计

【关联题】

【自测】

  1. 为什么上了 HTTPS 还需要防重放? 参考答案:HTTPS 防链路窃听,不防客户端侧抓到的合法请求被原样再发。重放的对象是“已解密的合法请求”。
  2. 时间戳和 nonce 分别防什么? 参考答案:时间戳拒时间窗外的历史包;nonce 拒时间窗内的重复请求。两者互补,缺一不可。
  3. 防重放的最终业务防线是什么? 参考答案:业务幂等(唯一幂等键/唯一索引),保证即使接口层被绕过,同一笔业务只生效一次。

持续学习,持续积累。