feat: 1. 新加入更改权限踢人功能。2. 使用几个 AI 工具测试工具效果。

This commit is contained in:
c
2026-03-18 15:29:22 +08:00
parent 8bb7dcca31
commit e142d099cc
75 changed files with 3524 additions and 3 deletions

450
docs/jwt-redis-usage.md Normal file
View File

@@ -0,0 +1,450 @@
# JWT + Redis + Session 双认证方式使用文档
## 概述
本文档描述了ERP系统中同时支持两种认证方式的使用说明
- **Web端后台管理**传统Session方式基于Cookie
- **小程序端**JWT方式基于Token存储于Redis
两种认证方式同时启用,互不影响。
## 1. 认证方式对比
| 特性 | Web端Session | 小程序JWT |
|------|-----------------|--------------|
| 认证机制 | Cookie + Session | TokenHeader |
| 登录接口 | POST `/auth/login` | POST `/api/auth/login` |
| 登出接口 | POST `/auth/logout` | POST `/api/auth/logout` |
| Token存储 | 服务端Session + Cookie | Redis + 客户端存储 |
| 适用场景 | 浏览器后台管理 | 小程序/移动端 |
| 单点登录 | 支持(同一账号同时只能一处登录) | 支持多设备登录 |
## 2. Web端Session方式
### 2.1 登录
**接口:** `POST /auth/login`
**请求方式:** 表单提交Content-Type: application/x-www-form-urlencoded
**请求参数:**
```
username=admin&password=123456
```
**响应示例:**
```json
{
"code": 0,
"message": "success"
}
```
**前端使用:**
```typescript
const loginParams = new URLSearchParams();
loginParams.append("username", username);
loginParams.append("password", password);
fetch("/auth/login", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: loginParams,
credentials: "include", // 重要携带Cookie
});
```
### 2.2 登出
**接口:** `POST /auth/logout`
**前端使用:**
```typescript
fetch("/auth/logout", {
method: "POST",
credentials: "include",
});
```
### 2.3 检测登录状态
**接口:** `GET /open/check-login`
**响应示例(已登录):**
```json
{
"code": 0,
"message": "success",
"data": {
"isLoggedIn": true,
"userInfo": {
"userId": 1,
"loginName": "admin",
"userName": "管理员"
},
"message": "登录状态有效Session"
}
}
```
## 3. 小程序端JWT方式
### 3.1 登录
**接口:** `POST /api/auth/login`
**Content-Type** `application/json`
**请求体:**
```json
{
"username": "admin",
"password": "123456"
}
```
**响应示例:**
```json
{
"code": 0,
"message": "success",
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiJ9...",
"expiresIn": 86400,
"refreshExpiresIn": 604800,
"userId": 1,
"loginName": "admin",
"userName": "管理员"
}
}
```
### 3.2 请求携带Token
在请求头中添加Authorization字段
```
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
```
**示例:**
```typescript
fetch("/api/some-endpoint", {
method: "GET",
headers: {
"Authorization": "Bearer " + accessToken,
},
});
```
### 3.3 刷新Token
**接口:** `POST /api/auth/refresh`
**请求参数:**
```
refreshToken=eyJhbGciOiJIUzI1NiJ9...
```
**响应示例:** 返回新的accessToken和refreshToken
### 3.4 登出
**接口:** `POST /api/auth/logout`
**请求头:**
```
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
```
**说明:** 将当前token加入黑名单使其失效
## 4. Redis缓存服务
### 4.1 缓存服务接口 (CacheService)
位置:`com.niuan.erp.common.cache.CacheService`
提供以下操作:
#### 基本操作
```java
// 设置缓存
<T> void set(String key, T value);
// 设置缓存并设置过期时间
<T> void set(String key, T value, long timeout, TimeUnit unit);
// 获取缓存
<T> T get(String key);
// 删除缓存
Boolean delete(String key);
// 判断key是否存在
Boolean hasKey(String key);
```
#### Hash操作
```java
// 设置字段值
<T> void hSet(String key, String field, T value);
// 获取字段值
<T> T hGet(String key, String field);
// 获取所有字段和值
<T> Map<String, T> hGetAll(String key);
// 删除字段
Long hDelete(String key, Object... fields);
```
#### Set操作
```java
// 添加成员
<T> Long sAdd(String key, T... members);
// 获取所有成员
<T> Set<T> sMembers(String key);
// 删除成员
<T> Long sRemove(String key, T... members);
```
### 4.2 使用示例
```java
@Service
@RequiredArgsConstructor
public class SomeService {
private final CacheService cacheService;
public void example() {
// 设置缓存1小时过期
cacheService.set("user:1", user, 3600, TimeUnit.SECONDS);
// 获取缓存
User user = cacheService.get("user:1");
// Hash操作
cacheService.hSet("user:1:profile", "email", "user@example.com");
String email = cacheService.hGet("user:1:profile", "email");
}
}
```
## 5. 用户强制下线(踢用户)
### 5.1 机制说明
当权限、角色或用户数据发生变化时,系统会自动踢掉相关用户,强制其重新登录。
**Session方式Web端**
- 清除用户缓存
- 用户下次请求时Session验证失败
**JWT方式小程序端**
- 将用户Token加入Redis黑名单
- 用户下次请求时Token验证失败
### 5.2 触发场景
| 操作 | Web端影响 | 小程序端影响 |
|------|----------|-------------|
| 权限修改 | 踢掉所有拥有该权限的用户 | 踢掉所有拥有该权限的用户 |
| 权限删除 | 踢掉所有拥有该权限的用户 | 踢掉所有拥有该权限的用户 |
| 角色修改 | 踢掉所有拥有该角色的用户 | 踢掉所有拥有该角色的用户 |
| 角色删除 | 踢掉所有拥有该角色的用户 | 踢掉所有拥有该角色的用户 |
| 用户修改 | 踢掉该用户 | 踢掉该用户 |
| 用户删除 | 踢掉该用户 | 踢掉该用户 |
### 5.3 手动踢用户
```java
@Service
@RequiredArgsConstructor
public class SomeService {
private final UserTokenService userTokenService;
public void kickUserExample(Long userId) {
// 踢掉指定用户同时踢掉Session和JWT
userTokenService.kickUser(userId);
// 踢掉拥有指定角色的所有用户
userTokenService.kickUsersByRole(roleId);
// 踢掉拥有指定权限的所有用户
userTokenService.kickUsersByPermission(permissionId);
}
}
```
## 6. 配置说明
### 6.1 application.yml
```yaml
spring:
data:
redis:
host: localhost
port: 6379
password:
database: 0
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
max-wait: 1000ms
# JWT配置
jwt:
secret: your-256-bit-secret-key-here-must-be-at-least-32-characters
expiration: 86400000 # 访问令牌过期时间24小时
refresh-expiration: 604800000 # 刷新令牌过期时间7天
```
### 6.2 依赖
已在build.gradle.kts中添加
```kotlin
// Redis
implementation("org.springframework.boot:spring-boot-starter-data-redis")
implementation("org.apache.commons:commons-pool2")
```
## 7. 接口清单
### 7.1 Web端接口Session
| 接口 | 方法 | 说明 |
|------|------|------|
| `/auth/login` | POST | 登录(表单提交) |
| `/auth/logout` | POST | 登出 |
| `/open/check-login` | GET | 检测登录状态 |
### 7.2 小程序接口JWT
| 接口 | 方法 | 说明 |
|------|------|------|
| `/api/auth/login` | POST | 登录JSON |
| `/api/auth/refresh` | POST | 刷新Token |
| `/api/auth/logout` | POST | 登出 |
| `/open/check-login` | GET | 检测登录状态 |
### 7.3 API演示接口
| 接口 | 方法 | 说明 |
|------|------|------|
| `/api/demo/public` | GET | 公开接口(无需认证) |
| `/api/demo/protected` | GET | 受保护接口(需要认证) |
| `/api/demo/user-info` | GET | 获取当前用户信息 |
| `/api/demo/cache-test` | POST | 缓存测试 |
| `/api/demo/kick-user` | POST | 踢掉指定用户 |
## 8. 文件清单
### 新增文件
1. `com.niuan.erp.common.cache.CacheService` - 缓存服务接口
2. `com.niuan.erp.common.cache.RedisCacheServiceImpl` - Redis缓存实现
3. `com.niuan.erp.common.config.RedisConfig` - Redis配置
4. `com.niuan.erp.common.security.JwtProperties` - JWT配置属性
5. `com.niuan.erp.common.security.JwtTokenProvider` - JWT令牌提供者
6. `com.niuan.erp.common.security.TokenService` - Token服务
7. `com.niuan.erp.common.security.JwtAuthenticationFilter` - JWT认证过滤器
8. `com.niuan.erp.common.security.UserTokenService` - 用户令牌管理服务
9. `com.niuan.erp.module.open.controller.OpenController` - 公开接口控制器
10. `com.niuan.erp.module.api.controller.MiniProgramAuthController` - 小程序认证接口
11. `com.niuan.erp.module.api.controller.ApiDemoController` - API演示控制器
### 修改文件
1. `com.niuan.erp.common.config.SecurityConfig` - 安全配置同时支持Session和JWT
2. `com.niuan.erp.common.bean.UrlPermissionAuthorizationManager` - 添加open和api路径放行
3. `com.niuan.erp.module.sys.service.SysUserService` - 添加loadUserById等方法
4. `com.niuan.erp.module.sys.service.impl.SysUserServiceImpl` - 实现新方法
5. `com.niuan.erp.module.sys.mapper.SysUserMapper` - 添加查询方法
6. `com.niuan.erp.module.sys.service.impl.SysPermissionServiceImpl` - 添加踢用户逻辑
7. `com.niuan.erp.module.sys.service.impl.SysRoleServiceImpl` - 添加踢用户逻辑
8. `build.gradle.kts` - 添加Redis依赖
9. `application.yml` - 添加Redis和JWT配置
10. `teek-design-vue3-template-main/src/common/api/user.ts` - 添加checkLogin方法
## 9. 注意事项
1. **Redis必须运行**系统启动前确保Redis服务已启动
2. **JWT密钥安全**:生产环境请使用复杂的密钥,并妥善保管
3. **跨域配置**小程序端需要配置相应的CORS允许域名
4. **Token过期处理**小程序端需要处理Token过期自动调用刷新接口
5. **权限配置**API Demo接口需要 `api:demo` 权限,需要在数据库中添加该权限
## 10. 前端使用示例
### Web端Vue
```typescript
// 登录
const login = async (username: string, password: string) => {
const params = new URLSearchParams();
params.append("username", username);
params.append("password", password);
const response = await fetch("/auth/login", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: params,
credentials: "include",
});
return response.json();
};
// 检测登录状态
const checkLogin = async () => {
const response = await fetch("/open/check-login", {
credentials: "include",
});
return response.json();
};
```
### 小程序端
```typescript
// 登录
const login = async (username: string, password: string) => {
const response = await fetch("/api/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, password }),
});
const data = await response.json();
// 保存token
if (data.code === 0) {
wx.setStorageSync("accessToken", data.data.accessToken);
wx.setStorageSync("refreshToken", data.data.refreshToken);
}
return data;
};
// 请求携带token
const requestWithToken = async (url: string, options: any = {}) => {
const token = wx.getStorageSync("accessToken");
const response = await fetch(url, {
...options,
headers: {
...options.headers,
"Authorization": "Bearer " + token,
},
});
return response.json();
};
```