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

20 KiB
Raw Blame History

登录模块

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.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: 存储验证码至 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
是否认证 否(公开接口)
请求体
{
  "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 或 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 用户管理、流程配置、审计日志、系统监控。
(管理员不在普通页面登录)
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 节)。