第八章 微服务·安全·工程化(第185-200题)
185. 单体应用 50 万行代码,要怎么拆微服务(微服务拆分)
【考察内容】服务拆分是微服务第一问
【题目】一个 50 万行代码的单体应用,团队 30 人开发效率越来越低,决定拆微服务。按什么原则拆(业务域/团队/数据)?哪些该拆哪些不该拆?拆错会有什么后果?分几步走?
【参考答案】
拆分维度:
- 按业务域拆分(DDD 限界上下文):订单、库存、支付、用户各自成服务——按“业务能力边界”拆,是最主流的依据;
- 按组织架构(康威定律):团队负责的服务边界与组织对齐;
- 按变化频率/稳定性:频繁变化与稳定模块分离;
- 按性能/资源需求:热点模块独立(秒杀服务独立部署);
拆分原则:
- 高内聚低耦合:一个服务内的数据与逻辑内聚,服务间只通过 API/事件交互;
- 数据独立:每个服务拥有自己的数据库(数据不共享是微服务关键特征,也是事务拆分的代价来源);
- 避免“分布式单体”:拆分后仍强同步调用+共享表=伪微服务;
拆分决策:
- 先单体后拆分(过早拆分成本高):模块化单体 → 按痛点逐步拆(团队规模/部署频率/性能瓶颈驱动);
- 评估拆分代价:分布式事务、调用链、运维复杂度 vs 独立部署/弹性/技术异构收益;
常见误区:按“技术层”拆(DAO 服务/工具服务,无业务边界);拆太细(服务间大量调用,延迟与事务灾难)。
容量估算: 拆分不是按代码行数,按领域与变更频率。经验:单体 50 万行往往对应 3–8 个限界上下文较合适;服务数超过团队两个披萨(约 6–10 人/服务域)会运维失控。调用链预算:一次用户请求串行微服务建议 ≤3–5 跳,否则 P99 累积爆炸。数据:先逻辑拆,库后拆,避免一开始分布式事务满天飞。
失败与降级: 拆分过程中必须有防腐层与双写/回放验证;新服务挂了要能兜底回老逻辑(开关切换)。每个新依赖引入超时、熔断、重试预算。禁止 Big Bang 一次拆完。
【原理溯源】
- 为什么按业务域而不是技术层拆? 技术层拆分会把“完成一个业务动作”横切到多个服务(Controller 层/Service 层/DAO 层各成服务),一次下单要跨 3 次网络调用,延迟与故障面同时放大,且没有服务能独立理解业务规则。业务域拆分让“订单规则”完整落在订单服务内,变更局部化——改订单只发订单服务,这是微服务提升研发效率的根本机制。
- 康威定律为什么成立? 系统架构会映射沟通结构:两个模块若由同一团队维护,边界会自然内聚;若跨团队,接口协商成本会倒逼耦合。所以拆服务时必须同步调整组织,否则会出现“服务边界对了、人还在跨边界改代码”的假微服务。
- 为什么“数据独立”是微服务的分水岭? 共享数据库意味着任何服务都能绕过 API 改别人的表,耦合在数据层重新粘合。数据独立强制所有交互走 API/事件,才有真正的部署独立与故障隔离。代价是跨服务事务无法再用本地 ACID,必须接受最终一致(TCC/Saga/本地消息表)——这是拆分的主要成本,必须事先认账。
- 为什么“分布式单体”比单体更糟? 它同时承担两边的代价:既有网络延迟、超时重试、分布式事务的复杂度,又因强同步依赖导致无法独立部署——改一个服务仍要全链路联调发布。判断标准:若服务间调用链路深度 >5 且强同步占比过高,就是分布式单体。
- 为什么“先单体按痛点再拆”更务实? 业务早期领域边界尚未稳定,过早拆分会反复重构边界。模块化单体(模块间只通过接口交互、禁止跨模块直连表)是“可拆未拆”的中间态:先用代码边界练习解耦,等某个模块的变更频率/性能需求真正成为瓶颈时再物理拆出——拆分是演进动作,不是一次性设计。
【选型判断树】
要不要拆、按什么拆?
1. 当前痛点是什么?
├─ 发布互相阻塞/冲突多 → 按变更频率拆(热模块独立)
├─ 某模块流量/资源特异 → 按性能拆(秒杀独立部署)
├─ 团队 >15 人且互相等待 → 按业务域+组织拆(康威)
└─ 只是"代码乱" → 先模块化单体,不急着物理拆
2. 这个模块该不该独立成服务?
├─ 数据与逻辑内聚,对外接口清晰 → 拆
├─ 与其他模块共享表/强事务依赖 → 先理清边界,或保留同库
└─ 变更频率低且无独立伸缩需求 → 不拆
3. 拆了之后事务怎么走?
├─ 强一致必需 → TCC / 本地事务+对账
└─ 可最终一致 → 事件驱动 / 本地消息表判断口诀: 先问痛点再定边界;数据不共享才是真微服务;先认事务代价再动手。
【口述骨架】(5 分钟)
| 时间 | 任务 | 内容 |
|---|---|---|
| 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 层强同步要警惕分布式单体 |
| 单服务代码量参考 | 数万行内聚域 | 过大说明边界漏了,过小说明拆碎了 |
| 数据独立原则 | 一服务一库(或逻辑隔离) | 共享表=假微服务 |
| 演进节奏 | 一次拆 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 分 | 能从变更局部化论证业务域拆分;指出分布式单体比单体更糟的原因;给出资源回收/服务聚合/合并的收敛方案;能讨论何时不该拆 |
【关联题】
- 事务代价展开: 第 99 题(分布式事务选型)、第 101 题(TCC)、第 103 题(Saga)
- 服务间通信: 第 186 题(RPC 设计)、第 112 题(注册/配置中心)
- 治理与观测: 第 115 题(链路追踪)、第 138 题(API 网关)
- 数据一致性: 第 109 题(微服务数据一致性)
【自测】
- 判断对错:微服务拆分应尽量按技术分层(接入层服务、业务层服务、数据层服务)拆。 参考答案:错。按技术层拆会导致一次业务动作横跨多服务,延迟与故障面放大。应按业务域/限界上下文拆。
- 为什么说“共享数据库”是伪微服务的标志? 参考答案:共享表允许绕过 API 互相改数据,耦合在数据层粘合,无法独立部署与故障隔离。
- 拆微服务最大的隐性成本是什么? 参考答案:跨服务事务从本地 ACID 变为分布式最终一致,需要 TCC/Saga/本地消息表+对账,复杂度显著上升。
186. 公司要自研 RPC 框架,核心组件有哪些(RPC 设计)
【考察内容】设计 RPC 是分布式基础高阶题
【题目】公司要自研内部 RPC 框架(对标 Dubbo),从零设计。需要哪些核心组件(通信、序列化、注册发现、负载均衡、容错、代理)?一次完整的 RPC 调用从客户端发起服务端返回,流程是怎样的?
【参考答案】
核心组件:
- 通信协议:TCP(Netty)上自定义协议(魔数、版本、序列化方式、请求 ID、数据长度、数据),解决粘包拆包;
- 序列化:JSON/Protobuf/Hessian,选型看性能与跨语言(Protobuf 性能好、二进制紧凑);
- 服务注册与发现:服务提供者启动注册(ZK/Nacos),消费者订阅获取实例列表+负载均衡;
- 负载均衡:随机/轮询/一致性哈希/最少活跃调用;
- 代理层:客户端动态代理(接口→远程调用),对业务透明;
- 超时与重试:超时控制、失败重试(幂等接口)、熔断;
- 连接管理:连接池、心跳保活、断线重连;
调用流程:客户端调用接口(代理)→ 序列化(接口名+方法+参数)→ 连接池选连接 → 发送 → 服务端反序列化 → 反射调用实现 → 序列化返回 → 客户端反序列化;
高级能力:泛化调用、异步调用(Future/回调)、优雅停机(摘流量再关连接)、链路透传(TraceID/隐式参数)、限流;
对比 HTTP:RPC 性能高(长连接+二进制)、服务治理完善;HTTP 跨语言/防火墙友好。生产:内部 Dubbo/gRPC,外部 REST。
容量估算: RPC 框架核心指标:单连接吞吐、连接数、序列化/反序列化开销、超时精度。经验:内网 RPC 连接超时 200–500ms(与本题【关键数字】同口径),读/业务超时按接口 RT 设且必须 ≥ 连接超时+一次服务端处理;切勿把业务超时配得比连接超时还短(那样建连还没成功就被判超时,等于全部失败);连接池与线程模型(Netty IO 线程 vs 业务线程池)要隔离。注册中心 QPS、元数据大小都要预留。压测:慢调用放大场景(一个下游 10% 慢)下,线程池是否被打满。
失败与降级: 注册中心不可用时本地缓存服务列表;失败重试默认关闭或只对幂等接口开启;熔断半开状态探测。框架自身要做成“可降级”:无治理能力时退回直连。
【原理溯源】
- 为什么 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。
【选型判断树】
自研/选型 RPC,先定四个问题:
1. 调用双方语言是否统一?
├─ 统一 Java → Dubbo/Hessian(生态成熟)
└─ 多语言 → gRPC/Protobuf
2. 是否需要治理能力(熔断/限流/灰度)?
├─ 需要 → Dubbo/Spring Cloud + 注册中心
└─ 只要高性能通信 → gRPC 裸用
3. 连接模型?
├─ 高并发短调用 → 长连接池 + 多路复用(request ID)
└─ 低频管理接口 → HTTP/REST 即可
4. 失败模式?
└─ 必配:超时(连/读分开)→ 重试(仅幂等)→ 熔断判断口诀: 协议解决边界,注册解决发现,代理解决透明,超时重试解决失败。
【口述骨架】(5 分钟)
| 时间 | 任务 | 内容 |
|---|---|---|
| 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 设 | 连接超时应显著小于业务超时 |
| 重试次数 | 0–1 次,仅幂等接口 | 非幂等禁止自动重试 |
| 连接池大小 | 每提供者实例 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 适用边界 |
【关联题】
- 注册发现: 第 112 题(注册/配置中心)、第 113 题(注册中心故障)
- 容错: 第 8 题(雪崩防护)、第 199 题(全链路灰度标签路由)
- 链路: 第 115 题(链路追踪)
- 网关对比: 第 138 题(API 网关)
【自测】
- TCP 粘包有哪三种解法?RPC 主流用哪种? 参考答案:固定长度、分隔符、长度字段。主流是长度字段(先读头中 length,再读满 body)。
- 为什么 RPC 要在客户端做负载均衡而不是服务端? 参考答案:客户端直连少一跳,延迟更低;便于会话粘滞和灰度路由;网关/服务端 LB 易成瓶颈。
- 自动重试的前提条件是什么? 参考答案:接口必须幂等;重试次数有限(通常≤1);最好配合熔断与退避。
187. 内部服务间调用,怎么确认调用方可信(服务间认证)
【考察内容】微服务安全体系
【题目】微服务 B 要调用内部服务 C,怎么确认请求真的来自 B 而不是外部攻击者伪造的?方案有哪些(内部网关、mTLS、签名+时效、token)?各自的强度和成本?
【参考答案】
问题背景:微服务内网也不是绝对可信(横向渗透、SSRF、误调用),需要服务间认证;
方案:
- mTLS(双向 TLS):每个服务有证书,调用时双向校验对方证书——最标准(Service Mesh/Istio 内置);成本:证书管理(PKI);
- 内部 Token/密钥:服务间共享密钥(或每服务一对密钥),调用方签名请求(HMAC,含时间戳+随机数防重放),被调方验签——简单;
- OAuth2 Client Credentials:服务以客户端身份向认证中心换取 token(JWT),调用时带 token,被调方验签(验 JWT 签名+权限 scope);强度:与 mTLS 同级但依赖中心签发,token 被盗用期内仍可用,必须短时效+
jti吊销列表+scope 收窄;成本:要自建/接入认证中心并维护签发、续签、轮换,四方案里改造量与运维成本最高; - 网关统一鉴权:内部调用统一走网关(或服务网格),在网关层做认证——集中管控;强度:前提是「绕不过去」,只要有服务能被内网直连,鉴权就等于没有,须配网络策略禁直连;成本:网关成为性能与可用性瓶颈(多一跳 RT、需集群与自保限流),且服务间调用也要出门再回来,链路成本不低;
纵深防御:内网隔离(VPC/命名空间/网络策略)+ 服务间认证 + 最小权限(每个服务只授权需要的接口)+ 审计日志(谁调了谁);
注意事项:不要把用户的 Token 直接当服务间凭证(用户与服务的信任模型不同);SSRF 防护(外部输入不可用于内部调用地址)。
容量估算: 内网认证开销应可忽略:mTLS/JWT 校验 p99 目标 <5ms。证书/密钥轮换周期 90 天内可自动化。服务数量增长时,密钥分发与吊销列表要能水平扩展;审计日志量按调用量估算(每请求一条审计可能很大,可采样+关键操作全量)。
失败与降级: 认证中心故障时:只允许「缓存已验签公钥/last-known-good token+短期宽限」,敏感操作一律 fail-closed,不可完全裸奔。不要把降级写成「按网段/内网放行」——那正是本题口诀「零信任不看网段看身份」否掉的前提,认证中心一抖就退回边界信任,横向渗透窗口立刻打开;若确有历史系统只能按网络策略临时放行,必须限时(小时级)、白名单到具体源 IP、全量审计并走应急审批,事后立即回收。紧急运维通道与业务通道隔离。吊销列表拉取失败要有失败关闭/失败打开策略(金融场景通常 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 的查询接口,不能调删除)可把横向移动的爆炸半径压到最小。
【选型判断树】
服务间认证怎么选?
1. 是否已有 Service Mesh?
├─ 有 → mTLS(Istio 内置)+ AuthorizationPolicy
└─ 无 → 继续问
2. 团队 PKI 运维能力?
├─ 强 → 自建 mTLS / SPIFFE
└─ 弱 → JWT Client Credentials(统一认证中心)
3. 调用模型?
├─ 同步 HTTP/RPC → Authorization header / 证书
└─ 异步 MQ → 消息签名 / 队列 ACL
4. 永远叠加:内网隔离 + 最小权限 + 审计判断口诀: 零信任不看网段看身份;传输层 mTLS,业务层 JWT;认证之后必须授权。
【口述骨架】(5 分钟)
| 时间 | 任务 | 内容 |
|---|---|---|
| 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 分 | 从零信任模型论证;分层(传输层/业务层)组合方案;最小权限+审计纵深;讨论攻破后的残余风险 |
【关联题】
- 重放防护: 第 192 题(重放攻击)
- 网关鉴权: 第 138 题(API 网关)
- 用户认证对比: 第 195 题(JWT 安全)
- 越权: 第 197 题(越权漏洞)
【自测】
- 为什么不能把内网当作可信边界? 参考答案:内网主机被攻破后可横向移动;零信任要求每次调用证明身份,不因 IP 在内网放行。
- mTLS 分别提供了哪两重保障? 参考答案:传输加密(防窃听篡改)+ 双向身份认证(防冒充)。
- HMAC 服务间签名为什么必须带时间戳和 nonce? 参考答案:纯签名不防重放;时间戳拒历史包,nonce 防时间窗内重放。
188. 几十人并行开发,怎么保证合入质量和发布稳定(CI/CD)
【考察内容】研发效能与工程质量体系
【题目】团队 50 人并行开发同一个大仓,经常出现“合并冲突”“改坏了别人代码”“上线即事故”。怎么通过代码评审、CI(静态检查/单测/构建)、CD(灰度/回滚)、分支策略(主干开发/特性分支)来保障质量?
【参考答案】
代码质量门禁(CI):
- 静态检查(Checkstyle/SpotBugs/ESLint)+ 单元测试(覆盖率卡点)+ 代码规范(格式化统一);
- Code Review(必过):合入主分支前必须 review(小 PR、双人复核);
- 依赖/安全扫描(漏洞依赖检测);
构建与集成:CI 流水线(GitLab CI/Jenkins)——提交即构建、跑测试、产出制品(镜像),失败阻断合入;
发布流水线(CD):
- 环境递进:dev → test(功能/回归)→ staging(预发,连生产配置)→ prod;
- 灰度发布:按实例/用户比例分批(10%→50%→100%),每批看监控(错误率/RT/业务指标)卡点;
- 快速回滚:一键回滚到上一版本(镜像 tag 管理);
- 发布前自动化:健康检查、冒烟测试、配置比对;
测试体系:单元测试 + 接口测试 + 集成测试 + 全链路压测(大促前)+ 故障演练;
治理:分支模型(主干开发/特性分支)、发布窗口(非高峰)、变更记录与审计。
容量估算: CI/CD 容量:并行构建 runner 数≈团队规模/2;单次流水线目标 <10 分钟(核心库可放宽)。测试分层:单元分钟级,集成 <15 分钟,E2E 不挡合并可夜间跑。制品仓库容量与构建缓存命中率影响很大(命中率从 40%→80% 可砍一半时间)。
失败与降级: 流水线挂了不应阻塞 hotfix:提供紧急发布通道(仍要基本门禁)。生产发布失败自动回滚;金丝雀不达标自动停止。禁止跳过测试长期合并主干。
【原理溯源】
- 为什么门禁必须左移到合入前? 缺陷发现越晚,修复成本指数上升(编码时 1x,联调 5x,上线后 20x+)。CI 把静态检查、单测放在 PR 阶段,坏代码根本进不了主干,而不是等集成环境才暴露。门禁失败即阻断合入——没有阻断力的检查等于没有检查。
- 为什么主干开发比长期特性分支更稳? 长期分支与主干漂移数周后合并,冲突面巨大且难 review。主干开发要求小步提交、频繁集成,冲突在“刚写完”时解决成本最低。特性开关(feature flag)替代特性分支:未完成功能开关关闭,代码却每天合入主干——代码持续集成,功能按需开放。
- 为什么 Code Review 不能只靠工具? 工具能抓规范与已知模式缺陷,抓不住业务语义错误(如“这里该用余额而不是优惠券”)、设计腐化、边界条件遗漏。人工 review 的核心价值是知识传递与设计把关,小 PR(<400 行)让 review 真正有效——大 PR 的 review 往往流于形式。
- 为什么灰度必须按用户/流量维度而不是只按实例? 按实例灰度(新老实例各半)下,同一用户的请求会随机落到新旧版本,若新版本数据格式不兼容,用户会看到“时好时坏”的灵异现象。按用户染色(同一用户固定版本)保证体验一致性(与第 199 题全链路灰度同理)。
- 为什么回滚能力比发布速度更重要? 发布一定会出错,能否分钟级回滚决定事故半径。回滚依赖:不可变制品(镜像 tag,不重新构建)、配置与代码分离(回滚代码不回滚配置时要能兼容)、数据变更可逆(加列可回滚代码,删列不可)。先证明能回滚,再谈持续交付。
【选型判断树】
质量保障体系怎么搭?
1. 分支模型
├─ 追求持续交付 → 主干开发 + feature flag
└─ 发布节奏固定(双周) → 特性分支 + 集成分支
2. CI 门禁卡什么
├─ 必卡:编译 + 单测 + 静态检查 + review
└─ 按需:覆盖率阈值、安全扫描、契约测试
3. 发布策略
├─ 无状态服务 → 按流量/用户比例灰度
├─ 有状态/数据变更 → 金丝雀 + DB 先行兼容
└─ 任何情况 → 预案一键回滚
4. 出事了
└─ 先回滚止血 → 再排查根因 → 复盘改门禁判断口诀: 坏代码进不了主干;功能用开关不用长分支;先能回滚再谈发布快。
【口述骨架】(5 分钟)
| 时间 | 任务 | 内容 |
|---|---|---|
| 0:00–0:30 | 定性 | “质量 = 门禁拦住缺陷 + 灰度控制爆炸半径 + 回滚兜底” |
| 0:30–1:30 | CI 门禁 | 静态检查/单测/review/安全扫描,失败阻断合入 |
| 1:30–2:30 | 分支策略 | 主干开发 vs 特性分支;feature flag 替代长分支 |
| 2:30–3:30 | CD 灰度与回滚 | 环境递进、按用户灰度、一键回滚的前提 |
| 3:30–4:30 | 测试体系 | 单测/集成/契约/压测分层 |
| 4:30–5:00 | 收尾 | “工具链是手段,门禁规则和回滚纪律才是灵魂” |
【关键数字】
| 参数 | 经验值 | 说明 |
|---|---|---|
| PR 大小 | ≤400 行(理想 <200) | 大 PR 的 review 质量断崖下降 |
| 单测覆盖率卡点 | 增量代码 ≥60–80% | 不要死磕全量 100% |
| CI 时长目标 | <10 分钟 | 超过则开发者会绕过 |
| 灰度批次 | 1%→10%→50%→100%(与正文「10%→50%→100%」的差别是是否带 1% 起步档,两处按同一阶梯表述) | 每批观察 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 分 | 论证缺陷左移成本曲线;数据变更的兼容发布;回滚的前提条件;把冲突当架构问题治理 |
【关联题】
- 全链路灰度: 第 199 题
- 发布事故: 第 174 题(发布事故处置)
- 配置变更: 第 180 题(配置事故)、第 120 题(配置灰度)
- 大促保障: 第 173 题
【自测】
- 为什么 Code Review 必须在合入主干前而不是合并后? 参考答案:坏代码进主干会污染集成环境并影响他人;缺陷越晚发现修复成本越高;门禁失败应阻断合入。
- 主干开发如何处理未完成的功能? 参考答案:用 feature flag(特性开关)让代码每天合入但功能未开放,避免长期特性分支与主干漂移。
- 回滚能力的前提有哪些? 参考答案:不可变制品(镜像 tag)、配置与代码兼容、数据变更可逆(加列可回滚代码,删列要两步)。
189. 登录接口被注入了,后台数据被拖(SQL 注入)
【考察内容】SQL 注入是安全第一必考题
【题目】你们登录接口用字符串拼接 SQL,被安全测试发现存在注入漏洞:输入 ' or '1'='1 能绕过登录。怎么从根上修复?为什么预编译/参数化能防注入?还有哪些补充手段?
【参考答案】
注入原理:用户输入拼接进 SQL,改变了 SQL 语义(如
' OR '1'='1),攻击者可绕过校验/拖库/删库;防护手段(核心):
- 预编译 + 参数化查询(首选):PreparedStatement/MyBatis
#{}(占位符)——SQL 结构与参数分离,参数永远只作为值,无法改变语义;严禁${}拼接(除非白名单校验后的动态表名/排序字段); - 输入校验:白名单校验(类型、长度、格式),特殊字符转义(作为辅助,不能替代预编译);
- 最小权限:数据库账号只给业务所需权限(禁止 DBA 权限给应用),即使注入也降低危害;
- 框架层:ORM(MyBatis/JPA)默认预编译;WAF 拦截明显攻击特征(辅助);
- 预编译 + 参数化查询(首选):PreparedStatement/MyBatis
检查与治理:安全扫描(SAST/DAST)、代码审查重点搜
${}拼接、SQL 审计平台;事故场景:用户输入“1; DROP TABLE users--”直接拼接——预编译后该字符串只是无意义的值,不执行。
容量估算与防护: 注入防护:100% 参数化查询/ORM 绑定;WAF 规则作纵深防御不是根治。账号权限:应用账号最小权限,禁 DROP/GRANT。日志与审计:登录与敏感查询可追溯。被拖库评估:表行数×敏感字段价值。应急顺序:止血(WAF/限流/下线问题入口)与吊销凭证(重置密码、轮换 API Key、Token 全失效)要并行,修代码紧随其后——只「先改密码不挡注入」会被二次拖库,只「先修码」则要等发布周期,已泄露的凭证在窗口期内仍可被拿去撞库与横向访问;追问链 L3 给的是同一顺序(① 修码与 WAF 拦截、③ 重置凭证,实操中这两步同期做),别答成两条互斥的先后。
失败与降级: 确认注入后:立即限流/下线接口、吊销相关 Token、强制改密;评估数据泄露合规报告义务。短窗口内可网关 SQL 特征拦截(union/sleep 等)应急。修复必须参数化,不能只“过滤关键字”。
【原理溯源】
- 为什么拼接 SQL 能改变语义? SQL 是解释执行的语言,数据库解析器无法区分“开发者写的 SQL 骨架”和“用户填的数据”——都是一段文本。
' OR '1'='1闭合了前面的字符串字面量并追加永真条件,解析器把它当成 SQL 逻辑的一部分。根因是代码与数据边界消失。 - 预编译为什么能从根上防注入? 预编译分两步:① 数据库先解析/编译 SQL 结构(占位符
?处只有“值位置”语义);② 参数绑定时,驱动以值类型传入(字符串就整段作为字符串字面量,含引号、分号、注释符都不参与解析)。结构在绑定前已固化,参数永远改不了语义——这是“代码与数据分离”的工程实现。 - 为什么 MyBatis 的
#{}安全而${}危险?#{}生成 PreparedStatement 占位符并参数绑定;${}做字符串替换进 SQL 文本——等价于手工拼接。动态表名、排序字段无法参数化时只能用${},但必须白名单校验(只允许预定义枚举),绝不能把用户输入直接放进${}。 - 为什么“过滤特殊字符”不够? 黑名单过滤总能被绕过(编码变体、注释符变体、宽字节、二阶注入)。二阶注入尤其隐蔽:第一处存储时已过滤,取出后再拼接仍会注入。参数化不依赖“输入长什么样”,是结构性防御。
- 为什么最小权限是关键纵深? 即使应用有注入点,若 DB 账号只有 SELECT 权限,攻击者无法 DROP/UPDATE;若限定库表,拖库范围也受限。防御分层:参数化(根治)→ 输入校验(减噪)→ 最小权限(降损)→ WAF/审计(发现)。
【选型判断树】
发现/防御 SQL 注入:
1. 代码层(根治)
├─ 普通查询 → PreparedStatement / MyBatis #{}
└─ 动态表名/排序 → ${} + 白名单枚举(绝不接用户原串)
2. 输入层(辅助)
└─ 类型/长度/格式白名单校验
3. 权限层(降损)
└─ 应用账号非 DBA,限库限表
4. 发现层
├─ SAST 扫 ${} 和拼接模式
├─ WAF 拦明显 payload
└─ SQL 审计平台看异常语句判断口诀: 参数化根治,校验辅助,权限降损,审计发现;禁 ${} 是铁律。
【口述骨架】(5 分钟)
| 时间 | 任务 | 内容 |
|---|---|---|
| 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 分 | 从代码/数据边界论证;二阶注入;动态标识符白名单;应急处置闭环 |
【关联题】
- Web 安全三件套: 第 190 题(XSS)、第 191 题(CSRF)
- 数据权限: 第 197 题(越权)
- 审计: 第 196 题(日志合规)
【自测】
- MyBatis 中
#{}和${}的本质区别是什么? 参考答案:#{}是 PreparedStatement 参数绑定,参数只当值;${}是字符串拼接,会改变 SQL 语义。 - 过滤用户输入中的单引号,能防住注入吗? 参考答案:不能完全防住。存在编码绕过、二阶注入等。根治必须预编译参数化。
- 应用连接数据库的账号应该给什么权限? 参考答案:按业务最小集(如仅 SELECT/INSERT/UPDATE 指定表),禁止 DDL 和跨库权限。
190. 用户评论里藏了脚本,其他用户一打开就中招(XSS)
【考察内容】XSS 是 Web 安全必考题
【题目】有用户在其他人的页面上传了带 <script> 的评论,其他用户一打开页面就弹窗/被窃取 Cookie。这是存储型 XSS。怎么防护(输入过滤、输出转义、CSP、HttpOnly)?各自在什么层面做?
【参考答案】
XSS 原理:用户输入被当作 HTML/JS 执行(存储型:存到服务端,其他用户访问时执行;反射型:URL 参数反射到页面;DOM 型:前端 DOM 操作引入);
防护:
- 输出转义(核心):渲染时对用户内容做 HTML 转义(
<→<),富文本用白名单过滤器(只允许安全标签,如 b/img,过滤 script/onerror); - CSP(内容安全策略):HTTP 头限制脚本来源(script-src 'self'),即使注入也不执行;
- HttpOnly Cookie:Cookie 带 HttpOnly,JS 读不到(防窃取登录态);
- 输入侧校验(辅助):限制长度/内容类型,但不能只靠输入过滤(绕过多);
- 前端框架(React/Vue)默认转义;危险 API(innerHTML/v-html)禁用或消毒;
- 输出转义(核心):渲染时对用户内容做 HTML 转义(
检测:安全扫描(XSS payload 自动化测试)、WAF、CSP 上报监控;
分级:存储型危害最大(影响所有访问者),必须重点防护。
容量估算: XSS 防护分层:输出编码(上下文相关:HTML/JS/URL/Attr)是根治;CSP 头可显著降低爆炸半径。富文本必须白名单过滤(如 DOMPurify),黑名单必被绕过。监控:CSP report-uri 上报量作为攻击面指标。
失败与降级: 发现存储型 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 题)。
【选型判断树】
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 分钟)
| 时间 | 任务 | 内容 |
|---|---|---|
| 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 渐进部署 |
【关联题】
- 安全三件套: 第 189 题(SQL 注入)、第 191 题(CSRF)
- Cookie 安全: 第 195 题(JWT)、第 191 题(SameSite)
- 合规: 第 196 题
【自测】
- 存储型 XSS 和反射型 XSS 的主要区别是什么? 参考答案:存储型 payload 落库,所有访问者中招;反射型在 URL/请求中即时反射,需诱骗点击。存储型危害更大。
- 为什么富文本不能用黑名单过滤? 参考答案:黑名单总被绕过(onerror、javascript: 协议等)。必须白名单:只允许预定义安全标签属性,其余剥离。
- HttpOnly 能防住什么、防不住什么? 参考答案:防 JS 窃取 Cookie(会话劫持);防不住 XSS 以用户身份发请求,也防不住 CSRF,需配合其他措施。
191. 用户登录着银行网站,点了个链接钱被转走(CSRF)
【考察内容】CSRF 是 Web 安全三件套必考题
【题目】用户登录着银行系统(Cookie 未过期),在恶意网站上点了个按钮,浏览器自动带上银行的 Cookie 发起了转账请求——钱被转走了。CSRF 攻击的原理是什么?怎么防御(Token 校验、SameSite、Referer 校验)?
【参考答案】
原理:攻击者诱导已登录用户的浏览器,自动携带 Cookie 发起跨站请求(img/form 自动提交),服务端只验证 Cookie 无法区分是否用户本意;
防护:
- CSRF Token(核心):页面渲染时服务端下发随机 Token(存 session),提交请求必须携带,服务端比对——攻击者无法读取 Token(同源策略);
- SameSite Cookie:Cookie 设 SameSite=Lax/Strict——跨站请求不携带 Cookie(现代浏览器默认 Lax,效果明显);
- 校验 Referer/Origin:服务端校验请求来源域名(有绕过场景,作辅助);
- 二次验证:敏感操作(转账/改密)要求输入密码/验证码——最可靠兜底;
- 自定义 Header:要求请求带自定义头(如 X-Requested-With),跨站表单无法添加(辅助);
与 XSS 关系:XSS 可窃取 CSRF Token——两者配合防护(HttpOnly、CSP);
现代实践:SameSite=Lax + 敏感操作二次验证为主,CSRF Token 用于高风险页面。
容量估算: CSRF 防护:所有状态变更接口必须二次校验(SameSite Cookie + CSRF Token 或自定义 Header)。金融/资金接口建议再加敏感操作二次认证。Token 有效期短,绑定会话。压测不影响 Token 校验逻辑正确性。
失败与降级: 事故时吊销会话、暂停可疑转账通道、保留审计。无法立刻改客户端时,先在网关对无 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(现代浏览器更可靠)稍好,但仍不适用于所有客户端。它们适合做低成本粗筛,不能当主防线。
- 为什么敏感操作要二次验证? 二次验证(重新输密码/短信/人脸)引入了攻击者无法自动完成的步骤,把“浏览器自动带凭证”的攻击面直接打断。这是资金操作的最后一道闸,即使前面全被绕过也拦得住。
【选型判断树】
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 分钟)
| 时间 | 任务 | 内容 |
|---|---|---|
| 0:00–0:30 | 定性 | “CSRF 是借用户之名发请求,不是偷 Cookie” |
| 0:30–1:30 | 原理 | 浏览器自动带 Cookie + 同源策略只防读不防写 |
| 1:30–2:30 | 核心防御 | CSRF Token 机制;为何 Token 不能放 Cookie |
| 2:30–3:30 | SameSite | Lax/Strict 行为;现代浏览器默认 |
| 3:30–4:30 | 高危兜底 | 二次验证;与 XSS 的关系 |
| 4:30–5:00 | 收尾 | “SameSite+Token+二次验证三层” |
【关键数字】
| 参数 | 经验值 | 说明 |
|---|---|---|
| SameSite 默认 | 现代浏览器 Lax | 已挡大部分跨站 POST |
| CSRF Token | 每会话或每表单随机 | 高危可每请求一次 |
| 敏感操作 | 转账/改密/绑卡强制二次验证 | 最后一道闸 |
| GET 不做变更 | 规范要求 | 敏感操作必须 POST/PUT |
| XSS 与 CSRF | XSS 可偷 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 残余风险 |
【关联题】
- 安全三件套: 第 189 题(SQLi)、第 190 题(XSS)
- 认证: 第 195 题(JWT——若用 Header Token 则天然防 CSRF)
- 网关: 第 138 题
【自测】
- CSRF 和 XSS 的本质区别是什么? 参考答案:XSS 是把恶意脚本注入页面以用户身份执行(窃身份);CSRF 是诱导浏览器自动携带凭证发请求(借身份),不注入脚本。
- 为什么 CSRF Token 要放在请求体/自定义头而不是 Cookie? 参考答案:Cookie 会被浏览器自动携带,恶意请求会自动带上 Token 导致校验失效。请求体/自定义头跨站页面无法伪造。
- SameSite=Lax 下还有什么攻击残留? 参考答案:顶级 GET 导航仍会带 Cookie,因此敏感操作必须用 POST,极高危用 Strict + 二次验证。
192. App 接口被抓包,请求被原样重放(重放攻击)
【考察内容】接口安全与反欺诈
【题目】攻击者抓包拿到你 App 的转账请求,原样重放 100 次,钱被转走 100 次。怎么防重放?请求签名怎么设计(参数+时间戳+nonce)?nonce 怎么去重?时间戳窗口怎么设?
【参考答案】
重放攻击:截获合法请求后原样重发,绕过业务校验(重复下单/转账);
防护体系:
- 时间戳校验:请求带客户端时间戳,服务端校验与当前时间差(如 ±5 分钟),超时拒绝——防“历史包”重放;
- 随机数 nonce:请求带随机数,服务端记录已用 nonce(Redis SETNX+TTL),同 nonce 拒绝——防时间窗内重放;
- 签名:参数+时间戳+nonce+密钥做 HMAC 签名,服务端验签(防篡改);
- 业务幂等(最终兜底):即使重放成功,业务幂等键/唯一索引保证只生效一次(转账单号唯一)——最可靠的最后防线;
组合流程:请求(参数+时间戳+nonce+签名)→ 验时间窗 → 验签名 → 查 nonce 去重 → 业务处理(幂等)→ 返回;
密钥管理:App 端密钥做混淆/动态下发(客户端密钥不是绝对安全,配合服务端风控:频控、设备绑定、行为校验);
进阶:防篡改(签名)、防批量(频控)、HTTPS(防中间人窃听——但 HTTPS 不防重放,仍需上述机制)。
容量估算: 防重放:请求时间戳窗口常见 30s–5min;nonce 缓存容量≈QPS×窗口全宽(±5 分钟的窗全宽是 600s:2000 QPS×600s=120 万条;按 300s 只算了一半,与【关键数字】「TTL=窗口全宽」同一口径,Redis 可轻松承载)。签名算法与密钥轮换。幂等键存储按业务峰值估算。
失败与降级: 疑似重放攻击:收紧时间窗、强制 nonce、临时限流该接口/设备。资金类接口失败要 fail-closed,不能“验签失败仍放行”。
【原理溯源】
- 为什么 HTTPS 防不了重放? HTTPS 保证传输过程加密且完整,但攻击者若在客户端侧(恶意代理/已 root 设备/抓包工具)拿到的是解密后的明文请求,原样再发一次在服务端看来完全合法。HTTPS 防的是链路上窃听,防不了“合法请求被复制再用”。
- 时间戳单独为什么不够? 时间戳只能拒绝“窗口外”的历史包。攻击者在 ±5 分钟内重放同一请求,时间戳校验照样通过。所以时间戳把攻击面从“无限”压到“5 分钟窗口”,但窗口内仍需 nonce 消重。
- nonce 为什么必须服务端存储? 服务端必须记住“这个 nonce 用过了”。常用 Redis:
SET nonce_key 1 NX EX 300(不能写成SETNX nonce_key 1 EX 300——SETNX只接受 key 与 value 两个参数,带不了过期时间),返回 OK = 首次,返回 nil = 重放。TTL 设为时间窗长度(窗口过后旧 nonce 即使重复也先被时间戳拒绝)。存储成本可控:只需存窗口内的活跃 nonce。 - 为什么签名不能单独防重放? 签名证明“请求未被篡改且来自持密钥方”,但重放的正是未被篡改的合法请求——签名依然有效。签名解决防篡改,时间戳+nonce 解决防重放,两者正交,必须组合。
- 为什么业务幂等是最终防线? 以上都是接口层防御,可能被绕过(密钥泄露、内部调用)。业务层唯一约束(转账单号/幂等键)保证“同一笔业务只扣一次钱”,即使前四层全失守,资金仍安全。安全必须纵深,业务正确性是最后一道。
【选型判断树】
防重放分层:
1. 传输层
└─ HTTPS(防窃听,不防重放)
2. 接口层(本题核心)
├─ 时间戳 ±N 分钟 → 拒历史包
├─ nonce + Redis SETNX → 拒窗口内重复
├─ HMAC 签名 → 防篡改
└─ 组合顺序:时间窗 → 验签 → nonce → 业务
3. 业务层(最终兜底)
└─ 幂等键/唯一索引
4. 风控层
└─ 频控、设备指纹、异常行为判断口诀: HTTPS 防偷看不防重播;时间戳圈窗口,nonce 查重播,签名防篡改,幂等保资金。
【口述骨架】(5 分钟)
| 时间 | 任务 | 内容 |
|---|---|---|
| 0:00–0:30 | 定性 | “重放是合法请求被复制再用,HTTPS 挡不住” |
| 0:30–1:30 | 三件套 | 时间戳/nonce/签名各自防什么 |
| 1:30–2:30 | 处理流程 | 时间窗→验签→nonce→业务幂等 |
| 2:30–3:30 | nonce 存储 | Redis SETNX+TTL;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 设为时间戳窗口长度。注意 nonce 要足够随机(UUID/128bit),防止碰撞。
L2|“客户端时钟不准怎么办?” → ① 窗口放宽到 ±5 分钟容忍常见误差;② 首次请求返回服务器时间,客户端计算偏移量后续校正;③ 高危接口可要求先同步时间。窗口过大会增加重放面,过小会误杀正常用户——按业务调。
L3|“App 密钥被打包进客户端,被抓出来怎么办?” → 客户端密钥无法绝对保密。缓解:代码混淆、白盒密码、动态下发、设备绑定(设备指纹参与签名)、服务端风控(行为/频控/异地)。最终靠业务幂等与风控兜底——客户端签名只能提高攻击成本,不能作为唯一防线。
【评分标准】
| 档位 | 答案特征 |
|---|---|
| 60 分 | 知道“加时间戳和随机数” |
| 80 分 | 讲清三件套分工、处理顺序、Redis nonce 存储、业务幂等兜底 |
| 95 分 | 论证 HTTPS 为何不够;窗口与 TTL 的耦合;客户端密钥的局限;纵深设计 |
【关联题】
- 服务间签名: 第 187 题(HMAC 同理)
- 接口幂等: 第 110 题、第 117 题
- 风控: 第 147 题(风控决策)
- 同簇: 第 4 题(防刷风控)
- HTTPS: 第 194 题
【自测】
- 为什么上了 HTTPS 还需要防重放? 参考答案:HTTPS 防链路窃听,不防客户端侧抓到的合法请求被原样再发。重放的对象是“已解密的合法请求”。
- 时间戳和 nonce 分别防什么? 参考答案:时间戳拒时间窗外的历史包;nonce 拒时间窗内的重复请求。两者互补,缺一不可。
- 防重放的最终业务防线是什么? 参考答案:业务幂等(唯一幂等键/唯一索引),保证即使接口层被绕过,同一笔业务只生效一次。
193. 手机号/身份证/支付密码,存储和传输怎么加密(敏感数据加密)
【考察内容】数据安全与合规
【题目】合规要求用户手机号、身份证、支付密码必须加密。存储时用什么算法(哈希/加盐/可逆加密/国密)?不同数据(密码 vs 手机号)加密方式有何不同?传输层(HTTPS、字段级加密)怎么做?
【参考答案】
分类分级:先明确哪些是敏感数据(个人信息、凭证、支付信息),分级管理;
传输加密:全站 HTTPS(TLS 1.2+),防中间人窃听与篡改;
存储加密(按数据性质选算法):
- 密码:不可逆哈希+盐(BCrypt/SCrypt/PBKDF2),禁止 MD5/SHA1 裸存,禁止可逆加密存密码;
- 身份证/手机号等可逆数据:AES-256 对称加密存储(密钥托管 KMS/HSM),配合脱敏展示(135****6789);
- 支付密码:与登录口令同类,属鉴别信息,应不可逆哈希+盐(BCrypt/Argon2,或硬件 PIN 加密机内的等价值保护),不得可逆加密存储;HSM/KMS 只用来保护加密数据用的密钥,不要写成"支付密码加密存储";
脱敏:展示/日志/接口返回时脱敏(手机号、身份证中间打码)——防泄露扩散;
密钥管理:密钥与数据分离(KMS)、定期轮换、密钥不硬编码(配置中心/环境变量加密);
传输层:App 与 Server 间可再加应用层加密(防抓包)——敏感接口二次签名/加密;
合规:日志脱敏(不能打完整手机号)、数据导出审批、加密策略审计。
容量估算: 加密开销:AES-GCM 加解密通常 μs 级,国密 SM4 同数量级;HTTPS 握手在开启会话复用后额外 RTT 可接受。密钥管理:信封加密,DEK 由 KMS 管理;备份与轮换演练。存储:密文略长于明文,分字段加密避免全表加密性能问题。检索需求用密文哈希索引。
失败与降级: KMS 不可用时:核心链路缓存 DEK 有限时间+告警,或降级暂停写敏感数据;禁止明文临时落盘。密钥轮换失败要有回退到上一版本密钥的能力。
【原理溯源】
- 为什么密码必须不可逆哈希而手机号用可逆加密? 密码的用途是“验证你知道它”,系统永远不需要还原明文——不可逆哈希即使库被拖走也无法直接得到原密码。手机号/身份证的用途是“读取与展示”(客服核身、短信下发),必须可还原,所以用可逆对称加密。选算法前先问:业务需不需要还原明文。
- 为什么 MD5/SHA1 不能存密码? 它们设计目标是快速摘要(微秒级),GPU/彩虹表可在短时间内暴力破解百亿组合。BCrypt/SCrypt/PBKDF2 是故意慢的密码哈希(含工作因子/内存硬化),把暴力破解成本抬高几个数量级。加盐(per-user salt)则让预计算彩虹表失效——相同密码不同用户哈希不同。
- 为什么密钥必须与数据分离(KMS/HSM)? 密钥写在代码/配置里,代码泄露=密钥泄露=加密形同虚设。KMS 让密钥在独立系统中生成与使用,应用只拿“用密钥加密的结果”或调用加解密接口;HSM 进一步保证密钥永不出硬件。轮换密钥时只需重新包装 DEK,不必重加密全库(信封加密)。
- 为什么要脱敏展示? 加密解决“存储安全”,脱敏解决“展示面扩散”。客服系统、日志、BI 导出、前端页面若都显示完整手机号,任一环节泄露都是明文泄露。脱敏(135****6789)在“可用”与“最小暴露”间取平衡——客服可核对前后位,但拿不到全号。
- 国密与通用算法如何选? 金融/政务/等保场景可能强制 SM2/SM3/SM4;一般互联网业务 AES-256 + SHA-256/BCrypt 足够。关键是算法选型合规、密钥管理规范,而不是“用了很酷的算法但密钥在 Git 里”。
【选型判断树】
敏感数据怎么保护?
1. 业务需要还原明文吗?
├─ 不需要(密码/支付密码)→ 不可逆哈希+盐(BCrypt/Argon2)
└─ 需要(手机号/身份证/银行卡)→ AES-256 可逆加密 + KMS
2. 密钥放哪?
└─ KMS/HSM,禁止硬编码;信封加密便于轮换
3. 哪里还会露出明文?
├─ 页面展示 → 脱敏
├─ 日志 → 脱敏/不落盘(见 196)
├─ 接口返回 → 按需脱敏
└─ 传输 → HTTPS + 必要时字段级加密
4. 合规场景
└─ 金融/政务评估国密(SM2/3/4)判断口诀: 密码只哈希,身份要加密;密钥进 KMS,展示必脱敏。
【口述骨架】(5 分钟)
| 时间 | 任务 | 内容 |
|---|---|---|
| 0:00–0:30 | 定性 | “按是否需还原明文分两类:密码哈希、身份加密” |
| 0:30–1:30 | 算法选型 | BCrypt 存密码;AES 存可逆字段;禁 MD5 裸存 |
| 1:30–2:30 | 密钥管理 | KMS/HSM、信封加密、轮换、禁硬编码 |
| 2:30–3:30 | 展示与日志脱敏 | 135****6789;多层露出面治理 |
| 3:30–4:30 | 传输 | HTTPS + 敏感接口二次加密 |
| 4:30–5:00 | 收尾 | “算法要对,密钥要管,露出面要收” |
【关键数字】
| 参数 | 经验值 | 说明 |
|---|---|---|
| 密码哈希 | BCrypt cost≥10 / Argon2 / PBKDF2 10万+ 轮 | 故意慢 |
| 对称加密 | AES-256-GCM | 具备完整性校验 |
| 哈希摘要 | SHA-256+盐 | 禁 MD5/SHA1 存密码 |
| 脱敏规则 | 手机号保留前3后4;身份证保留前6后4 | 按业务可调 |
| 密钥轮换 | 90–180 天 | 信封加密降低轮换成本 |
| TLS 版本 | 1.2+(推荐 1.3) | 禁 SSLv3/TLS1.0 |
【追问链】(三层)
L1|“为什么密码要加盐?” → 防彩虹表与相同密码关联。每个用户一个随机盐,相同密码哈希结果不同;攻击者无法用预计算表批量破解,只能对每个用户单独爆破,成本乘以用户数。
L2|“加密后的手机号怎么按号段查询?” → ① 用确定性加密(同明文同密文)建立等值索引,但会泄露相等关系;② 保存密文+可搜索加密/盲索引;③ 手机号哈希(加盐)建精确匹配索引,明文字段另行加密。按查询场景权衡。
L3|“密钥泄露了怎么办?” → ① 立即吊销/轮换密钥;② 评估用该密钥加密的数据范围;③ 用新密钥重新加密受影响数据;④ 排查泄露渠道(Git 提交、日志、核心转储);⑤ 若涉及个人信息,按个保法评估是否告知用户。事前:KMS + 扫描硬编码密钥 + 权限最小化。
【评分标准】
| 档位 | 答案特征 |
|---|---|
| 60 分 | 知道“密码加密存、传输用 HTTPS” |
| 80 分 | 区分哈希 vs 可逆加密;KMS;脱敏 |
| 95 分 | 论证“是否需还原”的决策;密钥分离与信封加密;多露出面治理;合规国密 |
【关联题】
- 日志合规: 第 196 题
- 传输: 第 194 题(HTTPS)
- 认证凭证: 第 195 题(JWT)
- 越权防拖库扩大: 第 197 题
【自测】
- 为什么不用 AES 存用户密码? 参考答案:AES 可逆,库泄露则密码明文暴露。密码只需验证不需要还原,应使用 BCrypt 等故意慢的不可逆哈希+盐。
- 密钥写在配置文件里有什么问题? 参考答案:代码/配置泄露即密钥泄露,加密失效。应使用 KMS/HSM 管理,密钥与数据分离。
- 加密存储后接口返回还要脱敏吗? 参考答案:要。加密保护存储面,脱敏保护展示面。日志、页面、导出任一处露出全号都会造成泄露扩散。
194. 抓包发现密码是明文,要上 HTTPS(HTTPS/TLS)
【考察内容】HTTPS 原理是网络/安全高频题
【题目】安全测试报告指出:App 登录请求走 HTTP,抓包能看到明文密码。要改造 HTTPS。HTTPS 比 HTTP 多做了什么?TLS 握手过程(证书校验、密钥交换、会话密钥)讲一下?中间人攻击怎么防?
【参考答案】
HTTPS = HTTP + TLS:提供加密(防窃听)+ 完整性(防篡改)+ 身份认证(防冒充);
握手流程(TLS 1.2 简化):
- 客户端发送 ClientHello(支持的 TLS 版本、加密套件、随机数 A);
- 服务端返回 ServerHello(选定套件、随机数 B)+ 证书(含公钥、域名、CA 签名);
- 客户端验证证书(CA 信任链、域名、有效期、吊销状态)→ 生成预主密钥,用服务端公钥加密发送(RSA 模式;ECDHE 模式则交换密钥参数);
- 服务端用私钥解密得到预主密钥;
- 双方用随机数 A+B+预主密钥推导会话密钥(对称密钥);
- 双方发送 Finished(用会话密钥加密的握手摘要)互相确认——握手完成,之后用对称加密通信;
核心要点:
- 握手阶段用非对称加密(RSA)或密钥协商(ECDHE)交换会话密钥(ECDHE 本身不加密业务数据,它做的是 Diffie-Hellman 协商),业务数据用对称加密(AES)——性能与安全平衡;
- 证书由 CA 签发,客户端信任链校验——防中间人冒充;
- TLS 1.3 简化握手(1-RTT,前向安全默认 ECDHE);
中间人攻击:没有证书校验时攻击者可冒充服务端;证书信任链是 HTTPS 防冒充的关键。
容量估算: TLS 性能:现代 CPU 上 TLS1.3 + TLS_AES_128 吞吐足够;开启 HTTP/2/3 与会话复用后握手占比很低。证书自动化(ACME)与到期告警(提前 30 天)。禁用 SSLv3/TLS1.0、弱套件。
失败与降级: 证书错误导致不可用:保留上一张有效证书应急安装路径;CDN/网关双活证书。迁移 HTTPS 时 HTTP 仅重定向,不承载业务。
【原理溯源】
- 为什么需要“非对称换对称”两段式? 纯非对称加密每字节成本极高,扛不住业务吞吐;纯对称加密则双方无法在不安全信道上安全地共享同一把密钥。TLS 用非对称(或 ECDHE 密钥交换)在不安全信道上安全协商出对称会话密钥,之后全部业务数据走对称加密——非对称解决“密钥怎么安全地商量出来”,对称解决“数据怎么高效地保护”。
- 证书链为什么能防冒充? 攻击者可以自己生成密钥对并声称“我是 bank.com”,但客户端会校验证书是否由系统信任的 CA 签发、域名是否匹配、是否过期/吊销。攻击者拿不到 CA 的私钥,造不出合法证书。信任链(服务器证书 → 中间 CA → 根 CA)让“信任根”可以很少,而签发可以分层。
- 为什么现代 TLS 默认要求前向安全(PFS)? 若用 RSA 密钥交换,攻击者录下全部流量,未来某天窃取了服务器私钥,就能解开历史所有会话。ECDHE 每次握手生成临时密钥对,会话密钥不依赖服务器长期私钥——即使私钥日后泄露,历史流量仍安全。TLS 1.3 移除了纯 RSA 密钥交换,强制 PFS。
- 为什么证书校验不能跳过(App 里很常见)? 跳过校验(信任所有证书)等于允许中间人冒充:攻击者用自签证书劫持连接,用户以为在和银行通信,实际在和攻击者通信。App 必须校验证书链与主机名,高安全场景可证书锁定(certificate pinning)。
- HTTPS 为什么仍防不了重放与客户端侧抓包? HTTPS 保护的是网络链路。若设备已 root/越狱并安装了用户信任的代理证书,流量在客户端侧就被解密了。所以资金接口还需签名、时间戳、nonce、设备指纹(见 192 题)——HTTPS 是必要非充分条件。
【选型判断树】
HTTPS/TLS 落地决策:
1. 版本与套件
├─ 优先 TLS 1.3(1-RTT,强制 PFS)
└─ 兼容 TLS 1.2 + ECDHE + AES-GCM
2. 证书
├─ 公网 → 公共 CA(Let's Encrypt/商业 CA),自动续期
└─ 内部 → 私有 CA / Mesh mTLS
3. App 侧
├─ 必须校验证书链与主机名
└─ 高危可 pinning(注意证书轮换配合)
4. 残余风险
└─ 根设备抓包 → 配合签名/nonce/风控(192 题)判断口诀: 非对称换密钥,对称传数据;证书链防冒充;PFS 防未来私钥泄露。
【口述骨架】(5 分钟)
| 时间 | 任务 | 内容 |
|---|---|---|
| 0:00–0:30 | 定性 | “HTTPS=HTTP+TLS,提供加密、完整、身份认证” |
| 0:30–2:00 | 握手流程 | ClientHello→证书→验证→预主密钥→会话密钥→Finished |
| 2:00–3:00 | 关键思想 | 非对称换对称;证书链防冒充 |
| 3:00–4:00 | 现代特性 | TLS1.3、PFS、套件收紧 |
| 4:00–5:00 | 收尾 | “HTTPS 必要但不充分,高危接口还要业务层防护” |
【关键数字】
| 参数 | 经验值 | 说明 |
|---|---|---|
| TLS 1.2 握手 | 2-RTT | 可会话复用降低开销 |
| TLS 1.3 握手 | 1-RTT(0-RTT 复用有重放风险) | 0-RTT 仅用于幂等请求 |
| 证书续期 | 90 天(Let's Encrypt) | 必须自动续期 |
| 推荐套件 | TLS_AES_256_GCM_SHA384 等 | 禁 RC4/3DES/SHA1 签名 |
| HSTS | max-age≥31536000 | 强制 HTTPS,防降级 |
【追问链】(三层)
L1|“为什么不用纯非对称加密传数据?” → 非对称运算比对称慢几个数量级(尤其解密/签名),无法支撑高吞吐业务。TLS 只在握手时用非对称安全地交换出对称密钥,之后数据全部走 AES 等对称算法。
L2|“TLS 1.3 相比 1.2 改了什么?” → ① 握手减到 1-RTT;② 废弃不安全算法(RSA 密钥交换、CBC 套件、SHA1、RC4 等);③ 默认前向安全(ECDHE);④ 握手加密更早,减少明文暴露面。兼容性已很成熟,生产应优先。
L3|“内部服务间要不要 HTTPS?” → 建议 mTLS(双向证书),尤其跨主机房/多租户环境。同主机/受信内网可权衡性能用明文+网络策略,但零信任架构下内网也应加密认证(见 187 题)。Service Mesh 可零侵入开启。
【评分标准】
| 档位 | 答案特征 |
|---|---|
| 60 分 | 知道“HTTPS 是加密的 HTTP” |
| 80 分 | 能讲握手关键步骤;非对称换对称;证书作用 |
| 95 分 | PFS/前向安全;TLS1.3 改进;App 跳过校验的风险;HTTPS 局限与业务层配合 |
【关联题】
- 重放: 第 192 题
- 服务间 mTLS: 第 187 题
- 数据加密: 第 193 题
- JWT 传输: 第 195 题
【自测】
- HTTPS 分别提供了哪三重安全属性? 参考答案:加密(防窃听)、完整性(防篡改)、身份认证(防冒充,靠证书)。
- 为什么业务数据不用非对称加密? 参考答案:非对称运算太慢。TLS 用非对称/ECDHE 安全协商对称密钥后,数据走对称加密。
- App 开发中“信任所有证书”有什么危害? 参考答案:允许中间人用自签证书冒充服务器,HTTPS 形同虚设。必须校验证书链与主机名。
195. Token 泄露/无法踢人/算法漏洞,JWT 怎么安全地用(JWT 安全)
【考察内容】JWT 安全是认证体系高频进阶题
【题目】你们用 JWT 做登录态,出现一串问题:Token 被 XSS 窃取后伪造请求、用户投诉“账号无法踢下线”、安全扫描发现支持 alg=none。JWT 有哪些安全坑?正确用法(短有效期、refresh、黑名单、密钥管理)是什么?
【参考答案】
JWT 结构:Header(算法)+ Payload(用户信息、过期时间 exp)+ Signature(签名);特点:无状态(服务端不存),验签即可信;
安全问题:
- 签名算法混淆:服务端必须指定算法并校验,防止攻击者把 alg 改成
none跳过验签,或把 RS256 降级成 HS256、拿服务端公钥当 HMAC 密钥自签(经典漏洞 alg=none 与 RS256→HS256); - Payload 可读:只是 Base64,不能放敏感信息——只能放非敏感声明;
- 密钥泄露:HS256 共享密钥必须强随机、妥善保管(配置中心/KMS);
- Token 泄露:XSS 窃取(HttpOnly/短有效期缓解)、本地存储风险(放内存/HttpOnly Cookie 而非 localStorage);
- 无法主动失效(踢人/登出):JWT 无状态=服务端无法撤销——解决:短有效期 + refresh token(可撤销)+ 必要场景维护黑名单(登出记录 jti);
- 签名算法混淆:服务端必须指定算法并校验,防止攻击者把 alg 改成
最佳实践:
- 有效期短 + refresh token 轮换;
- 绑定用户与设备(jti 唯一标识),异常检测(异地/多端);
- 关键操作二次验证;
- 服务端可撤销场景(后台管理)用 Session/Redis 而非纯 JWT;
与 Session 对比:JWT 适合分布式/移动端/无状态;Session 适合需要主动管理会话的场景。
容量估算: JWT 校验是本地 CPU 操作,μs 级,可水平扩展。黑名单/踢人:短 access token(15–30min)+ refresh token + 服务端会话版本号;踢人通过版本号或 refresh 黑名单,容量按活跃用户估算(千万级 Redis 可承载)。载荷只放身份与权限版本,勿塞大对象。
失败与降级: 签名密钥泄露:立即轮换并吊销所有 token(升 session version);对旧算法 none/HS256 误用 RS 公钥当密钥的漏洞要做静态扫描。认证服务故障时可短时本地验签(在密钥未泄露前提下)。
【原理溯源】
- JWT 为什么“验签即可信”? 服务端用密钥/私钥对 Header+Payload 签名,任何人篡改 Payload 都会导致验签失败。这省去了服务端会话存储与查询,水平扩展友好。代价是:签发之后服务端无法单方面说“这个 Token 作废了”,除非引入黑名单或短有效期。
- alg=none 漏洞为什么会出现? 一些库实现“从 Token 的 header 读算法再验签”。攻击者把 header 改成
{"alg":"none"}并去掉签名,若服务端按 header 执行,就跳过了验签。防御:服务端白名单指定允许的算法,绝不信任 Token 自带的 alg;HS/RS 混淆同理(用公钥当 HMAC 密钥验签)。 - 为什么 Payload 不能放敏感信息? Payload 只是 Base64URL 编码,不是加密。任何人拿到 Token 都能解码看到内容。可以放 user_id、role、exp 等非敏感声明;手机号、身份证、权限细节应查库或放加密字段。
- 为什么短有效期+Refresh 是主流补偿? 短有效期(15 分钟)把“Token 泄露后的可利用窗口”压小;过期后用 Refresh Token(存服务端、可撤销、可轮换)换新 Access Token。这样兼顾无状态的扩展性与可控的会话生命周期。Refresh 泄露危害更大,要更严格存储与检测。
- 为什么高权限后台有时宁可用 Session? 管理后台需要“立即踢人”“强制下线”“按用户封锁”。Session 存在 Redis,删除 key 即失效;纯 JWT 做不到即时失效。技术选型服从于会话管理需求,不是“JWT 更先进所以处处用 JWT”。
【选型判断树】
登录态选型与 JWT 加固:
1. 需要主动踢人/即时失效吗?
├─ 需要(后台/风控) → Session/Redis 或 JWT+黑名单
└─ 不需要(C 端短时) → JWT 短有效期 + Refresh
2. Token 存哪?
├─ Web → HttpOnly+Secure+SameSite Cookie
├─ SPA → 内存(防 XSS)+ Refresh Cookie
└─ 禁止 localStorage 存长期 Access Token
3. 算法
├─ 对称 HS256(内部)→ 密钥进 KMS
└─ 非对称 RS256/ES256(多服务验签)→ 私钥签发,公钥分发
4. 必配
└─ 算法白名单、exp 短、jti、敏感操作二次验证判断口诀: 算法白名单,Payload 不放敏感,短效+Refresh,要踢人就上黑名单或 Session。
【口述骨架】(5 分钟)
| 时间 | 任务 | 内容 |
|---|---|---|
| 0:00–0:30 | 定性 | “JWT 无状态带来扩展性,也带来无法撤销的代价” |
| 0:30–1:30 | 结构与漏洞 | 三段式;alg=none/算法混淆;Payload 明文 |
| 1:30–2:30 | 泄露与存储 | HttpOnly vs localStorage;XSS 面 |
| 2:30–3:30 | 失效难题 | 短有效期+Refresh+黑名单/jti |
| 3:30–4:30 | 与 Session 对比 | 按“是否要主动管理会话”选型 |
| 4:30–5:00 | 收尾 | “JWT 不是银弹,失效与存储策略决定它是否安全” |
【关键数字】
| 参数 | 经验值 | 说明 |
|---|---|---|
| Access Token 有效期 | 15–30 分钟(高危场景压到 5–15 分钟) | 泄露窗口小;与正文同口径 |
| Refresh Token | 数天~数周(常见 7–30 天),可轮换 | 存服务端可撤销 |
| 签名算法白名单 | HS256 或 ES256/RS256 | 禁 none,禁信任 Token 自带 alg |
| 密钥长度 | HMAC ≥256 bit;RSA ≥2048 | 强随机 |
| 黑名单场景 | 登出/改密/风控封禁 | 否则靠过期 |
【追问链】(三层)
L1|“refresh token 有什么用?” → Access 短效降低泄露危害;Refresh 长效且存服务端可撤销,用于静默续期。Refresh 应轮换(用旧换新),并检测重放(同一 Refresh 用两次则视为泄露,吊销家族内全部 Token)。
L2|“JWT 能放 Redis 吗?” → 放 Redis 当全量会话存储就失去了无状态意义,不如直接用 Session。合理用法是:Access 仍无状态验签,只把“已登出/已吊销的 jti”放 Redis 黑名单——有状态做减法而不是做全量。
L3|“分布式下 HS256 和 RS256 怎么选?” → HS256:签发与验签共享同一密钥,简单,但任何能验签的服务都能伪造 Token。RS256/ES256:私钥只在认证中心,各业务服务用公钥验签——伪造需要私钥,更安全。多服务架构推荐非对称。
【评分标准】
| 档位 | 答案特征 |
|---|---|
| 60 分 | 知道“JWT 是无状态 Token,有过期时间” |
| 80 分 | 讲清 alg=none、Payload 不加密、无法撤销→短效+Refresh+黑名单 |
| 95 分 | 算法混淆与白名单;存储位置与 XSS;Refresh 轮换;与 Session 的选型边界;HS/RS 分布式选择 |
【关联题】
- XSS 窃取 Token: 第 190 题
- CSRF(Cookie 存 Token 时): 第 191 题
- 服务间 JWT: 第 187 题
- 分布式 Session: 第 111 题
【自测】
- alg=none 漏洞是怎么产生的?如何防? 参考答案:服务端信任 Token 头里的算法字段,攻击者改为 none 跳过验签。防御:服务端算法白名单,绝不按 Token 自带 alg 验签。
- 为什么 JWT 难以实现“立即踢人”? 参考答案:无状态验签,签发后服务端不存会话。需短有效期、Refresh 吊销、或 jti 黑名单/改用 Session。
- Access Token 放 localStorage 有什么风险? 参考答案:XSS 可直接读取并窃走。更安全是 HttpOnly Cookie 或仅存内存。
196. 等保/个保法要求日志不能出现用户隐私(日志合规)
【考察内容】数据合规工程化(个保法/等保场景高频)
【题目】等保和《个人信息保护法》要求:日志和数据库查询不能明文出现手机号、身份证等个人信息。日志脱敏怎么做(打码/哈希/不落盘)?开发规范怎么定?存量日志怎么处理?
【参考答案】
识别敏感字段:手机号、身份证、银行卡、地址、token、密码——建立敏感字段清单;
日志脱敏:
- 打日志前脱敏(工具类统一处理:手机号 138****6789、身份证 110101********1234);
- 序列化时脱敏(Jackson 注解/自定义序列化器,统一处理对象中的敏感字段);
- 框架层拦截(日志切面统一脱敏,避免业务漏写);
- 禁止打完整请求参数(尤其含密码/支付信息);
日志分级:debug 可打内部字段(仅本地),生产日志必须脱敏;
数据生命周期合规:
- 数据加密存储(见 193 题)+ 权限管控(谁能查明文);
- 数据保留期(不能写成「日志保留 N 天自动清理」:网络安全日志法定下限 ≥6 个月/180 天,业务与审计日志按等保定级常见 6 个月–3 年,只有无合规要求的临时 debug 日志才按天滚动,且要在策略里注明「不适用合规日志」。开发照「N 天」落地就会把网络日志清在法定期之外,题干这个合规问题等于没解决;口径见【关键数字】「日志保留期」一行);
- 数据导出审批(运营导出用户数据需审批留痕);
- 数据删除(用户注销后删除/匿名化);
检测:日志扫描(正则/敏感词)、脱敏规则自动化测试、审计(谁查了敏感数据);
工具:脱敏中间件/日志脱敏框架(如 logstash 脱敏 filter)。
容量估算: 日志脱敏在采集端做,避免敏感原文进磁盘/ES。脱敏规则数量与日志字段数相关,CPU 开销可接受。审计日志保留:等保/金融常见 6 个月–3 年,按日增量×天数估存储,冷热分层。访问审计本身也要防篡改(只追加、定期哈希上链或异地备份)。
失败与降级: 发现已落盘明文:先限制日志文件权限与下载,再安全清理/轮转;排查下游 ES/HDFS/数仓是否有副本。合规事件按制度上报,不能只删文件了事。
【原理溯源】
- 为什么日志是泄露重灾区? 日志会流向 ELK、对象存储、第三方 APM、开发本地、工单截图——传播面远大于数据库。一次“方便排查问题打了完整请求体”就可能让手机号/身份证进入不可控的系统。合规视角:日志里的个人信息与库里的同等对待。
- 为什么要在框架层统一脱敏而不是靠开发自觉? 人总会忘。把脱敏做进日志框架(切面/序列化器/Logback 插件),敏感字段默认脱敏,开发想打全号反而要显式绕过并被审计——把安全从“个人自觉”变成“系统默认”。
- 打码与哈希分别适合什么场景? 打码(135****6789)适合展示与排障(可核对前后位);哈希(加盐)适合需要“同一用户可关联但不可还原”的统计/审计场景。密码类永不落日志;token 类打码或不落盘。
- 为什么要有保留期与删除权? 个保法要求目的明确、最短必要保存。日志永久堆积既是存储成本也是合规风险。用户注销后,其个人信息应从日志/数仓中删除或匿名化——这是“被遗忘权”的工程落地,依赖敏感字段清单做检索。
- 如何证明“我们已脱敏”? 需要检测闭环:自动化扫描生产日志样本中的手机号/身份证正则;脱敏规则的单元测试;访问审计(谁导出了用户数据)。没有检测的规范只是纸面合规。
【选型判断树】
日志合规怎么落地?
1. 建清单
└─ 手机号/身份证/银行卡/地址/token/密码/设备号
2. 脱敏层选点
├─ 业务代码 → 工具类(易漏,不推荐单独依赖)
├─ 序列化框架 → Jackson 注解(推荐)
└─ 日志框架 → Logback/Log4j2 插件(默认兜底)
3. 展示与存储
├─ 库内 → 加密(193 题)
├─ 日志 → 打码/哈希/不落盘
└─ 导出 → 审批+留痕
4. 生命周期
└─ 保留期 → **按类别分档清理**:网络日志法定 ≥6 个月(180 天)不得按天删、业务/审计按等保定级(6 个月–3 年)、仅临时 debug 按天滚动;到期归档或销毁;注销 → 删除/匿名化
5. 检测
└─ 正则扫描 + 规则测试 + 访问审计判断口诀: 先清单,再框架默认脱敏,存储加密,导出审批,扫描验证。
【口述骨架】(5 分钟)
| 时间 | 任务 | 内容 |
|---|---|---|
| 0:00–0:30 | 定性 | “日志泄露面比库更大,个保法同等看待” |
| 0:30–1:30 | 脱敏手段 | 打码/哈希/不落盘;框架层统一处理 |
| 1:30–2:30 | 工程化 | 序列化注解+日志切面;禁打完整参数 |
| 2:30–3:30 | 生命周期 | 保留期、导出审批、注销删除 |
| 3:30–4:30 | 检测 | 正则扫描、规则测试、访问审计 |
| 4:30–5:00 | 收尾 | “默认脱敏+可验证,才算合规落地” |
【关键数字】
| 参数 | 经验值 | 说明 |
|---|---|---|
| 手机号打码 | 前3后4 | 138****6789 |
| 身份证打码 | 前6后4 | 保留地址码与校验位线索 |
| 日志保留期 | 网络日志法定下限 ≥6 个月;业务/审计日志按等保定级,常见 6 个月–3 年 | 到期归档或销毁,别写"3 天"这类低于下限的默认值 |
| 生产日志级别 | info/warn,禁 debug 全参 | debug 仅本地 |
| 导出审批 | 100% 留痕 | 含操作人/范围/目的 |
【追问链】(三层)
L1|“日志里能存加密后的字段吗?” → 可以。密文本身不是明文个人信息,可用于关联排查。但解密权限必须管控,且算法/密钥管理要合规(KMS)。注意:确定性加密可能泄露相等关系,按场景选择。
L2|“排查线上问题必须要全号怎么办?” → ① 用脱敏值+内部工具在授权下临时解密;② 走审批的临时明文查询通道,全量审计;③ 用用户 ID/订单号作为主键关联,避免在日志里直接用手机号。原则:排查便利不能突破最小必要。
L3|“存量历史日志已经打了全号怎么办?” → ① 摸底范围(哪些系统、时间窗);② 可回刷的做脱敏重写或删除;③ 不可改写的归档隔离+访问收紧;④ 评估是否达到告知/报告门槛;⑤ 新日志立即上框架脱敏,防止增量扩大。
【评分标准】
| 档位 | 答案特征 |
|---|---|
| 60 分 | 知道“日志要打码脱敏” |
| 80 分 | 敏感清单、框架层统一脱敏、生命周期与导出审批 |
| 95 分 | 论证日志传播面;默认安全工程化;检测闭环;存量治理与个保法义务 |
【关联题】
- 存储加密: 第 193 题
- 展示脱敏同源: 第 193 题脱敏
- 越权审计: 第 197 题
- 日志系统设计: 第 140 题
【自测】
- 为什么日志脱敏要在框架层做而不是业务代码里? 参考答案:业务代码靠自觉会漏;框架/序列化层默认脱敏把安全变成系统默认行为,降低遗漏概率。
- 手机号在日志中打码和哈希分别适合什么场景? 参考答案:打码适合展示排障(可核对);哈希适合需要用户关联但不可还原的统计审计。
- 用户行使删除权时,日志侧要做什么? 参考答案:按敏感字段检索并删除或匿名化相关日志/数仓记录,并保留处理审计证明。
197. 用户改个订单号就能看别人的订单(越权漏洞)
【考察内容】越权是安全测试最高频考点(甲方安全/全栈高频)
【题目】安全测试发现:用户 A 把请求里的订单号改成别人的,就能查看/修改他人订单(水平越权);普通用户直接调管理员接口也能成功(垂直越权)。两类越权怎么防?从接口设计、鉴权、数据权限几个层面给出方案。
【参考答案】
两类越权:
- 水平越权:同角色用户访问他人资源(改 order_id 查别人的订单);
- 垂直越权:低权限用户执行高权限操作(普通用户调管理接口);
防护:
- 垂直越权(权限校验):RBAC 权限模型 + 后端接口鉴权(注解/拦截器校验角色权限,不能只靠前端隐藏按钮);接口级权限矩阵(谁可调哪个接口);
- 水平越权(资源归属校验):接口内必须校验资源归属——查询/更新前检查资源 owner(order.user_id == 当前用户)或数据权限规则(SQL 注入 owner 条件,如
WHERE user_id = 当前用户); - 防遍历:ID 不可预测(雪花/随机 ID,避免自增可遍历)、敏感接口频控;
架构级:统一鉴权中间件(网关/框架层做权限校验,业务层做资源归属校验)、数据权限框架(MyBatis 拦截器自动拼 owner 条件);
测试:越权测试用例(A 访问 B 的资源)纳入安全测试;安全扫描(IDOR 检测);
原则:永远不信任前端传入的 ID,后端必须二次确认归属与权限。
容量估算: 越权防护是代码正确性问题不是容量问题;但网关鉴权 QPS、权限中心查询要缓存。经验:每个资源接口必须做“数据归属校验”,不能只做菜单权限。自动化测试:改 ID 横向越权用例应进 CI。渗透测试频率:核心金融接口至少年检+重大变更后。
失败与降级: 发现越权:立即网关按用户维度限流/下线接口,吊销可疑会话,排查访问日志评估泄露范围。短时可在网关加“资源属主校验”补丁策略(若网关能拿到用户与资源关系)。
【原理溯源】
- 为什么两类越权要分开防? 垂直越权缺的是“这个接口需要什么角色”;水平越权缺的是“这个资源归谁”。前者是接口级权限矩阵,后者是数据级归属校验。只做 RBAC 只能防垂直,普通用户之间互查订单照样失守——很多团队误以为“有权限系统就安全了”。
- 为什么前端隐藏按钮没用? 接口是公开的攻击面。攻击者可直接 curl/改包调用任何已知 URL。权限必须在服务端校验;前端隐藏只是体验,不是安全。
- 为什么 SQL 层拼 owner 条件是好模式? 把
user_id = 当前用户做成数据权限框架(MyBatis 拦截器自动拼接),业务代码难以忘记。即使开发写了selectById(id),框架也会自动限制数据范围。这比“每个接口手写 if owner”更不易漏。 - 为什么 ID 可预测会放大越权? 自增 ID 让攻击者可以遍历(10001、10002...)批量拉取他人数据。雪花/随机 ID 增加遍历成本,但不能替代归属校验——只是降低自动化攻击效率。纵深上归属校验才是根治。
- 为什么网关鉴权不够? 网关适合认证(你是谁)与粗粒度接口权限(能不能调 /admin);资源归属(这个 order 是不是你的)只有业务层知道。安全责任必须分层:网关认身份,业务认归属。
【选型判断树】
越权防护分层:
1. 垂直越权
├─ 网关/拦截器:接口级 RBAC/ABAC
└─ 前端隐藏只是体验
2. 水平越权
├─ 查询条件强制加 owner(数据权限框架)
└─ 业务代码二次校验 resource.userId == currentUser
3. 防遍历
└─ 随机/雪花 ID + 频控
4. 验证
└─ 越权用例进安全测试(A 访 B 的资源)判断口诀: 垂直管接口权限,水平管资源归属;不信任前端 ID,SQL 自动拼 owner。
【口述骨架】(5 分钟)
| 时间 | 任务 | 内容 |
|---|---|---|
| 0:00–0:30 | 定性 | “越权分水平(资源归属)与垂直(角色权限)两类” |
| 0:30–1:30 | 垂直防御 | RBAC + 后端接口鉴权;前端隐藏无效 |
| 1:30–2:30 | 水平防御 | owner 校验;数据权限框架拼条件 |
| 2:30–3:30 | 防遍历 | 不可预测 ID + 频控 |
| 3:30–4:30 | 工程化 | 网关认身份、业务认归属;安全测试 |
| 4:30–5:00 | 收尾 | “每个带 ID 的接口都要问:这个资源归谁” |
【关键数字】
| 参数 | 经验值 | 说明 |
|---|---|---|
| ID 类型 | 雪花/UUID/随机串 | 避免纯自增暴露量级 |
| 鉴权位置 | 100% 后端 | 前端仅体验 |
| 数据权限 | MyBatis 拦截器自动拼 | 减少业务遗漏 |
| 安全测试 | 每个资源接口要有 A→B 用例 | 纳入回归 |
| 网关 vs 业务 | 网关=身份/粗权限;业务=归属 | 分层 |
【追问链】(三层)
L1|“如何自动化发现越权?” → ① 安全测试平台准备两个账号,用 A 的 Token 请求 B 的资源 ID,比对是否 200;② 代码审计扫描“取 id 后未校验 owner”的模式;③ 渗透测试 IDOR。应纳入 CI/定期扫描。
L2|“管理员接口和用户接口在同一个服务里,怎么隔离?” → 路径前缀隔离(/admin/**)+ 独立鉴权拦截器 + 独立部署更佳(管理面与用户面分离)。至少保证 /admin 强制高权限角色,且默认拒绝。
L3|“数据权限框架会不会误伤跨部门协作查询?” → 需要显式的“数据范围”模型:本人/本部门/全部,角色配置数据范围而不是写死。客服“查用户订单”应走受控工具(全量权限+操作审计),而不是给业务接口开后门。
【评分标准】
| 档位 | 答案特征 |
|---|---|
| 60 分 | 知道“要校验权限、不能改别人 ID” |
| 80 分 | 区分水平/垂直;后端鉴权;owner 校验;防遍历 |
| 95 分 | 数据权限框架工程化;网关/业务分层责任;自动化测试;客服等特殊场景受控例外 |
【关联题】
- RBAC/认证: 第 195 题、第 187 题
- 网关鉴权: 第 138 题
- 审计日志: 第 196 题
- 防刷频控: 第 4 题
【自测】
- 水平越权和垂直越权的区别是什么? 参考答案:水平是同角色用户访问他人资源;垂直是低权限调高权限接口。防护分别是资源归属校验与接口角色校验。
- 为什么前端隐藏管理按钮不能防垂直越权? 参考答案:攻击者可直接构造 HTTP 请求调用接口。权限必须在服务端校验。
- 怎么从架构上减少“忘记校验 owner”? 参考答案:数据权限框架在 SQL 层自动拼接 user_id/数据范围条件;代码审查与越权测试用例兜底。
198. 服务上 K8s 后频繁被杀、启动慢、流量异常(K8s 部署适配)
【考察内容】云原生部署是 2024+ 高频题
【题目】服务容器化上 K8s 后问题不断:滚动发布时流量切到还在启动的实例、停机时正在处理的请求被断、Pod 内存超限被杀。开发侧要注意什么(优雅停机、就绪/存活探针、资源限制、日志输出)?
【参考答案】
优雅停机(关键):
- 应用注册 PreStop Hook + SIGTERM 处理:收到停止信号后,先摘除注册中心/负载均衡流量(下线),停止接收新请求,处理完在途请求(等待时间窗口)再退出——避免请求中断/消息丢失;
- 关闭线程池/连接池/消息消费前先排空;
探针配置:
- readinessProbe(就绪:可以接流量了)——应用启动完成、依赖就绪才返回 200;
- livenessProbe(存活:进程活着)——死锁/卡死时 K8s 重启;
- startupProbe(启动慢的应用);
- 探针路径要做成轻量接口(不能依赖下游,否则误判);
资源管理:requests/limits 合理设置(CPU/内存),防止 OOM Kill(JVM 容器内存要 -XX:MaxRAMPercentage 感知容器限制);避免进程内线程数=CPU 核数×N 的假设(容器核数感知);
无状态与存储:日志输出到 stdout(由平台采集,不写本地文件);有状态数据用 PVC/外部存储;
配置:环境配置通过 ConfigMap/环境变量注入,不打包进镜像;
其他:镜像瘦身(启动快)、健康检查、优雅退出超时设置(terminationGracePeriodSeconds)。
容量估算: K8s 资源:requests/limits 按压测单实例峰值 CPU/内存设置,CPU limit 过低会导致 throttle(表现为慢但不是 OOMKilled)。HPA:按 CPU 或自定义 QPS 指标,扩容冷却与稳定窗口要配。就绪探针:避免未预热实例接流。驱逐:PDB 保证最小可用副本数。镜像体积影响启动秒数。
失败与降级: 频繁被杀先看 OOMKilled vs Evicted vs liveness 失败,处置不同。紧急:放宽 resources/探针、增加副本、临时固定节点。应用要支持优雅退出(停接流→处理完在途→退出),减少滚动发布失败。
【原理溯源】
- 为什么 K8s 默认 kill 会造成请求中断? 滚动更新时 K8s 先删旧 Pod(发 SIGTERM,宽限期后 SIGKILL)。若应用不处理 SIGTERM,或摘流量太慢,在途请求与新请求会打到正在死亡的实例。正确顺序:摘流量 → 排空在途 → 退出。PreStop 里 sleep 几秒可让 endpoints 更新传播到 kube-proxy。
- readiness 与 liveness 为什么必须分开? readiness 控制“是否接流量”,失败只是不接新流量,不影响存活;liveness 控制“是否重启”,失败会杀容器。若把“下游 Redis 超时”设成 liveness,会引发级联重启风暴(下游一抖,全集群重启)。liveness 只探活进程本身(死锁/卡死)。
- 为什么 JVM 在容器里容易 OOM? 传统 JVM 按宿主机内存算堆,容器 limits 只给 2G 时 JVM 可能按 32G 规划堆,直接被 cgroup OOM Kill。Java 10+/8u191+ 用
-XX:+UseContainerSupport与MaxRAMPercentage(如 70%)感知容器限制。还要预留元空间、栈、堆外内存。 - 为什么日志要打 stdout? 容器文件系统是临时的,Pod 重建即丢;且多副本写本地文件无法集中检索。stdout/stderr 由节点日志驱动收集,进入统一日志平台(与第 140 题一致)。有状态数据一律外置(对象存储/PVC/DB)。
- 为什么镜像要瘦、启动要快? 滚动发布与弹性扩缩容都依赖“快速就绪”。启动 3 分钟则:发布慢、扩容接不住流量峰值、故障恢复时间长。优化:分层构建、小基础镜像、懒加载非核心、减少启动时全量预热(或用 startupProbe 容忍慢启动)。
【选型判断树】
K8s 上的服务适配清单:
1. 流量切换
└─ readinessProbe 轻量自检;PreStop 摘流量+sleep
2. 进程死亡
└─ SIGTERM → 排空线程池/在途请求 → 退出;terminationGracePeriod 足够
3. 卡死重启
└─ livenessProbe 只探本进程,不依赖下游
4. 慢启动
└─ startupProbe + 合理 initialDelay
5. 内存
└─ limits 设置 + JVM MaxRAMPercentage;预留堆外
6. 可观测
└─ 日志 stdout;指标 /metrics;配置 ConfigMap判断口诀: readiness 管接流量,liveness 管重启;SIGTERM 先摘流量再排空;JVM 认容器内存。
【口述骨架】(5 分钟)
| 时间 | 任务 | 内容 |
|---|---|---|
| 0:00–0:30 | 定性 | “容器化问题集中在生命周期:启动、接流量、停机、内存” |
| 0:30–1:30 | 探针 | readiness/liveness/startup 分工;探针不依赖下游 |
| 1:30–2:30 | 优雅停机 | SIGTERM→摘流量→排空→退出;PreStop 作用 |
| 2:30–3:30 | 资源与 JVM | limits;MaxRAMPercentage;堆外内存 |
| 3:30–4:30 | 日志与配置 | stdout;ConfigMap;镜像瘦身 |
| 4:30–5:00 | 收尾 | “把 K8s 生命周期钩子接对,比改业务代码更重要” |
【关键数字】
| 参数 | 经验值 | 说明 |
|---|---|---|
| terminationGracePeriodSeconds | 30–60s | 大于排空所需 |
| PreStop sleep | 5–10s | 等 endpoint 摘除传播 |
| JVM MaxRAMPercentage | 50–75% | 预留堆外/元空间 |
| readiness | 启动完成+本地依赖 | 不调下游 |
| liveness | 轻量自检,failureThreshold 放宽 | 防误杀 |
| 日志 | stdout/stderr | 由平台采集 |
【追问链】(三层)
L1|“为什么容器里 JVM 会 OOM?” → JVM 默认按宿主机内存分配堆,容器 limits 较小时堆超限被 cgroup 杀。用 UseContainerSupport + MaxRAMPercentage,并预留 metaspace、线程栈、直接内存、code cache。
L2|“探针依赖了 Redis,Redis 抖动导致全集群重启怎么办?” → 这是 liveness 误用。liveness 只探进程;依赖检查放 readiness(暂时不接流量)或启动探针。修复后应提高 liveness 的失败阈值与延迟,避免瞬时抖动触发重启。
L3|“发布时仍有少量 502,怎么继续压?” → ① PreStop 充分 sleep;② 优雅退出排空时间加长;③ 网关/Service 的连接排空(LB 摘除延迟);④ 应用侧处理 in-flight 请求完成后再关 HTTP server;⑤ 发布策略改 maxUnavailable=0/maxSurge>0 先扩后缩。
【评分标准】
| 档位 | 答案特征 |
|---|---|
| 60 分 | 知道“要配健康检查、限制资源” |
| 80 分 | 讲清三类探针分工、优雅停机流程、JVM 容器感知 |
| 95 分 | liveness 级联重启风险;PreStop 与 endpoint 传播;日志 stdout;先扩后缩发布策略 |
【关联题】
- 发布事故: 第 174 题
- K8s/弹性: 第 18 题
- 日志系统: 第 140 题
- 配置: 第 180 题
【自测】
- readinessProbe 和 livenessProbe 有什么区别? 参考答案:readiness 失败则不接流量;liveness 失败则重启容器。依赖下游的检查只能放 readiness,防级联重启。
- 滚动发布时如何避免请求打到正在退出的实例? 参考答案:SIGTERM/PreStop 先摘注册与 Service 流量,sleep 等传播,排空在途请求再退出。
- 为什么 JVM 需要 MaxRAMPercentage? 参考答案:让堆大小基于容器 limits 而非宿主机内存,避免超限被 OOM Kill。
199. 一次改动跨 5 个服务,怎么只灰度给 1% 用户(全链路灰度)
【考察内容】全链路灰度是微服务高级工程题(大厂架构岗高频)
【题目】一次版本升级同时改了 A、B、C 三个服务,如果只灰度 A,流量到旧 B 就错乱了。怎么做全链路灰度(按用户维度染色,灰度流量在整个调用链都走新版本)?染色标记怎么传递?
【参考答案】
需求:多服务联动变更,全量发布风险大——需要按用户维度的灰度(同一用户全程走新版本,避免 A 新版+B 旧版混用);
方案:
- 流量染色/标签路由:网关/入口按用户 ID 或请求头打标(灰度用户标记,如用户 ID hash 前 10% 或白名单),通过标签路由(Nacos/Dubbo/网关)把灰度用户的请求路由到新版本服务,非灰度用户走旧版本——链路内所有服务按同一标签路由(全链路灰度);
- 注册中心支持(Nacos 多版本/灰度分组);K8s 可用 Istio 流量管理(destination rule 按 header 路由);
流程:确定灰度比例(起点按爆炸半径取 1%~5%,其后 5%→10%→50%→100%,与本题【关键数字】表同一阶梯)→ 观察灰度用户的关键指标(错误率/RT/转化/核心业务数据)→ 每个阶段卡点 → 全量后清理灰度标记;
关键:
- 数据兼容:新版本可能写新字段——旧版本要能兼容读(前向兼容,DB 变更先行灰度);
- 灰度用户画像要具代表性(不能只灰度内部员工);
- 回滚:灰度失败则把标签路由切回旧版本(秒级);
对比部署灰度(按实例):部署灰度只管“新老实例共存”,全链路灰度管“同一用户全链路一致版本”,两者常结合使用。
容量估算: 全链路灰度:标签从网关透传到 RPC/MQ/缓存键或影子表。流量放大:跨服务后实际打到下游的灰度比例会衰减/错乱,要保证染色一致。观察指标:核心业务+错误率+性能,样本量按统计功效估算(转化类指标要按「转化事件数」而不是会话数算,且必须与放量节奏对齐:日会话 720 万时,1% 档 60 分钟只有 720万×1%÷1440×60≈3000 会话,按 3% 转化率折算仅约 90 个转化事件/组,离「数千样本/组」差一两个数量级——所以转化率卡点只能放到 20%~50% 档、窗口按小时~天;日流量再小一档就要放到天级;1%~5% 档的卡点只写工程护栏)。跨 5 服务时,任一环节丢染色都会导致“灰度不纯”。
失败与降级: 灰度出问题:一键把染色流量切回基线;熔断灰度服务实例。灰度数据要「可识别+可回滚」:写入带灰度标记位或独立字段、schema 只加可空列(前向兼容),出问题按标记清理。但别把「影子库」当灰度手段:本块【原理溯源】「DB 变更必须先行」的前提就是灰度期新老实例同时写真实主表,灰度流量若落影子库就没验证过真实数据路径,还要额外回迁;影子表/影子库是压测的手段,两者不要混用。
【原理溯源】
- 为什么按实例灰度不够? 实例灰度下,用户第一次请求可能落到 A 新版,第二次落到 A 旧版;且 A 新版调用 B 时可能落到 B 旧版。若新版改了协议/数据语义,会出现“时好时坏”的灵异 Bug,极难排查。全链路灰度保证同一用户的调用路径上版本一致。
- 染色标记为什么要全链路透传? 标记只在入口打还不够——服务 A 调 B、B 发 MQ 给 C,标记必须跟着走。手段:RPC 框架隐式透传(attachment/header)、MQ 消息头、线程上下文(注意线程池传递)、HTTP Header。丢失一环就会“染色断链”。
- 为什么 DB 变更必须先行? 灰度期间新老实例同时写库。若新版写新列,旧版读不懂;若新版改枚举语义,旧版解析错误。正确顺序:先做兼容的 DB 变更(加可空列)→ 发布兼容代码 → 灰度新逻辑 → 全量后再清理。代码可以灰度,不兼容的 schema 变更不能。
- 为什么灰度用户要有代表性? 只灰度员工/白名单,流量模式(机型、网络、操作路径)与真实用户不同,指标失真。应按 user_id hash 稳定采样,保证画像覆盖;对极高危功能可叠加白名单。
- 为什么回滚能做到秒级? 标签路由是控制面配置,改配置即把灰度流量切回旧版本,无需重新部署。前提是旧版本仍在线且能处理灰度期间产生的新数据——又回到数据兼容问题。
【选型判断树】
多服务联动怎么灰度?
1. 变更是否跨服务且互依赖?
├─ 否 → 普通部署灰度即可
└─ 是 → 全链路染色
2. 染色维度
├─ 用户体验一致性优先 → user_id hash 稳定采样
└─ 极高危功能 → 白名单内测再放量
3. 实现
├─ 微服务框架 → Nacos/Dubbo 标签路由 + 透传
└─ K8s → Istio VirtualService/DestinationRule
4. 前置条件
└─ DB/接口向后兼容;回滚路径可用
5. 观察指标
└─ 错误率、RT、资源(1%~5% 档);**核心转化只在 20% 以上档位作为卡点**判断口诀: 先染色再透传,DB 先行保兼容,按比例放量看指标,失败秒级切回。
【口述骨架】(5 分钟)
| 时间 | 任务 | 内容 |
|---|---|---|
| 0:00–0:30 | 定性 | “跨服务联动变更要按用户全链路灰度,避免新老混用” |
| 0:30–1:30 | 染色与路由 | 入口打标;标签路由到新版本组 |
| 1:30–2:30 | 标记透传 | RPC header/MQ/上下文;断链问题 |
| 2:30–3:30 | 数据兼容 | DB 先行;加列不删列 |
| 3:30–4:30 | 放量与回滚 | 比例阶梯;指标卡点;秒级切回 |
| 4:30–5:00 | 收尾 | “染色保证路径一致,兼容保证可回滚” |
【关键数字】
| 参数 | 经验值 | 说明 |
|---|---|---|
| 灰度比例阶梯 | 1%→5%→20%→50%→100% | 窗口按指标类型分两档:错误率/RT/分布这类工程护栏每档 15–60 分钟即可;转化/GMV 这类业务显著性卡点要 20%~50% 档+小时~天级窗口(样本数按转化事件算) |
| 白名单阶段 | 可先 100% 内部员工 | 再转 hash 采样 |
| 染色 key | user_id / uid hash | 稳定,同一用户始终同版本 |
| 回滚 | 改路由配置,秒级 | 依赖旧版本仍在线 |
| DB 变更 | 提前 1 个发布周期 | 加可空列 |
【追问链】(三层)
L1|“标签怎么透传到 MQ 下游?” → 消息体外加 headers(Kafka headers / RocketMQ properties)写入染色标记;消费者取出后放入 ThreadLocal/Context,再在后续 RPC 中透传。注意线程池、异步回调场景的 Context 传递。
L2|“灰度期间数据不一致怎么查?” → 日志与链路追踪必须带灰度标记(trace 中记录 version/route tag),出问题可按“是否灰度流量”切片对比。监控大盘也应分灰度/基线两条曲线。
L3|“全量后如何安全清理旧版本?” → ① 灰度标记流量归零;② 观察旧版本无流量后下线;③ 下个迭代清理兼容代码与废弃列;④ 保留回滚窗口一段时间。避免“全量即删旧”导致无法回滚。
【评分标准】
| 档位 | 答案特征 |
|---|---|
| 60 分 | 知道“按用户灰度、打标记” |
| 80 分 | 讲清标签路由、全链路透传、数据兼容、指标卡点 |
| 95 分 | 染色断链场景;DB 先行时序;代表性采样;与部署灰度结合;清理节奏 |
【关联题】
- 发布工程: 第 188 题(CI/CD)
- 配置灰度: 第 120 题
- 注册中心多版本: 第 112 题
- 接口兼容: 第 200 题
- 发布事故: 第 174 题
【自测】
- 为什么只灰度 A 服务不够? 参考答案:A 新版会调到 B 旧版,协议/语义不兼容时行为错乱。需要同一用户全链路都走新版本。
- 染色标记丢失一环会怎样? 参考答案:染色断链,后续服务可能路由到旧版本,出现新老混用。RPC/MQ/异步上下文都要透传。
- 全链路灰度对数据库变更的要求是什么? 参考答案:必须向后兼容并先行发布(如加可空列),保证新老实例共存期都能读写。
200. 接口字段要变,老客户端不能挂(接口兼容性)
【考察内容】接口演进与兼容性工程
【题目】线上接口需要改字段(加必填参数/改返回结构/改逻辑),但老版本 App 还在大量使用。怎么做接口版本管理(URL 版本/Header 版本)?怎么保证向后兼容?新老逻辑怎么共存与下线?
【参考答案】
原则:接口向后兼容(老调用方不感知变更),变更不兼容时必须版本化;
版本策略:
- URL 版本:/v1/orders、/v2/orders——显式、最常用;新版本独立实现,老版本保留;
- Header/参数版本:Accept 头或 version 参数(同 URL 多版本)——RESTful 风格;
- 兼容性判断:
- 新增字段:兼容(老客户端忽略未知字段——序列化要忽略未知属性);
- 删字段/改类型/改语义/改必填:不兼容,必须新版本;
- 字段默认值兜底:新逻辑缺老字段时给默认值;
演进策略:
- 加字段先于改逻辑:DB 加列(可空/默认值)→ 发布支持新字段的版本 → 老版本共存;
- 版本共存期:老版本维护(bug 修复),约定下线周期(如保留 6 个月/两个大版本);
- 服务端适配层:一个接口兼容多版本入参(参数转换器);
- 客户端强制升级策略(版本号+最低支持版本,老版本接口提示升级);
实践:契约测试(消费者驱动,防接口破坏)、接口变更评审、文档(OpenAPI)同步、兼容性自动化测试(老版本用例持续回归)。
容量估算: 接口兼容成本:老客户端版本长尾可能数月–1年+,必须按活跃版本分布决定兼容窗口。字段变更原则:只增不删不改语义;删除字段先停写再停读。契约测试与流量录制回放。网关层可做字段适配,但会积累技术债。
失败与降级: 老客户端大面积报错:网关按版本兜底转换或返回兼容响应;必要时服务端保留旧逻辑开关。发布后监控按客户端版本拆分错误率。
【原理溯源】
- 为什么“新增字段”通常兼容而“删改字段”不兼容? 多数序列化协议/JSON 解析器在反序列化时忽略未知字段——老客户端遇到新字段可安全跳过。但删字段会让老客户端解析失败或空指针;改类型导致反序列化异常;改语义更隐蔽(同字段含义变了,客户端逻辑错误)。所以向前兼容容易,向后兼容难。
- 为什么 App 接口必须假设“老客户端永远存在”? App 发布后用户不一定升级。长尾版本可能存活数年。服务端必须同时服务多版本客户端,或用“最低支持版本”强制淘汰。这是移动端接口与内部 RPC 最大的不同——客户端集合不可控。
- 为什么 URL 版本更常用? 显式、可路由、可按版本独立限流与监控、缓存键清晰。Header 版本更“REST 纯粹”,但网关路由与缓存配置更绕。内部 API 可用 Header,对外 App 接口 URL 版本更直观。
- 为什么契约测试重要? 消费者驱动的契约测试(Pact 等)让提供者在 CI 阶段就知道“我这个改动会不会打破消费者期望”。没有契约时,兼容性靠人眼 review 与联调,容易漏。接口是团队间的社会契约,要像代码一样测试。
- 下线老版本的正确姿势? 不能“看着没人用了就删”。要:① 统计版本流量直至 0;② 通过 App 弹窗/接口报错强制升级;③ 提前公告;④ 保留一个发布周期的双跑;⑤ 真正删除。很多事故来自“以为没人用了”。
【选型判断树】
接口怎么改?
1. 变更类型
├─ 加可选字段/加接口 → 向后兼容,可直接上
├─ 加必填字段 → 新版本或默认值兼容
└─ 删字段/改类型/改语义 → 必须新版本
2. 版本化方式
├─ 对外 App → URL /v1 /v2
└─ 内部/REST 纯粹 → Header/参数
3. 共存
└─ 适配层/参数转换;老版本修 bug 不删
4. 下线
└─ 流量监控→强制升级→公告→双跑→删除
5. 防回归
└─ 契约测试 + 老版本用例进 CI判断口诀: 加字段可兼容,删改要版本化;先改 DB 再改代码;下线看流量不看感觉。
【口述骨架】(5 分钟)
| 时间 | 任务 | 内容 |
|---|---|---|
| 0:00–0:30 | 定性 | “移动端接口要假设老客户端永远存在” |
| 0:30–1:30 | 兼容性判断 | 加字段 OK;删改类型语义不 OK |
| 1:30–2:30 | 版本化 | URL vs Header;新旧共存 |
| 2:30–3:30 | 演进时序 | DB 加列→代码双支持→灰度→清理 |
| 3:30–4:30 | 下线 | 流量归零+强制升级+公告 |
| 4:30–5:00 | 收尾 | “契约测试把兼容性变成门禁” |
【关键数字】
| 参数 | 经验值 | 说明 |
|---|---|---|
| 版本共存期 | ≥6 个月或 2 个大版本 | 以流量为准 |
| 最低支持版本 | 按活跃用户分布定 | 强制升级阈值 |
| 序列化 | 忽略未知字段 | Jackson/Protobuf 默认或配置 |
| 契约测试 | 消费者驱动,CI 必跑 | 防破坏性变更 |
| 文档 | OpenAPI 同步 | 变更评审留痕 |
【追问链】(三层)
L1|“新版本上线老版本什么时候删?” → 以调用量监控为准:版本流量降到 0(或业务可接受的阈值)且过了强制升级窗口,再删除。不能按“发版日期+拍脑袋”。删除前保留双跑一个观察期。
L2|“字段类型改不了怎么办?” → 新版本用新字段名(如 amount 改 amount_cent 为整型分),老字段保留过渡,数据双写,客户端迁完再删旧字段。禁止原地改类型。
L3|“如何防止同事无意中改坏接口?” → ① 变更评审(接口治理委员会/架构师);② 契约测试进 CI;③ 消费者用例回归;④ 破坏性变更检查工具(与 OpenAPI diff)。把“人盯”升级为“机器拦”。
【评分标准】
| 档位 | 答案特征 |
|---|---|
| 60 分 | 知道“要兼容老版本,可以加字段” |
| 80 分 | 讲清兼容性判断、URL/Header 版本、共存与下线 |
| 95 分 | 移动端长尾;DB 先行时序;契约测试;类型迁移用新字段名;下线看流量 |
【关联题】
- 全链路灰度: 第 199 题(同样强调数据兼容)
- CI 门禁: 第 188 题
- 配置兼容: 第 180 题
- 强制升级: 第 130 题(登录与会话)
- 同簇: 第 195 题(JWT 与会话版本策略)
【自测】
- 为什么新增字段通常是兼容的? 参考答案:多数客户端序列化会忽略未知字段。删除/改类型/改语义才会破坏老客户端。
- 对外 App 接口推荐哪种版本化方式? 参考答案:URL 版本(/v1 /v2),显式、易路由、易监控。内部可用 Header 版本。
- 老版本接口如何安全下线? 参考答案:监控该版本调用量归零 → 强制升级策略 → 用户公告 → 双跑观察 → 再删除。
后端主库第 1–208 题 + 国企金融专题第 209–228 题。