11 KiB
11 KiB
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
响应示例:
{
"code": 0,
"message": "success"
}
前端使用:
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
前端使用:
fetch("/auth/logout", {
method: "POST",
credentials: "include",
});
2.3 检测登录状态
接口: GET /open/check-login
响应示例(已登录):
{
"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
请求体:
{
"username": "admin",
"password": "123456"
}
响应示例:
{
"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...
示例:
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
提供以下操作:
基本操作
// 设置缓存
<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操作
// 设置字段值
<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操作
// 添加成员
<T> Long sAdd(String key, T... members);
// 获取所有成员
<T> Set<T> sMembers(String key);
// 删除成员
<T> Long sRemove(String key, T... members);
4.2 使用示例
@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 手动踢用户
@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
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中添加:
// 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. 文件清单
新增文件
com.niuan.erp.common.cache.CacheService- 缓存服务接口com.niuan.erp.common.cache.RedisCacheServiceImpl- Redis缓存实现com.niuan.erp.common.config.RedisConfig- Redis配置com.niuan.erp.common.security.JwtProperties- JWT配置属性com.niuan.erp.common.security.JwtTokenProvider- JWT令牌提供者com.niuan.erp.common.security.TokenService- Token服务com.niuan.erp.common.security.JwtAuthenticationFilter- JWT认证过滤器com.niuan.erp.common.security.UserTokenService- 用户令牌管理服务com.niuan.erp.module.open.controller.OpenController- 公开接口控制器com.niuan.erp.module.api.controller.MiniProgramAuthController- 小程序认证接口com.niuan.erp.module.api.controller.ApiDemoController- API演示控制器
修改文件
com.niuan.erp.common.config.SecurityConfig- 安全配置(同时支持Session和JWT)com.niuan.erp.common.bean.UrlPermissionAuthorizationManager- 添加open和api路径放行com.niuan.erp.module.sys.service.SysUserService- 添加loadUserById等方法com.niuan.erp.module.sys.service.impl.SysUserServiceImpl- 实现新方法com.niuan.erp.module.sys.mapper.SysUserMapper- 添加查询方法com.niuan.erp.module.sys.service.impl.SysPermissionServiceImpl- 添加踢用户逻辑com.niuan.erp.module.sys.service.impl.SysRoleServiceImpl- 添加踢用户逻辑build.gradle.kts- 添加Redis依赖application.yml- 添加Redis和JWT配置teek-design-vue3-template-main/src/common/api/user.ts- 添加checkLogin方法
9. 注意事项
- Redis必须运行:系统启动前确保Redis服务已启动
- JWT密钥安全:生产环境请使用复杂的密钥,并妥善保管
- 跨域配置:小程序端需要配置相应的CORS允许域名
- Token过期处理:小程序端需要处理Token过期,自动调用刷新接口
- 权限配置:API Demo接口需要
api:demo权限,需要在数据库中添加该权限
10. 前端使用示例
Web端(Vue)
// 登录
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();
};
小程序端
// 登录
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();
};