Skip to content
bitzorcas
中EN

Reference

预置演示账号与口令注入契约:零信任种子与多租户隔离

深度解构 BitzOrcas.Modern 预置种子账号体系:Fail-Closed 环境变量口令注入契约、RSA-OAEP 传输加密防重放、以及多租户 404 防资源探测隔离机制。

Last updated

在企业级系统与 SaaS 平台的演进历史中,因“预置默认账号与弱口令”导致的生产安全事故屡见不鲜。大量开源框架为了追求“开箱即用”,在数据库种子脚本中硬编码诸如 admin / 123456 的静态凭据;当这些系统被部署到测试网甚至公网生产环境时,极易因运维遗漏而沦为供应链攻击与未授权横向移动的突破口。

BitzOrcas.Modern 贯彻“默认安全(Secure by Default)与零信任(Zero-Trust)”设计哲学。种子数据体系在源码层面严禁存储任何明文口令,全面实施 Fail-Closed(失败即闭合)环境变量注入契约,结合客户端 RSA-OAEP 非对称传输加密与多租户 404 防探测机制,构建银行级的身份治理基线。

账号拓扑与安全注入架构

系统将数据资产严格划分为独立隔离的租户数据面与宿主研发运维面:

Fail-Closed 口令安全注入管道

环境变量 USER____PASSWORD
(映射为 .NET USER::PASSWORD)

BCrypt 密码哈希器 (WorkFactor = 11)

SQL Server 用户凭据表

Demo Tenant 业务租户 (TenantId = 1000001, 律所多租户场景)

admin
租户主管理员 (tenant-admin)

auditor
合规审查员 (只读合规审查)

operator / support
日常业务操作与支持

Host 宿主分区 (TenantId = 0, 平台运维与后台作业面)

host-admin
平台超管角色

host-ops / host-developer
运维与诊断角色


特权超级管理员:admin / Admin@2026

内置超管种子(BuiltinIdentitySuperAdminSeedStep)专为本地快速研发联调交付。该步骤仅在开发环境且租户 1000001 下初始化特权账户:

  • 用户名:admin
  • 默认开发口令:Admin@2026
  • 绑定角色:SuperAdmin(自动汇总平台所有模块治理权限,含 RootCrossTenant 与 TenantDataScope)。

生产环境覆盖原则

在生产环境中,该步骤严禁使用默认弱口令。系统按以下优先级依次解析超管口令:

Identity:SuperAdmin:Password 配置键 > Parameters:demo-user-password > USER__ADMIN__PASSWORD 环境变量 > 抛出异常中断

若检测到运行环境为 Production 且未提供任何覆盖口令,种子初始化将直接抛出安全异常并中止进程,防止未受保护的管理员账号暴露。


演示账号矩阵:Fail-Closed 环境变量注入契约

除上述特权超管外,所有租户业务账号与 Host 运维账号在种子 CSV 资产中均只登记环境引用占位符(例如 from-env:USER__OPERATOR__PASSWORD)。

1. 注入规范与分层映射

解析器支持明文口令自动哈希与预先计算的哈希值直接落盘两种形态:

环境变量命名载荷内容运行时处理逻辑
USER__<NAME>__PASSWORD明文密码字符串种子引擎在落库前调用 IPasswordHasher(BCrypt,成本因子 11)进行哈希运算后写入
USER__<NAME>__PASSWORD_HASH已计算的 BCrypt 摘要种子引擎跳过计算,直接将该摘要作为密码散列值持久化

2. 注入示例与重置机制

在拉起种子前,通过终端注入各账号口令:

批量注入演示账号口令 (Bash 示例)
# 为租户 1000001 业务角色注入口令
export USER__OPERATOR__PASSWORD='Operator@2026'
export USER__SUPPORT__PASSWORD='Support@2026'
export USER__AUDITOR__PASSWORD='Auditor@2026'
# 为 Host 运维角色注入口令
export USER__HOST_ADMIN__PASSWORD='HostAdmin@2026'
export USER__HOST_OPS__PASSWORD='HostOps@2026'
  • 缺少变量即中断(Fail-Closed):若种子引擎检测到某账号缺少对应的环境变量,将立即抛出 InvalidOperationException 并打印缺失的键名,绝不静默填充默认口令;
  • 增量重置口令:默认情况下,已存在的用户账号在重新运行种子时将保留原数据库中的口令;若需要强制将口令刷新为本次注入的新值,需显式声明:
    Terminal window
    export BITZORCAS_RESET_DEMO_PASSWORDS=true

预置账号与业务角色清单

租户数据面:Demo Tenant(TenantId = 1000001)

账号绑定角色业务领域定位与数据访问边界
admintenant-admin + root-admin租户级最高管理员,拥有该租户内全量资源与席位管理权限
operatoroperator核心业务操作员(如 LegalTech 案件立案、合同发起、计费充值)
supportsupport-agent一线支持与客服人员,负责工单流转与客户会话支持
auditorauditor内部合规与审计人员,仅拥有跨业务只读查询与操作审计日志查看权限
developerdeveloper沙箱调试人员,拥有 OpenAPI 交互与开发诊断能力

宿主运维面:Host 分区(TenantId = 0)

Host 分区专用于系统基础设施运维与租户生命周期管理,不承载任何具体租户的业务数据:

  • host-admin:平台总体运营超管;
  • host-ops:基础设施巡检、数据库备份与中间件健康探针维护人员;
  • host-developer:平台底层插件与模块治理研发人员。

