Files
erp-backend/docs/jwt-redis-usage.md

11 KiB
Raw Blame History

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

响应示例:

{
  "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. 文件清单

新增文件

  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

// 登录
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();
};