
Multi Tenant Safety
当代码涉及多租户隔离(TenantContext、tenantId、租户拦截器/过滤器、X-Tenant-Code)时触发。防止租户越权访问、数据串租户等安全问题。
Install
npx skills add doccker/cc-use-exp --skill multi-tenant-safety -g -a claude-code -y- Repository
- doccker/cc-use-exp1.1k
- Category
- Engineering
- Updated
- 2mo ago
- License
- See repository
- Context cost
- Mediumabout 2,338 tokens
- Runs scripts
- No, instructions only
What it does
当代码涉及多租户隔离(TenantContext、tenantId、租户拦截器/过滤器、X-Tenant-Code)时触发。防止租户越权访问、数据串租户等安全问题。
多租户隔离安全规范
当系统涉及多租户架构时,防止租户间数据越权访问。
陷阱 #1: 租户上下文来源信任错误
场景: 拦截器/过滤器从请求头(如 X-Tenant-Code)设置租户上下文,但未与认证 token 中的 tenantId 做一致性校验
问题根因
请求头可以被客户端任意伪造。如果后端只信任请求头中的租户标识,攻击者只需修改 header 就能访问其他租户的数据。
错误示例
// ❌ 错误: 只信任请求头,未校验 token
@Override
public boolean preHandle(HttpServletRequest request, ...) {
String tenantCode = request.getHeader("X-Tenant-Code");
TenantMiniAppConfig config = configRepository.findByTenantCode(tenantCode);
TenantContext.setTenantId(config.getTenantId()); // 直接信任 header
return true;
}
// 攻击者拿着 tenantId=1 的 token,配上 X-Tenant-Code: OTHER_TENANT
// 就能读到其他租户的数据
正确做法
// ✅ 正确: header 只做路由定位,必须与 token tenantId 校验一致
@Override
public boolean preHandle(HttpServletRequest request, ...) {
String tenantCode = request.getHeader("X-Tenant-Code");
TenantMiniAppConfig config = configRepository.findByTenantCode(tenantCode);
// 从认证 token 中取出 tenantId(真相源)
Long tokenTenantId = (Long) request.getAttribute("tokenTenantId");
if (tokenTenantId != null && !tokenTenantId.equals(config.getTenantId())) {
response.setStatus(403);
response.getWriter().write("{\"code\":403,\"message\":\"租户信息不匹配\"}");
return false;
}
TenantContext.setTenantId(config.getTenantId());
return true;
}
检查清单
- 租户上下文的最终来源是否以认证 token 为准
- 请求头中的租户标识是否只用于路由定位,而非直接信任
- token 中的 tenantId 与请求头租户是否做了一致性校验
- 校验不通过时是否返回 403 而非静默放行
陷阱 #1.5: Service 层调用 Repository.findById(id) 缺租户过滤
与陷阱 #2 并行:陷阱 #2 是「全局过滤兜底」,本陷阱是「显式深度防御」。即使项目已用 Hibernate
@Filter/ MyBatis 拦截器,仍建议 Service 层显式调用findByTenantIdAndId, , 主键直查在二级缓存命中、getReferenceById等路径上常常绕过全局过滤。两层一起用,可读性也更好(看 Service 代码就知道隔离了 tenantId)。
场景: 即使有了全局过滤机制,开发者在 Service 里直接 repo.findById(id) 仍可能绕过过滤, , 主键查询常常被 JPA/Hibernate 当成"按 ID 直查",跳过 entity filter
