20 KiB
20 KiB
登录模块
1. 模块概述
1.1 设计原则
| 原则 | 说明 |
|---|---|
| 安全优先 | 传输全程加密,防暴力破解。 |
| 角色分流 | 通过“内部员工登录”开关明确区分认证路由,杜绝权限越界。 |
| 存量兼容 | 无缝兼容 SRM 存量供应商账号,降低用户迁移成本。 |
| 体验极简 | 支持账号/手机双因子登录,并提供“记住我”与智能异常提示。 |
1.2 角色与认证路由映射
| 用户类型 | 开关状态 | 认证目标 | 说明 |
|---|---|---|---|
| 集团内部员工 | 开启(员工模式) | IAM(集团统一身份认证) | 跳转至 IAM 登录页或调用 OAuth2/OIDC 接口,不校验平台本地密码。 |
| 外部供应商 | 关闭(供应商模式) | STP 本地用户表 / SRM 回退认证 | 优先校验 STP 本地账号;若不存在,自动路由至 SRM 验证(满足 SRM 存量用户无感登录)。 |
2. 页面布局与 UI 设计
2.1 整体布局
<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 登录主流程
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 时序图:多认证源详细交互
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 手机验证码登录详细流程
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 |
| 是否认证 | 否(公开接口) |
| 请求体 |
{
"account": "zhangli@binhai.com", // 账号/邮箱/手机号
"password": "abcsdfg",
"mode": "vendor", // "employee" 或 "vendor"
"back_url":"/index" //返回的链接,可省略
"rememberMe": true // 是否记住我
}
成功响应(200)
{
"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)
{
"code": "ERR_AUTH_001",
"message": "账号或密码错误",
"remainingAttempts": 4
}
4.2 手机验证码发送接口
| 项目 | 内容 |
|---|---|
| 路径 | /api/v1/auth/sms/send |
| 方法 | POST |
请求体
{
"phone": "13800138000",
"captchaToken": "a1b2c3d4" // 图形验证码 token
"mode": "vendor", // "employee" 或 "vendor"
"back_url":"/index" //返回的链接,可省略
"rememberMe": true // 是否记住我
}
成功响应
{
"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 |
用户管理、流程配置、审计日志、系统监控。 (管理员不在普通页面登录) |
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 节)。