412 lines
20 KiB
Markdown
412 lines
20 KiB
Markdown
# 登录模块
|
||
|
||
## 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.详细交互流程
|
||
### 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: 存储验证码至 Redis(Key: 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** 或 **BCrypt(cost≥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 节)。
|
||
|