Files
hzdyjs/docs/开发文档/API/用户认证登录API.md
T
zlwu afe3e234e9 feat(mobile): 实现全局路由守卫功能,自动处理用户未登录跳转
✨ 新增功能:
- 全局路由守卫模块 (route-guard.js)
- 多层次权限验证 (登录状态+Token存在+Token过期检查)
- 自动拦截机制 (页面级+跳转级+HTTP级+应用级)
- 401响应自动处理和跳转
- 路由守卫测试页面

🔧 技术实现:
- checkPageAccess() 全局页面权限检查
- guardedNavigate/guardedSwitchTab 带权限检查的跳转
- handleUnauthorized() HTTP拦截器401处理
- 公开页面配置管理
- 优雅的错误提示和跳转逻辑

📱 页面集成:
- App.vue 初始化路由守卫
- profile/workbench 页面添加权限检查
- index 页面优化跳转逻辑
- 新增测试页面验证功能

📚 文档完善:
- 移动端路由守卫使用指南
- 详细的变更记录和技术说明
- 最佳实践和注意事项

🎯 核心效果:
用户登录失效或未登录时,访问任何需要权限的页面都自动跳转到登录页,提升安全性和用户体验。
2025-07-04 22:28:49 +08:00

19 KiB

用户认证登录 API

📋 文档说明

本文档记录用户认证、登录、用户信息管理等相关 API 接口。

依赖文档: 请先阅读 API 文档基础规范.md 了解通用规范。


🔐 用户认证

用户登录

接口地址: POST /auth/login

接口描述: 用户登录认证接口,支持密码登录。请求数据使用 AES+RSA 混合加密传输,确保安全性。

权限要求: 无需认证(公开接口)

安全机制:

  • 数据加密: 请求参数使用 AES 对称加密
  • 密钥保护: AES 密钥使用 RSA 非对称加密
  • 自动解密: 后端自动解密请求数据和响应数据

请求头配置:

{
    "Content-Type": "application/json;charset=utf-8",
    "clientid": "e5cd7e4891bf95d1d19206ce24a7b32e",
    "encrypt-key": "[RSA加密的AES密钥]",
    "referer": "http://localhost/login?redirect=%252Findex",
    "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
    "isToken": "false",
    "isEncrypt": "true",
    "repeatSubmit": "false"
}

关键请求头说明:

请求头名 必填 说明 示例值
clientid 是 客户端 ID,用于身份验证 "e5cd7e4891bf95d1d19206ce24a7b32e"
encrypt-key 是 RSA 加密的 AES 密钥,用于数据解密 "Q8Td0si9R3EYj3pFe50rzn/8wxo69Y9uR6LE..."
referer 是 来源页面地址,用于请求来源验证 "http://localhost/login?redirect=%252Findex"

加密流程说明:

  1. 前端生成随机 AES 密钥(32 位字符串)
  2. 使用 AES-ECB-PKCS7 模式加密请求数据
  3. 使用 RSA 公钥加密 AES 密钥
  4. 将加密后的 AES 密钥放入请求头encrypt-key
  5. 后端使用 RSA 私钥解密 AES 密钥,再解密请求数据

请求参数:

参数名 类型 必填 说明 示例值
tenantId string 是 租户 ID,多租户系统标识 "000000"
username string 是 用户名,长度 2-30 字符 "admin"
password string 是 用户密码,长度 5-30 字符 "admin123"
clientId string 是 客户端 ID,用于权限控制 "e5cd7e4891bf95d1d19206ce24a7b32e"
grantType string 是 授权类型,目前支持 password "password"
code string 否 验证码(当系统开启验证码时必填) "1234"
uuid string 否 验证码唯一标识(当需要验证码时必填) "uuid-string"

请求示例:

{
    "tenantId": "000000",
    "username": "admin",
    "password": "admin123",
    "clientId": "e5cd7e4891bf95d1d19206ce24a7b32e",
    "grantType": "password"
}

响应示例:

{
    "code": 200,
    "msg": "操作成功",
    "data": {
        "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOiJzeXNfdXNlcjoxIiwicm5TdHIiOiJ5MlhvbHRnd1Y2YjUzRnRMVzF4SFR1anRSUERjeGdmYyIsImNsaWVudGlkIjoiZTVjZDdlNDg5MWJmOTVkMWQxOTIwNmNlMjRhN2IzMmUiLCJ0ZW5hbnRJZCI6IjAwMDAwMCIsInVzZXJJZCI6MSwidXNlck5hbWUiOiJhZG1pbiIsImRlcHRJZCI6MTAzLCJkZXB0TmFtZSI6IuaZuuiDveaOp-WItuWtpumZoiIsImRlcHRDYXRlZ29yeSI6IiIsIm5pY2tOYW1lIjoi55av54uC55qE54uu5a2QTGkifQ.WI6iY3AgwPz7ceI8WDsVy-GaTXZnU-JKUYUTjhQdoKM",
        "refresh_token": null,
        "expire_in": 604800,
        "refresh_expire_in": null,
        "client_id": "e5cd7e4891bf95d1d19206ce24a7b32e",
        "scope": null,
        "openid": null
    }
}

