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

830 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 用户认证登录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`)升序排序,保持前端下拉展示一致性