# 用户认证登录API文档 ## 目录 1. [用户登录](#用户登录) 2. [获取用户信息](#获取用户信息) 3. [获取具有指定权限的用户列表](#获取具有指定权限的用户列表) ## 📋 文档说明 本文档记录用户认证、登录、用户信息管理等相关 API 接口。 **依赖文档**: 请先阅读 [API 文档基础规范.md](./API文档基础规范.md) 了解通用规范。 **统一身份认证**: 前端「统一认证登录」按钮的完整业务流程、CAS OAuth2.0 接入说明及排查指南,请参阅 [统一身份认证接入说明.md](../统一身份认证接入说明.md)。 --- ## 🔐 用户认证 ### 用户登录 **接口地址**: `POST /auth/login` **接口描述**: 用户登录认证接口,支持密码登录。请求数据使用 AES+RSA 混合加密传输,确保安全性。 **权限要求**: 无需认证(公开接口) **安全机制**: - **数据加密**: 请求参数使用 AES 对称加密 - **密钥保护**: AES 密钥使用 RSA 非对称加密 - **自动解密**: 后端自动解密请求数据和响应数据 **请求头配置**: ```json { "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" | **请求示例**: ```json { "tenantId": "000000", "username": "admin", "password": "admin123", "clientId": "e5cd7e4891bf95d1d19206ce24a7b32e", "grantType": "password" } ``` **响应示例**: ```json { "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. **多租户安全**: - 租户隔离验证 - 客户端权限控制 - 不同设备类型支持 **实际使用示例**: ```javascript // 完整的登录实现示例(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` **接口描述**: 用户退出登录,清除服务端会话信息 **权限要求**: 需要登录 **请求参数**: 无需请求体 **请求头**: ```json { "Authorization": "Bearer {access_token}" } ``` **响应示例**: ```json { "code": 200, "msg": "退出成功", "data": null } ``` **功能说明**: - 清除服务端 Token 会话 - 关闭 SSE 连接(如果开启) - 清理用户登录状态 ### 获取验证码 **接口地址**: `GET /auth/code` **接口描述**: 获取图形验证码,用于登录时的安全验证 **权限要求**: 无需认证 **请求参数**: 无 **响应示例**: ```json { "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` **接口描述**: 获取当前登录用户的详细信息,包括角色权限等 **权限要求**: 需要登录 **请求参数**: 无 **响应示例**: ```json { "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` **接口描述**: 获取系统可用的租户列表,用于登录时选择租户 **权限要求**: 无需认证 **请求参数**: 无 **响应示例**: ```json { "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兼容,但在实现细节上有所差异: **网络请求配置**: ```javascript // 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调用**: ```javascript // 移动端登录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... } ``` ### 移动端用户信息缓存 **用户信息存储工具**: ```javascript // 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/login`API → 获取access_token 3. **获取用户信息** → 调用`/system/user/getInfo`API → 获取用户详情 4. **缓存信息** → 使用`userStorage.saveLoginInfo()`缓存登录信息 5. **页面访问** → 所有页面自动检查登录状态 6. **退出登录** → 清除本地缓存 → 跳转登录页 ### 移动端页面入口配置 **pages.json配置**: ```json { "pages": [ { "path": "pages/login/index", "style": { "navigationBarTitleText": "登录", "navigationStyle": "custom", "navigationBarBackgroundColor": "transparent", "backgroundColor": "transparent" } } ] } ``` ### 移动端登录状态验证 **全局登录验证**: ```javascript // 在每个需要登录的页面中 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 | ### 响应参数 ```typescript { 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; // 总记录数 } ``` ### 响应示例 ```json { "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`)升序排序 ### 使用示例 ```typescript // 前端调用示例 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 | ### 响应参数 ```typescript { 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 | ### 响应参数 ```typescript { code: number; msg: string; data: { userId: number; userName: string; nickName: string; phonenumber: string; }[]; } ``` ### 注意事项 1. 用户ID集合为空时返回有权限范围内的全部用户 2. 返回结果默认按照用户名(`user_name`)升序排序,保持前端下拉展示一致性