1. 在SysUserController中新增/list-by-permission接口 2. 实现根据权限标识获取用户列表的业务逻辑 3. 支持按租户筛选用户 4. 更新用户认证登录API文档 5. 优化前端用户相关接口
21 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" |
加密流程说明:
- 前端生成随机 AES 密钥(32 位字符串)
- 使用 AES-ECB-PKCS7 模式加密请求数据
- 使用 RSA 公钥加密 AES 密钥
- 将加密后的 AES 密钥放入请求头
encrypt-key - 后端使用 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 | 第三方登录标识(可为空) |
多种登录方式支持:
系统采用策略模式,支持多种登录方式:
-
密码登录 (
grantType: "password")- 使用用户名和密码登录
- 支持验证码验证(可配置开关)
- BCrypt 密码加密存储
-
邮箱登录 (
grantType: "email")- 使用邮箱和邮箱验证码登录
- 需要先发送邮箱验证码
-
短信登录 (
grantType: "sms")- 使用手机号和短信验证码登录
- 需要先发送短信验证码
-
小程序登录 (
grantType: "xcx")- 微信小程序授权登录
- 需要小程序码和应用 ID
错误码说明:
| 错误码 | 错误信息 | 说明 | 解决方案 |
|---|---|---|---|
| 400 | 参数校验失败 | 请求参数格式错误 | 检查参数格式和必填字段 |
| 400 | 客户端 id 异常 | clientId 不存在或被禁用 | 检查 clientId 配置 |
| 400 | 授权类型不正确 | grantType 不支持 | 使用支持的授权类型 |
| 400 | 当前系统没有开启注册功能 | 租户未开启注册 | 联系管理员开启注册功能 |
| 401 | 用户不存在 | 用户名不存在 | 检查用户名是否正确 |
| 401 | 用户已被停用 | 用户账号被禁用 | 联系管理员激活账号 |
| 401 | 密码错误 | 用户密码不正确 | 检查密码是否正确 |
| 401 | 验证码已过期 | 验证码超时 | 重新获取验证码 |
| 401 | 验证码错误 | 验证码输入错误 | 检查验证码是否正确 |
| 403 | 登录失败次数过多,账户已锁定 | 密码错误次数超限 | 等待解锁或联系管理员 |
| 500 | 租户不存在 | tenantId 无效 | 检查租户配置 |
安全特性:
-
密码安全:
- BCrypt 哈希加密存储
- 登录失败次数限制
- 账户自动锁定机制
-
传输安全:
- AES+RSA 混合加密
- 防重放攻击保护
- HTTPS 协议传输
-
会话安全:
- JWT Token 认证
- Token 自动过期机制
- 刷新 Token 机制
-
多租户安全:
- 租户隔离验证
- 客户端权限控制
- 不同设备类型支持
实际使用示例:
// 完整的登录实现示例(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 编码的验证码图片 |
使用流程:
- 前端调用此接口获取验证码图片和 UUID
- 用户输入验证码
- 登录时将验证码和 UUID 一起提交
- 服务端验证验证码的正确性和有效性
👤 用户信息
获取当前用户信息
接口地址: 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)
}
}
}
移动端登录流程
- 应用启动 → 检查登录状态 → 未登录自动跳转登录页
- 用户登录 → 调用
/auth/loginAPI → 获取access_token - 获取用户信息 → 调用
/system/user/getInfoAPI → 获取用户详情 - 缓存信息 → 使用
userStorage.saveLoginInfo()缓存登录信息 - 页面访问 → 所有页面自动检查登录状态
- 退出登录 → 清除本地缓存 → 跳转登录页
移动端页面入口配置
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
获取具有指定权限的用户列表
基本信息
- 接口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; // 头像URL
}[];
total: number; // 总记录数
}
响应示例
{
"code": 200,
"msg": "操作成功",
"rows": [
{
"userId": "1",
"userName": "admin",
"nickName": "管理员",
"dept": {
"deptId": "1",
"deptName": "后勤部"
},
"avatar": "http://example.com/avatar.jpg"
}
],
"total": 1
}
错误码
| 错误码 | 说明 |
|---|---|
| 401 | 未登录或登录超时 |
| 403 | 没有权限 |
| 500 | 服务器内部错误 |
注意事项
- 该接口需要用户登录后才能访问
- 返回的用户信息已过滤敏感字段
- 建议前端对结果进行缓存,避免频繁请求
- 返回结果按照用户ID升序排序
使用示例
// 前端调用示例
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 [];
}
}