# 2026-07-16 泰额版代码优化 本文档说明 **泰额版后端** 当日对 `tenant-select` 出参、默认管理员凭据加密及 Web 登录入参的改造。仅涉及泰额版 `Yi.Abp.Net8`,前端需同步对接。 --- ## 一、变更概述 | 项 | 说明 | |----|------| | 接口 | `GET /api/app/th-multi-tenancy/tenant-select` | | 目的 | 登录页展示租户列表时不再暴露租户 Id、数据库连接串;改为返回创建时间与可解密的默认管理员凭据 | | 登录 | `POST /api/app/th-web-auth/login` 支持 `tenantName` 替代 `tenantId` | | 配置 | 新增 `FoodLabeling:TenantSelectCrypto:SecretKey` | --- ## 二、tenant-select 出参变更 ### 2.1 新旧字段对比 | 旧字段 | 新字段 | 说明 | |--------|--------|------| | `id` | — | **已移除**,不再返回租户 Guid | | `name` | `name` | 保留,租户名称 | | — | `creationTime` | **新增**,租户创建时间(主库 `yitenant`) | | — | `loginAccount` | **新增**,默认管理员账号,固定为 `admin`(与 `UserDataSeed` 一致) | | — | `password` | **新增**,AES-CBC 加密后的 Base64 密文(**非**库内不可逆哈希) | | — | `passwordSalt` | **新增**,AES IV 的 Base64,前端解密时使用 | | `tenantConnectionString` / 连接串相关 | — | **从未在此接口返回**;仍不暴露 | ### 2.2 出参示例 ```json [ { "name": "Default", "creationTime": "2026-01-15T10:30:00", "loginAccount": "admin", "password": "xK3mP9vL2nQ8wR1sT4uV7yZ0aB5cD6eF8gH9iJ0kL1m=", "passwordSalt": "AbCdEfGhIjKlMnOpQrStUvWxYz0123==" } ] ``` > 每条记录的 `password` / `passwordSalt` 因随机 IV 而不同;明文来源均为 `RbacOptions.AdminPassword`(当前默认 `123456`)。 --- ## 三、密码加盐加密与前端解密 ### 3.1 算法约定 | 项 | 值 | |----|-----| | 算法 | AES-256-CBC | | 填充 | PKCS7 | | Key | `SHA256(UTF8(SecretKey))` → 32 字节 | | IV | 每次加密随机 16 字节,Base64 写入 `passwordSalt` | | 密文 | Base64 写入 `password` | | 明文 | `RbacOptions.AdminPassword`(appsettings,与种子管理员一致) | ### 3.2 后端配置 `appsettings.json`: ```json "FoodLabeling": { "TenantSelectCrypto": { "SecretKey": "FoodLabelingThTenantSelect2026Key!" } } ``` - `SecretKey` 为前后端约定的字符串,**不要求**本身为 32 字节;后端用 SHA256 派生 AES-256 密钥。 - 生产环境请更换为足够长度的随机字符串,并与前端构建配置保持一致。 ### 3.3 前端解密(CryptoJS 示例) ```javascript import CryptoJS from 'crypto-js'; const SECRET_KEY = 'FoodLabelingThTenantSelect2026Key!'; // 与后端 TenantSelectCrypto:SecretKey 一致 function decryptTenantPassword(passwordBase64, passwordSaltBase64) { const key = CryptoJS.SHA256(CryptoJS.enc.Utf8.parse(SECRET_KEY)); const iv = CryptoJS.enc.Base64.parse(passwordSaltBase64); const cipher = CryptoJS.enc.Base64.parse(passwordBase64); const decrypted = CryptoJS.AES.decrypt( { ciphertext: cipher }, key, { iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }, ); return decrypted.toString(CryptoJS.enc.Utf8); } ``` ### 3.4 前端解密(Web Crypto 伪代码) ```javascript async function decryptTenantPassword(passwordBase64, passwordSaltBase64) { const secretKey = 'FoodLabelingThTenantSelect2026Key!'; const keyMaterial = await crypto.subtle.importKey( 'raw', await crypto.subtle.digest('SHA-256', new TextEncoder().encode(secretKey)), { name: 'AES-CBC' }, false, ['decrypt'], ); const iv = Uint8Array.from(atob(passwordSaltBase64), (c) => c.charCodeAt(0)); const cipher = Uint8Array.from(atob(passwordBase64), (c) => c.charCodeAt(0)); const plain = await crypto.subtle.decrypt({ name: 'AES-CBC', iv }, keyMaterial, cipher); return new TextDecoder().decode(plain); } ``` ### 3.5 安全说明 - 接口为 **匿名可访问**,密文仅降低明文密码在传输/展示中的直接暴露风险,**不能**替代 HTTPS 与生产环境改密策略。 - 解密后的密码仅用于登录表单预填或一键登录;请勿持久化明文密码。 --- ## 四、Web 登录:tenantName 替代 tenantId `tenant-select` 不再返回 `id` 后,Web 登录须使用 **租户名称**。 ### 4.1 入参变更 | 字段 | 必填 | 说明 | |------|------|------| | `tenantId` | 否 | 与 `tenantName` 二选一 | | `tenantName` | 否 | 与 `tenantId` 二选一,推荐 | | `userName` | 是 | 账号或邮箱;`admin` 或 `admin@example.com` 均可 | | `password` | 是 | 明文密码 | 二者均为空时返回:`请指定租户 Id 或租户名称`。 ### 4.2 登录示例(curl) ```bash curl -X POST "http://127.0.0.1:19002/api/app/th-web-auth/login" \ -H "Content-Type: application/json" \ -d "{\"tenantName\":\"Default\",\"userName\":\"admin\",\"password\":\"123456\"}" ``` ### 4.3 出参(不变) ```json { "token": "eyJhbGciOiJIUzI1NiIs...", "refreshToken": "...", "tenantId": "11111111-1111-1111-1111-111111111111", "tenantName": "Default" } ``` Token 内仍写入真实 `TenantId` Guid;后续业务 API 行为不变。 --- ## 五、泰额 App 登录说明 `POST /api/app/th-app-auth/login` **本次未改**,仍要求入参 `tenantId`(Guid)。 若 App 端也需要去掉 tenantId,需单独改造 `ThAppLoginInputVo` 与 `ThAppAuthAppService`,并与移动端联调。 --- ## 六、相关接口(当日其它优化,可选) - **开通租户** `POST /api/app/th-tenant-provisioning/provision`:创建租户后可在后台异步初始化业务库(`DatabaseInitializing` / `InitializeTenantDatabase`)。 - **同步初始化** `POST /api/app/th-tenant-provisioning/initialize-tenant-database/{tenantId}`:补跑租户库 CodeFirst + Seed。 与本次 `tenant-select` 改造无直接冲突;平台管理员仍通过 provision 创建租户,登录页通过 `tenant-select` + `tenantName` 登录。 --- ## 七、改动文件列表(后端) | 文件 | 变更 | |------|------| | `FoodLabeling.Th.Application.Contracts/Dtos/MultiTenancy/ThTenantSelectDto.cs` | 出参字段调整 | | `FoodLabeling.Th.Application.Contracts/Options/FoodLabelingThTenantSelectCryptoOptions.cs` | 新增加密配置 | | `FoodLabeling.Th.Application/MultiTenancy/TenantSelectCredentialCipher.cs` | AES-CBC 加密 | | `FoodLabeling.Th.Application/MultiTenancy/ThTenantSelectConsts.cs` | 默认 admin 账号常量 | | `FoodLabeling.Th.Application/MultiTenancy/TenantResolveHelper.cs` | 支持 tenantName 解析 | | `FoodLabeling.Th.Application/Services/ThMultiTenancyAppService.cs` | tenant-select 映射与加密 | | `FoodLabeling.Th.Application.Contracts/Dtos/Auth/ThWebLoginInputVo.cs` | TenantId 可选 + TenantName | | `FoodLabeling.Th.Application/Services/ThWebAuthAppService.cs` | 按名称登录 | | `FoodLabeling.Th.Application/FoodLabelingThApplicationModule.cs` | 注册加密配置 | | `src/Yi.Abp.Web/appsettings.json` | TenantSelectCrypto 节点 | --- ## 八、前端对接 checklist - [ ] 更新 `ThTenantSelectDto` 类型,移除 `id`,增加 `creationTime` / `loginAccount` / `password` / `passwordSalt` - [ ] 配置与后端一致的 `SecretKey`,解密 `password` 后填入登录表单(若需) - [ ] 登录请求改传 `tenantName: selected.name`,不再依赖 `tenantId` - [ ] 登录成功后仍保存响应中的 `tenantId` 供业务 API / `__tenant` 头使用