feat: 1. 新加入更改权限踢人功能。2. 使用几个 AI 工具测试工具效果。
This commit is contained in:
450
docs/jwt-redis-usage.md
Normal file
450
docs/jwt-redis-usage.md
Normal file
@@ -0,0 +1,450 @@
|
||||
# JWT + Redis + Session 双认证方式使用文档
|
||||
|
||||
## 概述
|
||||
|
||||
本文档描述了ERP系统中同时支持两种认证方式的使用说明:
|
||||
- **Web端(后台管理)**:传统Session方式(基于Cookie)
|
||||
- **小程序端**:JWT方式(基于Token,存储于Redis)
|
||||
|
||||
两种认证方式同时启用,互不影响。
|
||||
|
||||
## 1. 认证方式对比
|
||||
|
||||
| 特性 | Web端(Session) | 小程序(JWT) |
|
||||
|------|-----------------|--------------|
|
||||
| 认证机制 | Cookie + Session | Token(Header) |
|
||||
| 登录接口 | 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();
|
||||
};
|
||||
```
|
||||
Reference in New Issue
Block a user