mirror of
https://github.com/RemainderTime/spring-boot-base-demo.git
synced 2026-02-06 15:10:56 +08:00
优化调整sa-token深入集成
This commit is contained in:
11
pom.xml
11
pom.xml
@@ -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>
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理登录/认证异常
|
||||
*/
|
||||
|
||||
@@ -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("/**");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 生效");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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请求
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user