Files
STP/docs/architecture/详细设计/登录模块.md
T

412 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 登录模块
## 1. 模块概述
### 1.1 设计原则
| 原则 | 说明 |
| -------- | ----------------------------- |
| **安全优先** | 传输全程加密,防暴力破解。 |
| **角色分流** | 通过“内部员工登录”开关明确区分认证路由,杜绝权限越界。 |
| **存量兼容** | 无缝兼容 SRM 存量供应商账号,降低用户迁移成本。 |
| **体验极简** | 支持账号/手机双因子登录,并提供“记住我”与智能异常提示。 |
### 1.2 角色与认证路由映射
| 用户类型 | 开关状态 | 认证目标 | 说明 |
| ---------- | --------- | -------------------- | ------------------------------------------------- |
| **集团内部员工** | 开启(员工模式) | IAM(集团统一身份认证) | 跳转至 IAM 登录页或调用 OAuth2/OIDC 接口,不校验平台本地密码。 |
| **外部供应商** | 关闭(供应商模式) | STP 本地用户表 / SRM 回退认证 | 优先校验 STP 本地账号;若不存在,自动路由至 SRM 验证(满足 SRM 存量用户无感登录)。 |
## 2. 页面布局与 UI 设计
### 2.1 整体布局
```svg
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 800" font-family="'Microsoft YaHei', 'PingFang SC', sans-serif">
<defs>
<linearGradient id="bgGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#f8f9fc"/>
<stop offset="100%" stop-color="#e9ecef"/>
</linearGradient>
<linearGradient id="brandGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#c2185b"/>
<stop offset="100%" stop-color="#e91e63"/>
</linearGradient>
<filter id="cardShadow" x="-5%" y="-5%" width="110%" height="110%">
<feDropShadow dx="0" dy="10" stdDeviation="20" flood-color="#000" flood-opacity="0.08"/>
</filter>
<filter id="inputShadow" x="-2%" y="-2%" width="104%" height="104%">
<feDropShadow dx="0" dy="2" stdDeviation="4" flood-color="#c2185b" flood-opacity="0.1"/>
</filter>
</defs>
<!-- 背景 -->
<rect width="1200" height="800" fill="url(#bgGrad)"/>
<!-- 左侧品牌区域 -->
<rect x="0" y="0" width="600" height="800" fill="url(#brandGrad)"/>
<circle cx="300" cy="350" r="160" fill="rgba(255,255,255,0.08)"/>
<circle cx="300" cy="350" r="100" fill="rgba(255,255,255,0.12)"/>
<text x="300" y="240" text-anchor="middle" font-size="48" font-weight="bold" fill="#fff" letter-spacing="4">智慧招标平台</text>
<text x="300" y="290" text-anchor="middle" font-size="16" fill="rgba(255,255,255,0.8)" letter-spacing="2">Smart Tendering Platform</text>
<!-- 左侧装饰性图标(简化招投标场景) -->
<g transform="translate(300, 420)" stroke="rgba(255,255,255,0.3)" stroke-width="2" fill="none">
<rect x="-80" y="-60" width="160" height="120" rx="8"/>
<line x1="-40" y1="-30" x2="40" y2="-30"/>
<line x1="-40" y1="-10" x2="30" y2="-10"/>
<line x1="-40" y1="10" x2="20" y2="10"/>
<rect x="-30" y="30" width="60" height="20" rx="3" fill="rgba(255,255,255,0.2)"/>
</g>
<text x="300" y="540" text-anchor="middle" font-size="14" fill="rgba(255,255,255,0.7)">AI 原生 · 全流程线上闭环 · 主动式智能</text>
<text x="300" y="570" text-anchor="middle" font-size="12" fill="rgba(255,255,255,0.5)">覆盖公开招标 · 邀请招标 · 竞争性谈判 · 单一来源</text>
<!-- 右侧登录卡片 -->
<rect x="660" y="120" width="440" height="560" rx="16" fill="#ffffff" filter="url(#cardShadow)"/>
<!-- 卡片顶部:欢迎语 -->
<text x="880" y="180" text-anchor="middle" font-size="24" font-weight="bold" fill="#1a2332">欢迎登录</text>
<text x="880" y="210" text-anchor="middle" font-size="14" fill="#8895aa">登录您的账户,开始智能招采之旅</text>
<!-- 角色切换开关 -->
<rect x="700" y="235" width="360" height="40" rx="20" fill="#f0f2f7" stroke="#e0e4ec"/>
<rect x="702" y="237" width="176" height="36" rx="18" fill="#c2185b" id="toggleSlider"/>
<text x="790" y="261" text-anchor="middle" font-size="14" font-weight="bold" fill="#fff">👤 内部员工</text>
<text x="970" y="261" text-anchor="middle" font-size="14" fill="#8895aa">🏢 外部账号</text>
<text x="880" y="290" text-anchor="middle" font-size="12" fill="#c2185b">当前:内部员工模式(走 IAM 认证)</text>
<!-- Tab 切换:账号登录 / 手机登录 -->
<rect x="700" y="315" width="360" height="2" fill="#e0e4ec"/>
<rect x="700" y="315" width="100" height="2" fill="#c2185b"/>
<text x="730" y="340" font-size="16" font-weight="bold" fill="#1a2332">账号登录</text>
<text x="880" y="340" font-size="16" fill="#8895aa" text-anchor="middle">手机验证码</text>
<!-- 账号输入 -->
<text x="700" y="385" font-size="14" fill="#4a5568">账号 / 手机号</text>
<rect x="700" y="395" width="360" height="44" rx="8" fill="#f7f8fc" stroke="#e0e4ec"/>
<text x="715" y="423" font-size="14" fill="#b0bbcc">请输入您的账号</text>
<circle cx="1020" cy="417" r="8" fill="#c2185b" opacity="0.6"/>
<text x="1025" y="422" font-size="12" fill="#fff">!</text>
<!-- 密码输入 -->
<text x="700" y="465" font-size="14" fill="#4a5568">密码</text>
<rect x="700" y="475" width="360" height="44" rx="8" fill="#f7f8fc" stroke="#e0e4ec"/>
<text x="715" y="503" font-size="14" fill="#b0bbcc">请输入您的密码</text>
<text x="1015" y="503" font-size="14" fill="#8895aa">👁️</text>
<!-- 额外选项:记住我 + 忘记密码 -->
<rect x="700" y="535" width="16" height="16" rx="3" fill="#c2185b"/>
<path d="M704 543 L708 547 L716 537" stroke="#fff" stroke-width="2" fill="none"/>
<text x="724" y="549" font-size="14" fill="#4a5568">记住我</text>
<text x="950" y="549" font-size="14" fill="#c2185b" text-anchor="end" text-decoration="underline">忘记密码?</text>
<!-- 登录按钮 -->
<rect x="700" y="570" width="360" height="48" rx="10" fill="url(#brandGrad)" filter="url(#inputShadow)"/>
<text x="880" y="600" text-anchor="middle" font-size="18" font-weight="bold" fill="#fff">登 录</text>
<!-- 立即注册 & SRM 提示 -->
<text x="700" y="645" font-size="14" fill="#4a5568">还没有账号?</text>
<text x="820" y="645" font-size="14" font-weight="bold" fill="#c2185b" text-decoration="underline">立即注册</text>
<rect x="700" y="660" width="360" height="2" stroke-dasharray="4,4" stroke="#e0e4ec"/>
<rect x="700" y="660" width="100" height="2" fill="#e0e4ec"/>
<text x="880" y="672" text-anchor="middle" font-size="12" fill="#8895aa">温馨提示</text>
<rect x="700" y="685" width="360" height="36" rx="6" fill="#fff3e0" stroke="#ffcc80"/>
<text x="880" y="709" text-anchor="middle" font-size="12" fill="#e65100">💡 原 SRM 注册用户,可直接使用 SRM 账号密码登录,无需重复注册。</text>
<!-- 底部版权 -->
<text x="880" y="780" text-anchor="middle" font-size="11" fill="#c0c8d8">© 2026 滨化集团 | 智慧招标平台 V1.0</text>
</svg>
```
### 2.2 账号登录页面
关键元素:
* 账号输入框
* 密码输入框
* `记住我`选择框。
* `忘记密码`链接
* `登录`按钮
* `内部员工登录`选择框,选择后将走集团 IAM 路由进行身份认证,否则走平台账号路由进行身认证。
* `立即注册`链接
* 登录说明:原 SRM 注册用户,可直接使用 SRM账号密码登录,无需重复注册。
### 2.3 手机验证码登录页面
关键元素:
* 手机号输出框
* 验证码输出框
* 发送验证码按钮
* 登录按钮
### 2.4 页面状态与元素交互说明
| UI 元素 | 交互行为 | 状态变化 |
| ------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------ |
| **内部员工 / 供应商 切换滑块** | 点击滑块或文字,滑块滑动至对应位置,下方提示文字切换,表单保持不变,但认证逻辑改变。 | 员工模式:下方显示`当前:内部员工模式(IAM 认证)`,且“忘记密码”链接跳转至 IAM 密码找回页;“立即注册”置灰或隐藏(员工由 HR 系统同步)。供应商模式:恢复常规提示。 |
| **账号登录 / 手机登录 Tab** | 点击切换 Tab,下方表单内容切换。 | 账号登录:显示账号+密码框。手机登录:显示手机号+验证码框+发送验证码按钮。 |
| **记住我** | 勾选后,登录成功时将 `refresh_token` 存入浏览器本地(Secure Storage),有效期为 7 天。 | 复选框状态持久化(基于本地 Cookie 记忆偏好)。 |
| **忘记密码** | 供应商模式:跳转至“重置密码”页(需短信/邮箱验证)。员工模式:跳转至 IAM 统一认证的密码重置页面。 | 页面跳转。 |
| **立即注册** | 供应商模式:跳转至供应商注册页(含 SRM 存量账号绑定引导)。员工模式:不可用(灰色)。 | 页面跳转 / 按钮置灰。 |
## .详细交互流程
### 3.1 登录主流程
```mermaid
graph TD
A[供应商访问STP门户] --> B{检测登录态?}
B -->|有有效Token| C[无感跳转至工作台]
B -->|无Token| D[展示登录页面]
D --> E[输入账号/密码]
E --> F{账号来源检测}
F -->|STP本地账号| G[STP身份认证]
F -->|SRM存量账号| H[调用Vendor SSO鉴权]
G --> I{认证成功?}
H --> I
I -->|失败| J[显示错误信息<br>记录失败日志]
J --> K{失败次数≥5?}
K -->|是| L[锁定账号30分钟<br>触发安全告警]
K -->|否| E
I -->|成功| M{是否MFA启用?}
M -->|是| N[发送短信验证码]
N --> O[验证码校验]
O -->|失败| N
O -->|成功| P[生成JWT令牌]
M -->|否| P
P --> Q[更新最后登录信息]
Q --> R[记录审计日志]
R --> S[跳转至供应商工作台]
```
### 3.2 时序图:多认证源详细交互
```mermaid
sequenceDiagram
participant U as 用户/浏览器
participant FE as STP 前端
participant GW as API 网关
participant Auth as 认证服务
participant IAM as 集团 IAM
participant LocalDB as STP 用户库
participant SRM as SRM 系统
U->>FE: 输入账号/密码,点击登录
FE->>FE: 前端基础校验(非空/格式)
FE->>GW: POST /api/v1/auth/login { loginType, account, password, mode }
alt 员工模式 (mode=employee)
GW->>Auth: 转发至 IAM 认证处理器
Auth->>IAM: OAuth2/OIDC 密码模式 / LDAP 验证
IAM-->>Auth: 返回用户信息 (employeeId, dept, roles)
Auth-->>Auth: 若成功,生成 STP 内部 Session
Auth-->>FE: 200 OK { token, redirectUrl }
else 供应商模式 (mode=vendor)
GW->>Auth: 转发至本地认证处理器
Auth->>LocalDB: 查询 account
alt 本地用户存在
LocalDB-->>Auth: 返回用户实体(含 salt, hash
Auth->>Auth: 校验密码(BCrypt/Argon2
else 本地用户不存在
Auth->>SRM: 调用 /api/v1/auth/login/for-stp { username, password }
SRM-->>Auth: 200 { srmId, name, taxId, ... }
Auth->>LocalDB: 创建影子用户(绑定 srmId)
LocalDB-->>Auth: 返回新用户实体
end
Auth-->>FE: 200 OK { token, vendorId, srmId, redirectUrl }
end
FE->>FE: 存储 Token 至 LocalStorage/Session
FE-->>U: 302 重定向至工作台
```
### 3.3 手机验证码登录详细流程
```mermaid
sequenceDiagram
participant U as 用户
participant FE as 前端
participant Auth as 认证服务
participant SMS as 短信网关
participant Cache as Redis 缓存
U->>FE: 输入手机号,点击“发送验证码”
FE->>FE: 校验手机号格式(11位/国际格式)
FE->>Auth: POST /api/v1/auth/sms/send { phone, captchaToken }
Auth->>Auth: 校验图形验证码(防机器人)
Auth->>Cache: 查询该手机号发送频率(60s内是否已发)
alt 频率过高
Cache-->>Auth: 已发送,拒绝
Auth-->>FE: 429 Too Many Requests { remainSeconds: 45 }
else 频率正常
Auth->>Auth: 生成6位随机验证码(如 882345)
Auth->>Cache: 存储验证码至 RedisKey: sms:login:{phone}, TTL: 5min
Auth->>SMS: 调用短信发送接口
SMS-->>Auth: 发送成功
Auth-->>FE: 200 OK { expireSeconds: 300 }
FE-->>U: 开始倒计时(60s
end
U->>FE: 输入验证码,点击“登录”
FE->>Auth: POST /api/v1/auth/login/mobile { phone, code }
Auth->>Cache: 读取验证码并比对
alt 验证码匹配
Cache-->>Auth: 匹配成功
Auth->>Auth: 查询或创建用户(若不存在则自动注册/绑定SRM)
Auth-->>FE: 200 { token, isNewUser }
else 验证码错误或过期
Auth-->>FE: 401 { code: "ERR_SMS_001", message: "验证码错误或已过期" }
end
```
## 4. 接口详细定义
### 4.1 账号密码登录接口
|项目|内容|
|---|---|
|**路径**|`/api/v1/auth/login`|
|**方法**|`POST`|
|**是否认证**|否(公开接口)|
**请求体**
```json
{
"account": "zhangli@binhai.com", // 账号/邮箱/手机号
"password": "abcsdfg",
"mode": "vendor", // "employee" 或 "vendor"
"back_url":"/index" //返回的链接,可省略
"rememberMe": true // 是否记住我
}
```
**成功响应(200**
```json
{
"code": "0",
"data": {
"accessToken": "eyJhbGciOiJSUzI1NiIs...",
"refreshToken": "dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4...",
"expiresIn": 1800,
"userInfo": {
"userId": "U_20240001",
"userType": "VENDOR", // EMPLOYEE / VENDOR
"name": "张立",
"srmId": "SRM_12345", // 若存在关联
"roles": ["SUPPLIER"],
"redirectUrl": "/dashboard/vendor"
}
}
}
```
**失败响应(401**
```json
{
"code": "ERR_AUTH_001",
"message": "账号或密码错误",
"remainingAttempts": 4
}
```
### 4.2 手机验证码发送接口
|项目|内容|
|---|---|
|**路径**|`/api/v1/auth/sms/send`|
|**方法**|`POST`|
**请求体**
```json
{
"phone": "13800138000",
"captchaToken": "a1b2c3d4" // 图形验证码 token
"mode": "vendor", // "employee" 或 "vendor"
"back_url":"/index" //返回的链接,可省略
"rememberMe": true // 是否记住我
}
```
**成功响应**
```json
{
"code": "0",
"data": {
"expireSeconds": 300,
"resendAfterSeconds": 60
}
}
```
## 5. 安全设计细项
| 维度 | 具体策略 |
| -------------- | ----------------------------------------------------------------------------------------------------- |
| **传输加密** | 强制 HTTPS/TLS 1.3,禁用弱加密套件。 |
| **密码存储** | 使用 **Argon2id** 或 **BCryptcost≥10** 加盐哈希存储。 |
| **防暴力破解** | 单账号 5 次失败后锁定 30 分钟(IP 维度 20 次失败后全局验证码强制)。 |
| **JWT 安全** | 算法使用 RS256(私钥签名,公钥验签);Access Token 有效期 30 分钟;Refresh Token 有效期 7 天,存储于 HTTPOnly Secure Cookie(防 XSS)。 |
| **验证码安全** | 手机验证码 6 位数字,有效期 5 分钟;发送间隔限制 60 秒;单日单号码上限 10 条。 |
| **登录审计** | 记录:用户 ID、登录时间、IP、User-Agent、设备指纹(Canvas Fingerprint)、认证源(IAM/Local/SRM)。审计日志写入哈希链(见《需求说明书》4.8.1)。 |
| **多因素认证(MFA** | 预留扩展接口。敏感操作(修改密码、更换手机)强制二次验证。 |
| **会话并发** | 默认允许同一账号多设备登录,==但可在管理后台启用“单点登录互踢”策略(保留配置项)==。 |
## 6. 异常场景与错误码矩阵
| 异常场景 | 错误码 | HTTP 状态码 | 用户提示 | 后端处理 |
| --------------- | --------------- | -------- | ----------------- | ---------- |
| 账号为空 | `ERR_PARAM_001` | 400 | 请输入账号 | 无 |
| 密码为空 | `ERR_PARAM_002` | 400 | 请输入密码 | 无 |
| 账号/密码不匹配(本地) | `ERR_AUTH_001` | 401 | 账号或密码错误 | 失败计数 + 1 |
| 账号不存在且 SRM 验证失败 | `ERR_AUTH_002` | 401 | 账号不存在,请检查或注册 | 记录未注册尝试 |
| 账号已锁定 | `ERR_AUTH_003` | 403 | 账号已被锁定,请 30 分钟后重试 | 告警(若为频繁攻击) |
| 手机号格式错误 | `ERR_PARAM_003` | 400 | 请输入正确的手机号 | 无 |
| 验证码发送频率过高 | `ERR_SMS_001` | 429 | 发送过于频繁,请 60 秒后重试 | 返回剩余秒数 |
| 验证码错误/过期 | `ERR_SMS_002` | 401 | 验证码错误或已过期 | 清除已用验证码 |
| IAM 认证超时 | `ERR_IAM_001` | 504 | 集团认证服务繁忙,请稍后重试 | 熔断降级,记录日志 |
## 7. 登录成功后的路由与权限门控
登录成功后,前端根据 `userType` 和 `roles` 决定跳转目标,同时后端在后续请求中通过 **JWT** 持续校验权限。
| 用户类型 | 默认跳转页面 | 可访问范围 |
| ------------------ | --------------------- | ------------------------------------- |
| **供应商(VENDOR** | `/portal/dashboard` | 招标公告列表、报名管理、我的投标、中标通知、保证金管理。 |
| **评审专家(EXPERT** | `/expert/dashboard` | 评标任务列表、回避申报、承诺书签署、历史评审记录。 |
| **内部员工(EMPLOYEE** | `/employee/dashboard` | 根据子角色(需求人员/招标专员/审批人/管理员)展示对应工作台。 |
| **系统管理员(ADMIN** | `/admin/console` | 用户管理、流程配置、审计日志、系统监控。<br>(管理员不在普通页面登录) |
```mermaid
graph LR
A[登录成功] --> B{UserType}
B -->|VENDOR| C[供应商工作台]
B -->|EXPERT| D[专家工作台]
B -->|EMPLOYEE| E{子角色}
E -->|需求人员| F[需求工作台]
E -->|招标专员| G[招标管理工作台]
E -->|审批人| H[审批待办中心]
E -->|管理员| I[系统管理后台]
style A fill:#e8f5e9,stroke:#388e3c
style C fill:#e3f2fd,stroke:#1565c0
style D fill:#fff3e0,stroke:#e65100
style I fill:#fce4ec,stroke:#c2185b
```
## 8. 与 SRM 无感登录的集成衔接
登录页下方的 **“原 SRM 注册用户,可直接使用 SRM账号密码登录”** 提示对应后端自动回退逻辑(详见 3.2 时序图)。此外,登录成功后,前端页面内嵌的指向 SRM 的所有链接,将自动携带 **跳转票据(Ticket** 实现无感跳转(技术方案详见《SRM 与 STP 数据交换接口方案说明书》3.4.2 节)。