实际 Token 解析信息:

字段名 值 说明
userId 1 用户 ID
userName admin 用户名
nickName 疯狂狮子 Li 用户昵称
deptId 103 部门 ID
deptName 智胜技术开发团队 部门名称
tenantId 000000 租户 ID
clientId e5cd7e4891bf95... 客户端 ID
loginType login 登录类型

Token 有效期: 7 天 (604800 秒)

响应参数说明:

参数名 类型 说明
access_token string 访问令牌,用于 API 调用认证
refresh_token string 刷新令牌,用于获取新的访问令牌
expire_in long 访问令牌过期时间(秒)
refresh_expire_in long 刷新令牌过期时间(秒)
client_id string 客户端 ID
scope string 权限范围
openid string 第三方登录标识(可为空)

多种登录方式支持:

系统采用策略模式,支持多种登录方式:

  1. 密码登录 (grantType: "password")

    • 使用用户名和密码登录
    • 支持验证码验证(可配置开关)
    • BCrypt 密码加密存储
  2. 邮箱登录 (grantType: "email")

    • 使用邮箱和邮箱验证码登录
    • 需要先发送邮箱验证码
  3. 短信登录 (grantType: "sms")

    • 使用手机号和短信验证码登录
    • 需要先发送短信验证码
  4. 小程序登录 (grantType: "xcx")

    • 微信小程序授权登录
    • 需要小程序码和应用 ID

错误码说明:

错误码 错误信息 说明 解决方案
400 参数校验失败 请求参数格式错误 检查参数格式和必填字段
400 客户端 id 异常 clientId 不存在或被禁用 检查 clientId 配置
400 授权类型不正确 grantType 不支持 使用支持的授权类型
400 当前系统没有开启注册功能 租户未开启注册 联系管理员开启注册功能
401 用户不存在 用户名不存在 检查用户名是否正确
401 用户已被停用 用户账号被禁用 联系管理员激活账号
401 密码错误 用户密码不正确 检查密码是否正确
401 验证码已过期 验证码超时 重新获取验证码
401 验证码错误 验证码输入错误 检查验证码是否正确
403 登录失败次数过多,账户已锁定 密码错误次数超限 等待解锁或联系管理员
500 租户不存在 tenantId 无效 检查租户配置

安全特性:

  1. 密码安全:

    • BCrypt 哈希加密存储
    • 登录失败次数限制
    • 账户自动锁定机制
  2. 传输安全:

    • AES+RSA 混合加密
    • 防重放攻击保护
    • HTTPS 协议传输
  3. 会话安全:

    • JWT Token 认证
    • Token 自动过期机制
    • 刷新 Token 机制
  4. 多租户安全:

    • 租户隔离验证
    • 客户端权限控制
    • 不同设备类型支持

实际使用示例:

// 完整的登录实现示例(AES+RSA加密)
const axios = require('axios');
const CryptoJS = require('crypto-js');
const NodeRSA = require('node-rsa');

async function loginWithEncryption(username = 'admin', password = 'admin123') {
    // 配置信息
    const CONFIG = {
        url: 'http://localhost/dev-api/auth/login',
        clientId: 'e5cd7e4891bf95d1d19206ce24a7b32e',
        tenantId: '000000',
        referer: 'http://localhost/login?redirect=%252Findex',
        rsaKey: 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==',
    };

    // 生成32位随机AES密钥
    function generateAESKey() {
        const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
        let result = '';
        for (let i = 0; i < 32; i++) {
            result += chars.charAt(Math.floor(Math.random() * chars.length));
        }
        return result;
    }

    // AES加密
    function aesEncrypt(data, key) {
        const keyUtf8 = CryptoJS.enc.Utf8.parse(key);
        return CryptoJS.AES.encrypt(data, keyUtf8, {
            mode: CryptoJS.mode.ECB,
            padding: CryptoJS.pad.Pkcs7,
        }).toString();
    }

    // RSA加密AES密钥
    function rsaEncrypt(aesKey) {
        const pemKey = `-----BEGIN PUBLIC KEY-----\n${CONFIG.rsaKey}\n-----END PUBLIC KEY-----`;
        const rsaKey = new NodeRSA(pemKey);
        rsaKey.setOptions({ encryptionScheme: 'pkcs1' });
        const aesKeyBase64 = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(aesKey));
        return rsaKey.encrypt(aesKeyBase64, 'base64');
    }

    // 生成AES密钥并加密登录数据
    const aesKey = generateAESKey();
    const loginData = {
        tenantId: CONFIG.tenantId,
        username: username,
        password: password,
        clientId: CONFIG.clientId,
        grantType: 'password',
    };

    const encryptedData = aesEncrypt(JSON.stringify(loginData), aesKey);
    const encryptedKey = rsaEncrypt(aesKey);

    // 设置请求头
    const headers = {
        'Content-Type': 'application/json;charset=utf-8',
        clientid: CONFIG.clientId,
        'encrypt-key': encryptedKey,
        referer: CONFIG.referer,
        'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
        isToken: 'false',
        isEncrypt: 'true',
        repeatSubmit: 'false',
    };

    // 发送登录请求
    const response = await axios.post(CONFIG.url, encryptedData, {
        headers: headers,
        timeout: 10000,
    });

    if (response.data.code === 200 && response.data.data?.access_token) {
        return response.data.data.access_token;
    } else {
        throw new Error(response.data.msg || 'Login failed');
    }
}

