知

模块四 · 工程与运维

第 8 / 11 节20 分钟阅读型

认证、缓存与测试:把接口做成产品

能跑通不等于能上线。这一节讲清认证授权的边界、两级缓存的用法,以及单元/集成测试怎么写才有价值。

读完这一节,你能做到

  1. 区分认证与授权,选对 JWT、Cookie 与 Policy。
  2. 用 IMemoryCache 与 Redis 做两级缓存,并处理失效。
  3. 搭出单元测试与集成测试的分层。
  4. 用 WebApplicationFactory 写一个端到端接口测试。

一、认证与授权

认证解决"你是谁",授权解决"你能做什么"。

方式特点适用
JWT无状态,适合 API 与多端前后端分离
Cookie浏览器自动携带,易受 CSRF传统 Web
OAuth/OIDC第三方登录、单点登录企业应用

ASP.NET Core 的授权策略:

builder.Services.AddAuthorization(o =>
{
    o.AddPolicy("admin", p => p.RequireRole("Admin").RequireClaim("scope", "api"));
});

注意:JWT 的过期时间要短,配合刷新令牌;签名密钥不能硬编码;[Authorize] 只是入口,资源级权限仍要在业务里校验。

二、缓存的两级

层级实现特点
本地IMemoryCache最快,进程内,容量有限
分布式Redis跨实例共享,网络开销

常见模式:先查本地,再查 Redis,最后查库并回填;失效时先删缓存再更新库,或订阅变更事件。

三个问题:

  • 穿透:查不存在的数据,缓存空值或布隆过滤器。
  • 击穿:热点 key 过期,加互斥锁或逻辑过期。
  • 雪崩:大量 key 同时过期,加随机 TTL。

输出缓存(Output Caching)适合整页/整接口缓存,注意按用户区分的接口不能全局缓存。

三、测试金字塔

单元测试(多)→ 集成测试(中)→ 端到端(少)
  • 单元测试:测纯逻辑,不碰数据库与网络,快。
  • 集成测试:测管道、数据库、序列化,慢但真实。
  • 端到端:测关键路径,最慢,数量最少。

判断标准:一个测试是否值得写,看它能不能在重构时给你信心;只测 getter/setter 没有意义。

四、集成测试

public class ApiTests : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly HttpClient _client;
    public ApiTests(WebApplicationFactory<Program> factory)
        => _client = factory.CreateClient();

    [Fact]
    public async Task Get_health_returns_ok()
    {
        var res = await _client.GetAsync("/health");
        res.EnsureSuccessStatusCode();
    }
}

要点:

  • 用测试专用数据库(内存库或容器),避免污染开发库。
  • 每个测试独立数据,避免顺序依赖。
  • 外部依赖用 mock 或 WireMock 替代。

五、质量闸门

  • 覆盖率不是目标,关键路径覆盖才有意义。
  • 静态分析(nullable、分析器)能提前发现空引用与资源泄漏。
  • CI 里跑构建、测试、格式检查,失败即阻断。
  • 性能关键路径加基准测试,防止回归。

六、常见坑

  1. JWT 密钥写进配置文件并提交。
  2. 只做认证不做资源级授权,越权访问。
  3. 缓存无 TTL,数据永久不更新。
  4. 集成测试共用数据库,互相污染。
  5. 只写单元测试,管道问题到线上才发现。

七、动手任务:自写 → AI 写 → 对比 → 取舍

四步不跳:① 自己写(限时,关掉补全)→ ② 让 AI 写同一题 → ③ 逐条比对 → ④ 说出保留谁的写法。 完整动作与记录模板见《AI 协作与代码评审》。

① 自己写

  1. 配一个 JWT 认证 + 角色策略,写一个越权用例验证。
  2. 用 IMemoryCache + Redis 做两级缓存,模拟击穿。
  3. 用 WebApplicationFactory 写三个接口测试:成功、参数错误、未授权。

② 让 AI 写同一题

把第 2 题(认证 + 缓存)交给 AI,补一句 说明 token 校验失败的分支与缓存失效策略。

③ 对比要盯的点

  • JWT 校验它验了签名、过期与受众吗?只验签名是不够的。
  • 缓存失效是删 key 还是设过期?删 key 时有没有处理并发击穿。
  • 测试它用了 WebApplicationFactory 还是自己造 mock?前者的可信度高得多。

