优化调整sa-token深入集成

This commit is contained in:
海言
2026-01-13 18:31:38 +08:00
parent ed34396f1a
commit 4803cb6ca6
11 changed files with 194 additions and 35 deletions

11
pom.xml
View File

@@ -126,6 +126,17 @@
<version>${sverlet.version}</version> <!-- 根据需要选择合适的版本 -->
<scope>provided</scope> <!-- 在Web服务器环境中由服务器提供 -->
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot3-starter</artifactId>
<version>${sa-token.version}</version>
</dependency>
<!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis-jackson</artifactId>
<version>${sa-token.version}</version>
</dependency>
</dependencies>
<build>

View File

@@ -23,6 +23,39 @@ import jakarta.servlet.http.HttpServletRequest;
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* Sa-Token: not login exception
*/
@ExceptionHandler(cn.dev33.satoken.exception.NotLoginException.class)
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public GenericResponse<Void> handleNotLoginException(cn.dev33.satoken.exception.NotLoginException e,
HttpServletRequest request) {
log.warn("Not logged in [URL:{}]: {}", request.getRequestURI(), e.getMessage());
return new GenericResponse<>(401, null, "Please login first");
}
/**
* Sa-Token: not permission exception
*/
@ExceptionHandler(cn.dev33.satoken.exception.NotPermissionException.class)
@ResponseStatus(HttpStatus.FORBIDDEN)
public GenericResponse<Void> handleNotPermissionException(cn.dev33.satoken.exception.NotPermissionException e,
HttpServletRequest request) {
log.warn("No permission [URL:{}]: {}", request.getRequestURI(), e.getMessage());
return new GenericResponse<>(403, null, "No permission to access this resource");
}
/**
* Sa-Token: not role exception
*/
@ExceptionHandler(cn.dev33.satoken.exception.NotRoleException.class)
@ResponseStatus(HttpStatus.FORBIDDEN)
public GenericResponse<Void> handleNotRoleException(cn.dev33.satoken.exception.NotRoleException e,
HttpServletRequest request) {
log.warn("No role [URL:{}]: {}", request.getRequestURI(), e.getMessage());
return new GenericResponse<>(403, null, "Insufficient role privileges");
}
/**
* 处理登录/认证异常
*/

View File

@@ -1,6 +1,8 @@
package cn.xf.basedemo.config;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.xf.basedemo.interceptor.SaTokenContextInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@@ -14,10 +16,16 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
*/
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
@Autowired
private SaTokenContextInterceptor saTokenContextInterceptor;
// 注册 Sa-Token 拦截器,打开注解式鉴权功能
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册 Sa-Token 拦截器,打开注解式鉴权功能
registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
// 注册上下文注入拦截器,兼容旧业务代码
registry.addInterceptor(saTokenContextInterceptor).addPathPatterns("/**");
}
}

View File