// 使用示例
loginWithEncryption()
    .then((token) => {
        console.log('Login successful, token:', token);
    })
    .catch((error) => {
        console.error('Login failed:', error.message);
    });

快速测试脚本:

项目中提供了完整的测试脚本,位于test/目录:

  • test/custom-login-script.js - 完整功能版
  • test/quick-token-getter.js - 快速获取版
  • test/登录脚本使用说明.md - 详细使用文档

退出登录

接口地址: POST /auth/logout

接口描述: 用户退出登录,清除服务端会话信息

权限要求: 需要登录

请求参数: 无需请求体

请求头:

{
    "Authorization": "Bearer {access_token}"
}

响应示例:

{
    "code": 200,
    "msg": "退出成功",
    "data": null
}

功能说明:

  • 清除服务端 Token 会话
  • 关闭 SSE 连接(如果开启)
  • 清理用户登录状态

获取验证码

接口地址: GET /auth/code

接口描述: 获取图形验证码,用于登录时的安全验证

权限要求: 无需认证

请求参数: 无

响应示例:

{
    "code": 200,
    "msg": "操作成功",
    "data": {
        "uuid": "d7c2c4c4-4e9d-4a5f-9c1a-2b3c4d5e6f7g",
        "img": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABH..."
    }
}

响应参数说明:

参数名 类型 说明
uuid string 验证码唯一标识
img string Base64 编码的验证码图片

使用流程:

  1. 前端调用此接口获取验证码图片和 UUID
  2. 用户输入验证码
  3. 登录时将验证码和 UUID 一起提交
  4. 服务端验证验证码的正确性和有效性

👤 用户信息

获取当前用户信息

接口地址: GET /system/user/getInfo

接口描述: 获取当前登录用户的详细信息,包括角色权限等

权限要求: 需要登录

请求参数: 无

响应示例:

{
    "code": 200,
    "msg": "操作成功",
    "data": {
        "user": {
            "userId": 1,
            "userName": "admin",
            "nickName": "疯狂狮子Li",
            "email": "admin@example.com",
            "phonenumber": "13800138000",
            "sex": "1",
            "avatar": "/profile/avatar/default.jpg",
            "status": "0",
            "loginIp": "127.0.0.1",
            "loginDate": "2025-01-26 10:30:00",
            "tenantId": "000000",
            "deptId": 103,
            "deptName": "智胜技术开发团队"
        },
        "roles": ["admin", "common"],
        "permissions": ["*:*:*"]
    }
}

超级管理员权限说明:

admin 用户具有系统最高权限*:*:*,可以访问所有功能模块和执行所有操作。


🏢 租户管理

获取租户列表

接口地址: GET /auth/tenant/list

接口描述: 获取系统可用的租户列表,用于登录时选择租户

权限要求: 无需认证

请求参数: 无

响应示例:

{
    "code": 200,
    "msg": "操作成功",
    "data": {
        "tenantEnabled": true,
        "voList": [
            {
                "tenantId": "000000",
                "companyName": "杭州第一技师学院",
                "domain": "hzdyjs.edu.cn",
                "status": "0"
            }
        ]
    }
}

响应参数说明:

参数名 类型 说明
tenantEnabled boolean 是否启用多租户功能
voList array 租户列表

voList 中每项参数:

参数名 类型 说明
tenantId string 租户 ID
companyName string 租户名称
domain string 租户域名
status string 状态(0 正常 1 停用)


📱 移动端登录实现

移动端特殊配置

移动端使用uni-app框架,登录实现与web端保持API兼容,但在实现细节上有所差异:

网络请求配置:

// mobile/src/utils/request.js
const baseURL = 'http://localhost:8080'

const request = (options) => {
  return new Promise((resolve, reject) => {
    const token = getToken()
    
    uni.request({
      url: baseURL + options.url,
      method: options.method || 'GET',
      data: options.data || {},
      header: {
        'Content-Type': 'application/json',
        'Authorization': token,
        'clientid': 'e5cd7e4891bf95d1d19206ce24a7b32e',
        'referer': baseURL,
        ...options.header,
      },
      // 响应处理逻辑...
    })
  })
}

登录API调用:

// 移动端登录API
export const api = {
  login: (data) =>
    request({
      url: '/auth/login',
      method: 'POST',
      data: {
        tenantId: data.tenantId || '000000',
        username: data.username,
        password: data.password,
        clientId: data.clientId || 'e5cd7e4891bf95d1d19206ce24a7b32e',
        grantType: data.grantType || 'password'
      },
      header: {
        isToken: false,
        isEncrypt: true,
        repeatSubmit: false
      }
    }),
    
  getUserInfo: () =>
    request({
      url: '/system/user/getInfo',
      method: 'GET'
    }),
    
  // 其他API...
}

移动端用户信息缓存

用户信息存储工具:

// mobile/src/utils/request.js
export const userStorage = {
  // 保存登录信息
  saveLoginInfo: (loginResult, userInfo) => {
    try {
      uni.setStorageSync('access_token', loginResult.access_token)
      if (userInfo) {
        uni.setStorageSync('userInfo', JSON.stringify(userInfo))
      }
      uni.setStorageSync('isLoggedIn', true)
    } catch (e) {
      console.error('保存登录信息失败:', e)
    }
  },

  // 获取用户信息
  getUserInfo: () => {
    try {
      const userInfo = uni.getStorageSync('userInfo')
      return userInfo ? JSON.parse(userInfo) : null
    } catch (e) {
      return null
    }
  },

  // 检查登录状态
  isLoggedIn: () => {
    try {
      const isLoggedIn = uni.getStorageSync('isLoggedIn')
      const token = uni.getStorageSync('access_token')
      return !!(isLoggedIn && token)
    } catch (e) {
      return false
    }
  },

  // 清除登录信息
  clearLoginInfo: () => {
    try {
      uni.removeStorageSync('access_token')
      uni.removeStorageSync('userInfo')
      uni.removeStorageSync('isLoggedIn')
    } catch (e) {
      console.error('清除登录信息失败:', e)
    }
  }
}

移动端登录流程

  1. 应用启动 → 检查登录状态 → 未登录自动跳转登录页
  2. 用户登录 → 调用/auth/loginAPI → 获取access_token
  3. 获取用户信息 → 调用/system/user/getInfoAPI → 获取用户详情
  4. 缓存信息 → 使用userStorage.saveLoginInfo()缓存登录信息
  5. 页面访问 → 所有页面自动检查登录状态
  6. 退出登录 → 清除本地缓存 → 跳转登录页

移动端页面入口配置

pages.json配置:

{
  "pages": [
    {
      "path": "pages/login/index",
      "style": {
        "navigationBarTitleText": "登录",
        "navigationStyle": "custom",
        "navigationBarBackgroundColor": "transparent",
        "backgroundColor": "transparent"
      }
    }
  ]
}

移动端登录状态验证

全局登录验证:

// 在每个需要登录的页面中
onLoad() {
  this.checkLoginStatus();
},

methods: {
  checkLoginStatus() {
    if (!userStorage.isLoggedIn()) {
      uni.reLaunch({
        url: '/pages/login/index'
      });
      return false;
    }
    return true;
  }
}

移动端与web端差异

特性 Web端 移动端
框架 Vue 3 + TypeScript Vue 3 + uni-app
存储 localStorage uni.storage
网络请求 axios uni.request
路由跳转 router.push uni.navigateTo/reLaunch
UI组件 Element Plus uview-plus
加密方式 AES+RSA 支持,但简化实现

移动端登录页面特性

  • ✅ 现代化UI: 渐变背景 + 毛玻璃效果
  • ✅ 租户选择: 支持多租户选择(与web端一致)
  • ✅ 表单验证: 完整的输入验证和错误提示
  • ✅ 响应式设计: 适配不同屏幕尺寸
  • ✅ 无障碍访问: 支持键盘导航和屏幕阅读器
  • ✅ 安全存储: 使用uni.storage安全存储用户信息

📞 联系方式

如有认证相关问题,请联系开发团队或查看项目仓库的 Issues。


📝 维护: 用户认证登录 API 文档
🔄 更新: 2025-01-03
📁 文件: 用户认证登录 API.md