登录交互协议:RSA-OAEP 传输加密与防重放

BitzOrcas 拒绝在网络传输中暴露明文密码。客户端在调用登录前,必须先获取临时非对称公钥并组装带时戳的防重放载荷:

"用户持久化表""IPasswordCipherService""Minimal API (/api/auth)""用户持久化表""IPasswordCipherService""Minimal API (/api/auth)"客户端本地组装载荷: $"{nonce}|{clientTimestamp}|{rawPassword}"使用公钥执行 RSA-OAEP-SHA256 加密并 Base64 编码"前端 SPA / 客户端"1. GET /api/auth/cipher-key (获取临时加密公钥)12. 返回 RSA-OAEP-SHA256 公钥、keyId 与服务端时间戳 (serverTime)23. POST /api/auth/login{ userName, encryptedPassword, cipherKeyId }34. 提交解密与时钟有效性校验45. 私钥解密,校验时戳漂移 (<= 300s) 与 nonce 防重放56. 还原出原始明文口令67. BCrypt 校验密码哈希并颁发 JWT 凭据78. 响应 200 OK 颁发 TokenResponse8"前端 SPA / 客户端"

C# 端到端集成测试验证代码

以下测试代码摘录并改编自框架自动化回归套件,展示如何在集成测试中完整模拟从公钥拉取、密码加密到租户隔离验证的全过程:

using System.Net;
using System.Net.Http.Json;
using System.Security.Cryptography;
using System.Text;
using BitzOrcas.Identity.Contracts.Identity.Dtos;
using Shouldly;
using Xunit;
public sealed class AuthenticationAndTenancyIntegrationTests : IClassFixture<CustomWebApplicationFactory>
{
private readonly HttpClient _client;
public AuthenticationAndTenancyIntegrationTests(CustomWebApplicationFactory factory)
{
_client = factory.CreateClient();
}
[Fact]
public async Task Login_WithRsaOaepEncryptedPassword_ShouldSucceedAndIssueJwt()
{
// 1. 获取服务端当前活跃的 RSA-OAEP 加密公钥与密钥标识符
var keyResponse = await _client.GetFromJsonAsync<CipherPublicKeyResponse>("/api/auth/cipher-key");
keyResponse.ShouldNotBeNull();
keyResponse.PublicKeyPem.ShouldNotBeNullOrWhiteSpace();
// 2. 组装具备防重放特性的载荷:nonce|timestamp|password
var nonce = Guid.NewGuid().ToString("N");
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var rawPayload = $"{nonce}|{timestamp}|Admin@2026";
// 3. 使用服务端下发的 RSA 公钥执行 OAEP-SHA256 密文加密
using var rsa = RSA.Create();
rsa.ImportFromPem(keyResponse.PublicKeyPem);
var encryptedBytes = rsa.Encrypt(Encoding.UTF8.GetBytes(rawPayload), RSAEncryptionPadding.OaepSHA256);
var base64EncryptedPassword = Convert.ToBase64String(encryptedBytes);
// 4. 发起登录请求
var loginRequest = new LoginRequest(
UserName: "admin",
EncryptedPassword: base64EncryptedPassword,
CipherKeyId: keyResponse.KeyId);
var loginResponse = await _client.PostAsJsonAsync("/api/auth/login", loginRequest);
// 5. 机器断言:验证返回 200 OK 并成功颁发 JWT 访问令牌
loginResponse.StatusCode.ShouldBe(HttpStatusCode.OK);
var tokenResult = await loginResponse.Content.ReadFromJsonAsync<TokenResponse>();
tokenResult.ShouldNotBeNull();
tokenResult.AccessToken.ShouldNotBeNullOrWhiteSpace();
}
}

多租户隔离断言:为何返回 404 而不是 403

在多租户系统设计中,一个极其隐蔽却严重的漏洞是资源 ID 枚举探测(Resource ID Enumeration Probing)。

若租户 B 试图访问租户 A 拥有的案件编号 MATTER-10029:

  • 错误做法(返回 403 Forbidden):系统告知客户端“你无权访问该资源”。这无意中向攻击者泄露了一个致命情报:该资源 ID 在系统中是真实存在的,攻击者可以通过高频遍历 ID 来推算业务总量或进行针对性社会工程学攻击。
  • BitzOrcas 架构做法(返回 404 Not Found):在持久化层(SqlSugar / EF Core),框架内置的全局租户查询过滤器(Global Tenant Filter)强制注入 TenantId = CurrentTenant.Id 条件。因此,跨租户查询在 SQL 层面直接返回 0 条记录,业务仓储将其判定为“资源不存在”,对外统一响应 404 Not Found。
"数据库""全局租户隔离过滤器""API 宿主""数据库""全局租户隔离过滤器""API 宿主""恶意调用方 (租户 1000002)"GET /api/cases/MATTER-10029 (此为租户 1000001 的核心资产)1查询条件: Id = 'MATTER-10029'2强制改写 SQL: WHERE Id = 'MATTER-10029' AND TenantId = 10000023命中 0 行数据4实体未找到 (EntityNotFound)5HTTP 404 Not Found (彻底杜绝资源 ID 存在性泄露)6"恶意调用方 (租户 1000002)"

通过这一物理层面的机制闭环,BitzOrcas 确保了无论是演示数据还是生产数据,多租户边界始终如铜墙铁壁般严密可信。

100%

滚轮或按钮缩放 · 放大后拖动画面 · 双击切换 100% / 200%