@@ -2,7 +2,8 @@ package cn.xf.basedemo.controller.business;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaCheckRole;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.dev33.satoken.annotation.SaMode;
import cn.xf.basedemo.common.model.LoginUser;
import cn.xf.basedemo.common.model.RetObj;
import cn.xf.basedemo.interceptor.SessionContext;
@@ -38,10 +39,38 @@ public class UserController {
@Operation(summary = "用户信息", description = "用户信息")
@PostMapping("/info")
@SaCheckPermission("user:info") //权限校验
public RetObj info(){
@SaCheckPermission("user:info") // 权限校验
public RetObj info() {
LoginUser loginUser = SessionContext.getInstance().get();
return RetObj.success(loginUser);
}
@Operation(summary = "注解示例-角色校验", description = "必须具有 'super-admin' 角色才能访问")
@PostMapping("/check-role")
@SaCheckRole("super-admin")
public RetObj checkRole() {
return RetObj.success("您拥有 super-admin 角色,验证通过");
}
@Operation(summary = "注解示例-权限组合(OR)", description = "只要拥有 user:add 或 user:update 其中一个权限即可")
@PostMapping("/check-permission-or")
@SaCheckPermission(value = { "user:add", "user:update" }, mode = SaMode.OR)
public RetObj checkPermissionOr() {
return RetObj.success("您拥有 user:add 或 user:update 权限,验证通过");
}
@Operation(summary = "注解示例-权限组合(AND)", description = "必须同时拥有 user:delete 和 user:export 权限")
@PostMapping("/check-permission-and")
@SaCheckPermission(value = { "user:delete", "user:export" }, mode = SaMode.AND)
public RetObj checkPermissionAnd() {
return RetObj.success("您同时拥有 user:delete 和 user:export 权限,验证通过");
}
@Operation(summary = "注解示例-忽略鉴权", description = "无需登录即可访问(常用于注册、验证码等公开接口)")
@PostMapping("/public-api")
@SaIgnore
public RetObj publicApi() {
return RetObj.success("这是一个公开接口,@SaIgnore 生效");
}
}

View File

@@ -1,7 +1,6 @@
package cn.xf.basedemo.interceptor;
import cn.dev33.satoken.interceptor.SaInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
@@ -17,18 +16,26 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Bean
public TokenInterceptor tokenInterceptor() {
return new TokenInterceptor();
}
@org.springframework.beans.factory.annotation.Autowired
private SaTokenContextInterceptor saTokenContextInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(tokenInterceptor()) //登录逻辑拦截类
.addPathPatterns("/**") //需要拦截的请求(设置的全部拦截)
.excludePathPatterns("/user/login", "/web/**"); //忽略的请求
}
// 注册 Sa-Token 拦截器,定义详细认证规则
registry.addInterceptor(new SaInterceptor(handler -> {
// 指定一条 match 规则
cn.dev33.satoken.stp.StpUtil.checkLogin();
}))
.addPathPatterns("/**")
.excludePathPatterns("/user/login", "/web/**", "/swagger-resources/**", "/webjars/**", "/v3/**",
"/doc.html");
// 注册 Context 拦截器,用于注入 SessionContext
registry.addInterceptor(saTokenContextInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/user/login", "/web/**", "/swagger-resources/**", "/webjars/**", "/v3/**",
"/doc.html");
}
/**
* 放行Knife4j请求

View File

@@ -0,0 +1,41 @@
package cn.xf.basedemo.interceptor;
import cn.dev33.satoken.stp.StpUtil;
import cn.xf.basedemo.common.model.LoginUser;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
/**
* @Description: Sa-Token 上下文拦截器
* 用于将 Sa-Token Session 中的用户信息注入到 SessionContext (ThreadLocal)
* 以兼容旧的业务代码 (SessionContext.getInstance().get())
* @Author: xiongfeng
*/
@Component
public class SaTokenContextInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// 如果已登录,尝试从 Session 中获取用户信息并注入 ThreadLocal
if (StpUtil.isLogin()) {
// 从 Sa-Token Session 中读取 loginUser (需确保登录时已存入)
LoginUser loginUser = (LoginUser) StpUtil.getSession().get("loginUser");
if (loginUser != null) {
SessionContext.getInstance().set(loginUser);
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
// 请求结束后清理 ThreadLocal防止内存泄漏
SessionContext.getInstance().clear();
}
}

View File

@@ -1,9 +1,9 @@
package cn.xf.basedemo.interceptor;
import cn.dev33.satoken.stp.StpInterface;
import cn.xf.basedemo.common.utils.ApplicationContextUtils;
import cn.xf.basedemo.mappers.SysPermissionMapper;
import cn.xf.basedemo.mappers.SysRoleMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
@@ -18,19 +18,23 @@ import java.util.List;
@Component
public class StpInterfaceImpl implements StpInterface {
private SysPermissionMapper sysPermissionMapper = ApplicationContextUtils.getBean(SysPermissionMapper.class);
private SysRoleMapper sysRoleMapper = ApplicationContextUtils.getBean(SysRoleMapper.class);
@Autowired
private SysPermissionMapper sysPermissionMapper;
@Autowired
private SysRoleMapper sysRoleMapper;
@Override
public List<String> getPermissionList(Object userId, String s) {
//获取登录用户权限数据
Long aLong = Long.valueOf(userId.toString());
List<String> permissionList = sysPermissionMapper.getPermissionListByRoleId(aLong);
// 获取登录用户权限数据
Long uId = Long.valueOf(userId.toString());
List<String> permissionList = sysPermissionMapper.getPermissionListByUserId(uId);
return permissionList;
}
@Override
public List<String> getRoleList(Object userId, String s) {
//获取用户角色数据
// 获取用户角色数据
return sysRoleMapper.getRoleListByUserId((Long) userId);
}
}

View File

@@ -7,14 +7,14 @@ import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* @author xiongfeng
* @description 针对表【sys_permission(系统权限表 sys_permission)】的数据库操作Mapper
* @createDate 2025-08-19 21:22:03
* @Entity cn.xf.basedemo.model.domain.SysPermission
*/
* @author xiongfeng
* @description 针对表【sys_permission(系统权限表 sys_permission)】的数据库操作Mapper
* @createDate 2025-08-19 21:22:03
* @Entity cn.xf.basedemo.model.domain.SysPermission
*/
@Mapper
public interface SysPermissionMapper extends BaseMapper<SysPermission> {
List<String> getPermissionListByRoleId(Long useId);
List<String> getPermissionListByUserId(Long userId);
}

View File

@@ -1,7 +1,6 @@
package cn.xf.basedemo.service.impl;
import cn.dev33.satoken.stp.StpUtil;
import cn.xf.basedemo.common.model.EsBaseModel;
import cn.xf.basedemo.common.model.LoginInfo;
import cn.xf.basedemo.common.model.LoginUser;
import cn.xf.basedemo.common.model.RetObj;
@@ -56,7 +55,8 @@ public class UserServiceImpl implements UserService {
}
String loginJson = "";
try {
loginJson = RSAUtils.privateDecryption(res.getEncryptedData(), RSAUtils.getPrivateKey(globalConfig.getRsaPrivateKey()));
loginJson = RSAUtils.privateDecryption(res.getEncryptedData(),
RSAUtils.getPrivateKey(globalConfig.getRsaPrivateKey()));
} catch (Exception e) {
log.error("解密失败------", e);
}
@@ -70,7 +70,7 @@ public class UserServiceImpl implements UserService {
if (!StringUtils.isEmpty(loginInfo.check())) {
return RetObj.error(loginInfo.check());
}
//校验登录账号密码
// 校验登录账号密码
QueryWrapper queryWrapper = new QueryWrapper();
queryWrapper.eq("account", loginInfo.getAccount());
queryWrapper.eq("password", loginInfo.getPwd());
@@ -84,13 +84,16 @@ public class UserServiceImpl implements UserService {
loginUser.setName(user.getName());
loginUser.setPhone(user.getPhone());
String token = JwtTokenUtils.createToken(user.getId());
loginUser.setToken(token);
redisTemplate.opsForValue().set("token:" + token, JSONObject.toJSONString(loginUser), 3600, TimeUnit.SECONDS);
redisTemplate.opsForValue().set("user_login_token:" + user.getId(), token, 3600, TimeUnit.SECONDS);
//登录成功 写入sa-token中
// 登录成功 写入sa-token中
StpUtil.login(user.getId());
// 将用户信息缓存到 Session 中,以便后续获取
StpUtil.getSession().set("loginUser", loginUser);
// 获取 Sa-Token 生成的 token 值
cn.dev33.satoken.stp.SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
loginUser.setToken(tokenInfo.tokenValue);
return RetObj.success(loginUser);
}

View File

@@ -24,3 +24,26 @@ spring:
import:
- nacos:${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
# Sa-Token Configuration
sa-token:
# token name (frontend needs to use this name, e.g., Authorization: Bearer xxxx, or just satoken: xxxx)
token-name: Authorization
# token validity period (seconds), -1 means never expire
timeout: 2592000
# token temporary validity (seconds), -1 means never expire
activity-timeout: -1
# allow concurrent login
is-concurrent: true
# share token api
is-share: true
# token style
token-style: uuid
# log
is-log: false
# read from cookie
is-read-cookie: false
# read from header
is-read-header: true
# read from body
is-read-body: false

View File

@@ -21,7 +21,7 @@
update_time,update_by
</sql>
<select id="getPermissionListByRoleId" resultType="java.lang.String">
<select id="getPermissionListByUserId" resultType="java.lang.String">
select code
from sys_permission p
left join sys_role_permission rp on p.id = rp.permission_id