# 登录模块
## 1. 模块概述
### 1.1 设计原则
| 原则 | 说明 |
| -------- | ----------------------------- |
| **安全优先** | 传输全程加密,防暴力破解。 |
| **角色分流** | 通过“内部员工登录”开关明确区分认证路由,杜绝权限越界。 |
| **存量兼容** | 无缝兼容 SRM 存量供应商账号,降低用户迁移成本。 |
| **体验极简** | 支持账号/手机双因子登录,并提供“记住我”与智能异常提示。 |
### 1.2 角色与认证路由映射
| 用户类型 | 开关状态 | 认证目标 | 说明 |
| ---------- | --------- | -------------------- | ------------------------------------------------- |
| **集团内部员工** | 开启(员工模式) | IAM(集团统一身份认证) | 跳转至 IAM 登录页或调用 OAuth2/OIDC 接口,不校验平台本地密码。 |
| **外部供应商** | 关闭(供应商模式) | STP 本地用户表 / SRM 回退认证 | 优先校验 STP 本地账号;若不存在,自动路由至 SRM 验证(满足 SRM 存量用户无感登录)。 |
## 2. 页面布局与 UI 设计
### 2.1 整体布局
```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[显示错误信息
记录失败日志]
J --> K{失败次数≥5?}
K -->|是| L[锁定账号30分钟
触发安全告警]
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` | 用户管理、流程配置、审计日志、系统监控。
(管理员不在普通页面登录) |
```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 节)。