Files
hzdyjs/docs/开发文档/API/用户认证登录API.md
2026-06-26 15:30:24 +08:00

24 KiB
Raw Permalink Blame History

用户认证登录API文档

目录

  1. 用户登录
  2. 获取用户信息
  3. 获取具有指定权限的用户列表

📋 文档说明

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

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

统一身份认证: 前端「统一认证登录」按钮的完整业务流程、CAS OAuth2.0 接入说明及排查指南,请参阅 统一身份认证接入说明.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": "1938120323613728770",
            "avatarUrl": "http://example.com/ruoyi/2025/08/31/xxxx.jpeg?X-Amz-Algorithm=AWS4-HMAC-SHA256&...",
            "status": "0",
            "loginIp": "127.0.0.1",
            "loginDate": "2025-01-26 10:30:00",
            "tenantId": "000000",
            "deptId": 103,
            "deptName": "智胜技术开发团队"
        },
        "roles": ["admin", "common"],
        "permissions": ["*:*:*"]
    }
}

字段说明补充:

  • avatar:始终返回 sys_user.avatar 中存储的 OSS ID(字符串),用于鉴权下载接口 /resource/oss/download/{ossId}。
  • avatarUrl:系统通过 TransConstant.OSS_ID_TO_URL 规则实时翻译得到的可直接访问地址(MinIO 预签名 URL),供无需链路改造的场景快速显示;有效期受 OSS 策略控制。

前端如需在带 Token 的受保护组件(如 ImagePreview)中展示,请优先使用 avatar 并通过统一的认证下载逻辑获取图片,避免直接暴露临时 URL。

超级管理员权限说明:

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 2 + uni-app
存储 localStorage uni.storage
网络请求 axios uni.request
路由跳转 router.push uni.navigateTo/reLaunch
UI组件 Element Plus uview-ui
加密方式 AES+RSA 支持,但简化实现

移动端登录页面特性

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

📞 联系方式

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


📝 维护: 用户认证登录 API 文档
🔄 更新记录:

  • 2025-11-28:/system/user/getInfo 增加 avatarUrl 字段,并明确 avatar 返回 OSS ID
  • 2025-01-03:文档初始化
    📁 文件: 用户认证登录 API.md

获取具有指定权限的用户列表

基本信息

  • 接口URL: /system/user/list-by-permission
  • 请求方式: GET
  • 接口描述: 获取具有指定权限的用户列表
  • 权限标识: system:user:list

请求参数

Query参数

参数名 类型 是否必须 说明 示例值
permission string 是 权限标识符 hq:hallAudit:list

响应参数

{
  code: number;       // 状态码
  msg: string;        // 提示信息
  rows: {
    userId: string;   // 用户ID
    userName: string; // 用户名
    nickName: string; // 昵称
    dept: {
      deptId: string;   // 部门ID
      deptName: string; // 部门名称
    };
    avatar: string;   // 头像OSS ID
    avatarUrl?: string; // 头像访问URL(可选,预签名)
  }[];
  total: number;      // 总记录数
}

响应示例

{
  "code": 200,
  "msg": "操作成功",
  "rows": [
    {
      "userId": "1",
      "userName": "admin",
      "nickName": "管理员",
      "dept": {
        "deptId": "1",
        "deptName": "后勤部"
      },
      "avatar": "1938120323613728770",
      "avatarUrl": "http://example.com/avatar.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&..."
    }
  ],
  "total": 1
}

错误码

错误码 说明
401 未登录或登录超时
403 没有权限
500 服务器内部错误

注意事项

  1. 该接口需要用户登录后才能访问
  2. 返回的用户信息已过滤敏感字段
  3. 建议前端对结果进行缓存,避免频繁请求
  4. 返回结果按照用户名(user_name)升序排序

使用示例

// 前端调用示例
import { listUsersByPermission } from '@/api/system/user';

async function getAuditors() {
  try {
    const res = await listUsersByPermission('hq:hallAudit:list');
    if (res.code === 200) {
      return res.rows;
    }
  } catch (error) {
    console.error('获取审核人员列表失败:', error);
    return [];
  }
}

根据部门获取用户列表

基本信息

  • 接口URL: /system/user/list/dept/{deptId}
  • 请求方式: GET
  • 接口描述: 根据部门ID获取该部门下所有启用状态的用户,用于部门人员选择器
  • 权限标识: system:user:list

路径参数

参数名 类型 是否必须 说明 示例值
deptId number 是 部门ID 1001

响应参数

{
  code: number;
  msg: string;
  data: {
    userId: number;
    userName: string;
    nickName: string;
    phonenumber: string;
  }[];
}

注意事项

  1. 只返回未删除且状态为正常的用户
  2. 返回结果按照用户名(user_name)升序排序,便于快速定位

批量获取用户基础信息

基本信息

  • 接口URL: /system/user/optionselect
  • 请求方式: GET
  • 接口描述: 根据用户ID集合(可选)和部门ID(可选)批量获取基础用户信息
  • 权限标识: system:user:query

Query参数

参数名 类型 是否必须 说明 示例值
userIds string 否 逗号分隔的用户ID集合 1,2,3
deptId number 否 指定部门ID时仅返回该部门用户 1001

响应参数

{
  code: number;
  msg: string;
  data: {
    userId: number;
    userName: string;
    nickName: string;
    phonenumber: string;
  }[];
}

注意事项

  1. 用户ID集合为空时返回有权限范围内的全部用户
  2. 返回结果默认按照用户名(user_name)升序排序,保持前端下拉展示一致性