白盒测试实战:从代码覆盖到缺陷挖掘的完整指南

W″子萱 安全 阅读 542
赞 12 收藏
二维码
手机扫码查看
反馈

为什么我们决定搞白盒测试

去年底接手一个内部管理后台重构项目,前端用的是 React + TypeScript,后端是 Node.js。这个系统权限控制特别复杂,角色、部门、资源粒度都很细,而且很多逻辑是动态生成的。上线前 QA 测出一堆权限越界的问题——比如普通用户居然能删管理员的数据。老板脸色不太好看,说“这要是线上被利用了,后果很严重”。

白盒测试实战:从代码覆盖到缺陷挖掘的完整指南

一开始我们只靠黑盒测试(点点点)和 E2E,但覆盖率太低。后来我提议加白盒测试,把核心逻辑单元测起来。团队里有人质疑:“前端测这些有意义吗?不都是后端该干的?” 但我想,既然权限判断在前端也做了(为了体验),那至少得保证逻辑别写错。于是硬着头皮上了。

核心代码就这几行,但坑不少

我们抽出了一个 canAccess 函数,专门判断当前用户是否有权限操作某个资源。逻辑大概是:根据用户角色、资源类型、操作类型,查一张权限映射表,再结合一些业务规则(比如“只能操作自己部门的数据”)。这个函数成了整个系统的安全阀门。

白盒测试的目标就是覆盖这个函数的所有分支。一开始我写了几个简单 case,比如:

// 权限判断函数简化版
function canAccess(user, resource, action) {
  if (user.role === 'admin') return true;
  if (resource.ownerId !== user.id) return false;
  if (action === 'delete' && user.role !== 'manager') return false;
  return true;
}

测试用 Jest 写,像这样:

test('普通用户不能删除他人数据', () => {
  const user = { id: 1, role: 'user' };
  const resource = { ownerId: 2 };
  expect(canAccess(user, resource, 'delete')).toBe(false);
});

看起来挺顺,但很快发现不对劲——实际项目里的 canAccess 有 300 多行,嵌套 if/else 超过 5 层,还有异步校验(比如要查远程 API 确认部门关系)。直接测根本没法覆盖所有路径。

最大的坑:异步权限校验怎么测

最头疼的是,有些权限依赖后端接口。比如判断“是否属于同一部门”,前端需要调 /api/check-dept。这种情况下,白盒测试就卡住了:你总不能每次跑测试都真去请求后端吧?

开始我用 jest.mock 模拟 fetch,但 mock 写得太死板,改一次权限逻辑就要改 mock,维护成本爆炸。后来折腾了半天,想到一个笨办法:把异步逻辑抽成独立函数,测试时传入 mock 实现。

// 抽离异步校验
async function checkDept(user, resource) {
  const res = await fetch(https://jztheme.com/api/check-dept?userId=${user.id}&resourceId=${resource.id});
  return res.json();
}

// 主函数接收校验器作为参数
function canAccess(user, resource, action, deptChecker = checkDept) {
  // ...同步逻辑
  if (someCondition) {
    return deptChecker(user, resource);
  }
  // ...
}

测试时就可以这样:

test('跨部门用户无访问权', async () => {
  const mockChecker = jest.fn().mockResolvedValue(false);
  const result = await canAccess(
    { id: 1 }, 
    { id: 100 }, 
    'view',
    mockChecker
  );
  expect(result).toBe(false);
  expect(mockChecker).toHaveBeenCalledWith({ id: 1 }, { id: 100 });
});

这招亲测有效,但有个副作用:主函数签名变丑了,多了个可选参数。不过为了可测性,忍了。

覆盖率数字好看,但心里发虚

用 Istanbul 跑完测试,报告显示分支覆盖率 92%。看起来不错,但我知道有水分——那些异步校验的错误处理分支(比如网络超时)根本没测。因为模拟网络错误太麻烦,而且业务上认为“失败时默认拒绝”就行,所以偷懒跳过了。

另外,权限映射表是从后端动态加载的 JSON,测试时用的是静态 fixture。有一次后端改了字段名,前端没更新,测试全过,但线上直接崩了。后来加了个 schema 校验,但也没自动化集成到测试流程里,算是留了个小尾巴。

所以说,白盒测试不是万能的。它能保证“你写的逻辑没错”,但防不住“你理解错了需求”或者“依赖变了”。这点一定要清楚。

回头看看,值不值

折腾了两周,加了 80 多个单元测试,CI 里跑一遍只要 3 秒。上线后权限相关 bug 归零了——至少没再出现越权问题。从结果看,投入是值得的。

做得好的地方:

  • 把核心安全逻辑集中到一个函数,方便测试
  • 异步依赖通过参数注入,解耦了测试
  • 关键路径(如 delete、edit)全覆盖

还能优化的:

  • 错误处理分支没覆盖,应该用 jest 的 mockRejectedValue 补上
  • 动态权限表应该做快照测试,避免结构变更导致静默失败
  • 现在测试用例还是手写的,其实可以用 property-based testing 自动生成边界 case

不过说实话,第三个优化可能永远不会做——工期紧,人力少,能跑通就不错了。现实项目就是这样,没有银弹,只有权衡。

给想尝试的同学几点提醒

如果你也在搞前端安全相关的逻辑,我的建议是:

  • 先识别核心安全函数:别一上来就测整个组件,找到那个“一旦出错就完蛋”的函数,集中火力
  • 异步依赖必须可替换:不管是 API 还是 localStorage,都要能 mock,否则测试就是摆设
  • 别迷信覆盖率:90% 覆盖率可能只是 if 分支跑了,但业务语义完全错
  • 和后端对齐测试策略:前端白盒不能替代后端鉴权,但可以减少低级错误

以上是我踩坑后的总结,希望对你有帮助。如果你们有更好的方案(比如怎么优雅处理动态权限表),欢迎评论区交流。这种安全相关的东西,多个人讨论总比一个人瞎琢磨强。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论