④ 一句话取舍

token 过期与缓存同时失效的那一秒,它那版会怎样?

八、自测

  1. 认证与授权的区别?
  2. JWT 与 Cookie 的适用场景?
  3. 穿透、击穿、雪崩的方案?
  4. 测试金字塔的三层?
  5. 集成测试如何隔离数据?

参考答案要点:身份 vs 权限;无状态 API vs 浏览器;空值/互斥/随机 TTL;单元/集成/端到端;独立库或事务回滚。

附录:高频追问

问:JWT 能主动失效吗? 无状态令牌本身不能,需要黑名单、短过期 + 刷新令牌,或版本号。

问:Token 放哪最安全? HttpOnly Cookie 防 XSS,但要处理 CSRF;localStorage 简单但易被 XSS 读取。

问:Policy 与 Role 的区别? Role 是粗粒度,Policy 可以组合 Claim、角色与自定义要求。

问:缓存一致性怎么保证? 先更新库再删缓存,接受短暂不一致;强一致场景用版本号或锁。

问:IMemoryCache 会过期吗? 可以设绝对/滑动过期;进程重启即丢失。

问:Redis 挂了怎么办? 降级到本地缓存或直接查库,并加限流保护。

问:单元测试要 mock 数据库吗? 纯逻辑不需要;涉及查询翻译的用集成测试更真实。

问:集成测试启动慢怎么办? 复用工厂、共享容器、只测关键路径。

问:覆盖率多少才够? 没有统一标准,核心模块高、边缘代码低;关注未覆盖的分支。

问:如何做接口契约测试? 用 OpenAPI 生成客户端或快照测试,保证前后端契约一致。

附:缓存的边界与降级

缓存不是数据库:强一致场景不能只靠缓存。Redis 故障时要有降级路径:本地缓存、直接查库、限流保护。

缓存键要可观测:把业务前缀、版本号、租户放进去,方便排查与清理。TTL 要有随机抖动,避免同一时刻集体失效。

测试数据的隔离

三种做法:每个测试用独立数据库/容器、每个测试用事务回滚、用唯一前缀生成数据。

避免共享开发库:测试之间会互相影响,失败难以复现。集成测试宁愿慢一点,也要可重复。

补充:JWT 的刷新与撤销

  • 访问令牌短期(几分钟到几小时),刷新令牌长期。
  • 刷新令牌要能撤销:存服务端或版本号。
  • 令牌里不要放敏感信息,它只是签名不是加密。
  • 时钟偏移要留余量,否则会出现“刚发就过期”。

补充:授权边界

[Authorize] 只证明“登录了”,不证明“能操作这条数据”。资源级校验必须在业务里做:订单属于当前用户、租户隔离、角色与数据范围。

每个受保护接口至少写一个越权用例,否则权限漏洞很难被发现。

安全验收

至少验证:无令牌返回 401、越权返回 403、过期令牌失效、缓存不泄露他人数据。不要只认证不授权;不要发长期有效令牌;不要缓存敏感数据。

一个判断

接口结果是否因人而异?是,就绝不能做全局输出缓存,要按用户/租户隔离缓存键。

验收

无令牌 401、越权 403、过期失效、缓存不串数据,四项都验过才算完成。先验越权,再上线;缓存键隔离用户。先验越权。

补遗:OAuth2 / OIDC

四个角色:资源所有者、客户端、授权服务器、资源服务器。常见流程:授权码 + PKCE(Web/移动端)、客户端凭证(服务间)。OIDC 在 OAuth2 上加了身份层(ID Token)。

不要用密码模式(已废弃);回调地址要白名单;state/nonce 防 CSRF 与重放。

补遗:常见攻击

SQL 注入(参数化)、XSS(输出编码、CSP)、CSRF(SameSite、Anti-Forgery Token)、越权(资源级校验)、开放重定向(白名单)、敏感数据泄露(加密、脱敏、日志过滤)。

补遗:xUnit、Moq 与 Testcontainers

xUnit 用 [Fact]/[Theory];Moq 模拟接口;Testcontainers 启动真实数据库/Redis 做集成测试,避免“内存库通过、线上失败”。

测试要独立、可重复:每个测试自建数据,外部依赖用容器或 WireMock。

配套题目

★ 越多越该先背;点题目可看完整答案与深入版。

提示:读完可以顺手做几道对应题目,检验是否真的理解