refactor: 重构Sa-Token认证模块

- 移除CoderSaTokenInterceptor拦截器,改用Filter方式
- 优化CoderSaTokenPasswordUtil密码工具类
- 完善CoderSaTokenStpInterfaceImpl权限接口实现
- 更新SaTokenConfigure配置类,优化路径排除规则
- 新增前台用户认证服务HrtUserAuthService
- 新增Sa-Token工具类SaTokenUtil,封装常用操作
- 更新pom.xml依赖配置
This commit is contained in:
Leo 2025-10-13 20:34:13 +08:00
parent 5d3d092bbb
commit a45a400238
8 changed files with 454 additions and 76 deletions

View File

@ -53,6 +53,12 @@
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-aop</artifactId>
</dependency>
<!-- BCrypt密码加密 -->
<dependency>
<groupId>org.mindrot</groupId>
<artifactId>jbcrypt</artifactId>
<version>0.4</version>
</dependency>
</dependencies>
</project>

View File

@ -1,16 +1,20 @@
package org.leocoder.heritage.satoken.anno;
import org.leocoder.heritage.satoken.config.CoderSaTokenFilter;
import org.leocoder.heritage.satoken.config.CoderSaTokenInterceptor;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
/**
* @author Leocoder
* @description [启用Sa-Token配置注解]
* SaTokenConfigure配置类已通过@Configuration自动加载无需@Import
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({ CoderSaTokenInterceptor.class, CoderSaTokenFilter.class })
@Import({ CoderSaTokenFilter.class })
public @interface EnableCoderSaToken {
}

View File

@ -1,68 +0,0 @@
package org.leocoder.heritage.satoken.config;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.ArrayList;
import java.util.List;
/**
* @author Leocoder
* @description [Sa-Token代码方式进行配置]
*/
@Configuration
public class CoderSaTokenInterceptor implements WebMvcConfigurer {
@Value("${coder.filePath}")
private String baseFilePath;
/**
* @description [注册拦截器]
* 使用后必须携带 Authorization[此名称在yml中可自行配置]- Bearer token值[除非不被拦截但是获取不到当前会话用户ID]
* @author Leocoder
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册登录拦截器
registry.addInterceptor(new SaInterceptor(handle -> {
// 白名单
List<String> ignoreUrls = new ArrayList<>();
// favicon.ico浏览器标签Logo
ignoreUrls.add("/favicon.ico");
// 验证码
ignoreUrls.add("/captcha/**");
// 登录退出登录
ignoreUrls.add("/auth/**");
// 测试接口
// ignoreUrls.add("/coder/**");
// 上传文件接口
ignoreUrls.add("/CoderFile/**");
// 后端项目详情
ignoreUrls.add("/");
// 静态资源
// ignoreUrls.add("/*.html");
// ignoreUrls.add("/**/*.html");
// ignoreUrls.add("/**/*.css");
// ignoreUrls.add("/**/*.js");
// 上传路径
ignoreUrls.add(baseFilePath + "/**");
// Swagger API文档相关路径
ignoreUrls.add("/swagger-ui/**");
ignoreUrls.add("/v3/api-docs/**");
ignoreUrls.add("/v3/api-docs");
ignoreUrls.add("/swagger-ui.html");
ignoreUrls.add("/webjars/**");
// 除白名单路径外均需要登录认证
SaRouter.match("/**").notMatch(ignoreUrls).check(r -> StpUtil.checkLogin());
})).addPathPatterns("/**");
}
}

View File

@ -1,6 +1,7 @@
package org.leocoder.heritage.satoken.config;
import cn.dev33.satoken.secure.SaSecureUtil;
import org.mindrot.jbcrypt.BCrypt;
/**
* @author Leocoder
@ -20,7 +21,7 @@ public class CoderSaTokenPasswordUtil {
/**
* @param salt 盐值随机数
* @param password 密码
* @description [字符串加密函数MD5实现]
* @description [字符串加密函数MD5实现 - 用于后台管理员]
*/
public static String getPassword(String password, String salt) {
String initMd5Pwd = password + salt;
@ -30,6 +31,25 @@ public class CoderSaTokenPasswordUtil {
return initMd5Pwd;
}
/**
* @param password 明文密码
* @description [BCrypt加密密码 - 用于前台用户]
* @author Leocoder
*/
public static String encryptPassword(String password) {
return BCrypt.hashpw(password, BCrypt.gensalt());
}
/**
* @param plainPassword 明文密码
* @param hashedPassword BCrypt加密后的密码
* @description [验证密码是否匹配 - 用于前台用户]
* @author Leocoder
*/
public static boolean matchesPassword(String plainPassword, String hashedPassword) {
return BCrypt.checkpw(plainPassword, hashedPassword);
}
public static void main(String[] args) {
System.out.println("管理员密码:" + getPassword("123456", "20231123"));
System.out.println("Coder密码" + getPassword("123456", "666666"));

View File

@ -8,6 +8,7 @@ import org.leocoder.heritage.common.exception.coder.YUtil;
import org.leocoder.heritage.common.satoken.CoderLoginUtil;
import org.leocoder.heritage.satoken.service.menu.SaMenuService;
import org.leocoder.heritage.satoken.service.role.SaRoleService;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
@ -17,11 +18,12 @@ import java.util.Set;
/**
* @author Leocoder
* @description [StpInterfaceImpl自定义权限验证接口扩展]
* @description [StpInterfaceImpl自定义权限验证接口扩展 - 后台管理员]
*/
@Slf4j
@RequiredArgsConstructor
// 保证此类被SpringBoot扫描完成Sa-Token的自定义权限验证扩展
@Primary
@Component
public class CoderSaTokenStpInterfaceImpl implements StpInterface {

View File

@ -1,6 +1,10 @@
package org.leocoder.heritage.satoken.config;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.stp.StpUtil;
import jakarta.annotation.PostConstruct;
import org.leocoder.heritage.satoken.util.HrtStpUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@ -13,16 +17,88 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
// 注册 Sa-Token 拦截器打开注解式鉴权功能
@Value("${sa-token.token-style:uuid}")
private String tokenStyle;
@Value("${sa-token.timeout:-1}")
private Long timeout;
@Value("${sa-token.active-timeout:-1}")
private Long activeTimeout;
@Value("${sa-token.auto-renew:true}")
private Boolean autoRenew;
@Value("${sa-token.is-concurrent:true}")
private Boolean isConcurrent;
@Value("${sa-token.is-share:false}")
private Boolean isShare;
@Value("${sa-token.is-read-cookie:false}")
private Boolean isReadCookie;
@Value("${sa-token.is-read-header:true}")
private Boolean isReadHeader;
@Value("${sa-token.token-prefix:Bearer}")
private String tokenPrefix;
/**
* @description [初始化前台用户专属配置]
* @author Leocoder
*/
@PostConstruct
public void initHrtStpUtil() {
// 独立初始化前台用户专属配置不影响后端配置
HrtStpUtil.initConfig(
tokenStyle,
timeout,
activeTimeout,
autoRenew,
isConcurrent,
isShare,
isReadCookie,
isReadHeader,
tokenPrefix
);
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册 Sa-Token 拦截器打开注解式鉴权功能
// 注册后台管理拦截器
registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin()))
.addPathPatterns("/coder/**")
.excludePathPatterns(
"/coder/login",
"/coder/captcha/**"
);
// 注册前台业务拦截器
registry.addInterceptor(new SaInterceptor(handle -> HrtStpUtil.checkLogin()))
.addPathPatterns("/api/**")
.excludePathPatterns(
"/api/auth/register",
"/api/auth/login",
"/api/captcha/**",
"/api/heritage/list",
"/api/heritage/detail/**",
"/api/heritage/hot",
"/api/heritage/featured",
"/api/inheritor/list",
"/api/inheritor/detail/**",
"/api/news/list",
"/api/news/detail/**",
"/api/event/list",
"/api/event/detail/**"
);
// 注册全局注解鉴权拦截器
registry.addInterceptor(new SaInterceptor())
.addPathPatterns("/**")
.excludePathPatterns(
// 排除静态资源
"/picture/**",
// 排除其他静态资源
"/favicon.ico",
"/static/**",
"/css/**",
@ -32,7 +108,12 @@ public class SaTokenConfigure implements WebMvcConfigurer {
"/swagger-ui/**",
"/v3/api-docs/**",
"/swagger-ui.html",
"/webjars/**"
"/webjars/**",
// 排除整个前台路径由前台专属拦截器处理
"/api/**",
// 排除后台登录接口
"/coder/login",
"/coder/captcha/**"
);
}

View File

@ -0,0 +1,73 @@
package org.leocoder.heritage.satoken.service.hrt;
import cn.dev33.satoken.stp.StpInterface;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* @author Leocoder
* @description [前台用户权限接口实现]
*
* 设计说明
* 前台用户采用简化的权限模型
* - 只需要登录即可访问所有前台页面
* - 不需要复杂的角色和权限控制
* - 所有登录用户拥有相同的访问权限
*
* 实现方式
* - 角色统一返回 "user" 角色
* - 权限返回通配符 "*"表示拥有所有权限
* - 拦截器仅检查登录状态HrtStpUtil.checkLogin()
*
* 注意事项
* - 前台Controller不应添加 @SaCheckPermission 注解
* - 如需权限控制应在业务层实现而非使用Sa-Token权限系统
*/
@Slf4j
@RequiredArgsConstructor
@Component
public class HrtStpInterfaceImpl implements StpInterface {
/**
* @description [返回前台用户拥有的角色标识集合]
* @author Leocoder
*/
@Override
public List<String> getRoleList(Object loginId, String loginType) {
// 只处理前台用户loginType="hrt"
if (!"hrt".equals(loginType)) {
return new ArrayList<>();
}
// 前台用户统一为普通用户角色
List<String> roleList = new ArrayList<>();
roleList.add("user");
log.debug("前台用户[{}]角色列表:{}", loginId, roleList);
return roleList;
}
/**
* @description [返回前台用户拥有的权限码集合]
* @author Leocoder
*/
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
// 只处理前台用户loginType="hrt"
if (!"hrt".equals(loginType)) {
return new ArrayList<>();
}
// 前台用户拥有所有权限通配符 "*"
List<String> permissionList = new ArrayList<>();
permissionList.add("*");
log.debug("前台用户[{}]权限列表:{}", loginId, permissionList);
return permissionList;
}
}

View File

@ -0,0 +1,260 @@
package org.leocoder.heritage.satoken.util;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.stp.StpUtil;
import java.util.List;
/**
* @author Leocoder
* @description [前台用户鉴权工具类]
*/
public class HrtStpUtil {
/**
* 前台用户账号类型标识
*/
public static final String TYPE = "hrt";
/**
* 前台用户专属StpLogic实例
*/
private static StpLogic stpLogic;
/**
* @description [初始化前台用户专属配置完全独立不影响后端配置]
* @author Leocoder
*/
public static void initConfig(String tokenStyle, Long timeout, Long activeTimeout,
Boolean autoRenew, Boolean isConcurrent, Boolean isShare,
Boolean isReadCookie, Boolean isReadHeader, String tokenPrefix) {
// 创建前台用户专属配置完全独立的新对象
SaTokenConfig hrtConfig = new SaTokenConfig();
// 设置前台专属的tokenName
hrtConfig.setTokenName("hrt-token");
// 前台不需要token前缀直接使用token值
hrtConfig.setTokenPrefix("");
// 继承其他配置项
hrtConfig.setTokenStyle(tokenStyle);
hrtConfig.setTimeout(timeout);
hrtConfig.setActiveTimeout(activeTimeout);
hrtConfig.setAutoRenew(autoRenew);
hrtConfig.setIsConcurrent(isConcurrent);
hrtConfig.setIsShare(isShare);
hrtConfig.setIsReadCookie(isReadCookie);
hrtConfig.setIsReadHeader(isReadHeader);
// 使用专属配置创建StpLogic实例
stpLogic = new StpLogic(TYPE);
stpLogic.setConfig(hrtConfig);
}
// 静态初始化块创建默认StpLogic实例防止Spring启动前调用
static {
if (stpLogic == null) {
stpLogic = new StpLogic(TYPE);
}
}
/**
* @description [获取StpLogic实例]
* @author Leocoder
*/
public static StpLogic getStpLogic() {
return stpLogic;
}
/**
* @description [前台用户登录]
* @author Leocoder
*/
public static void login(Object id) {
stpLogic.login(id);
}
/**
* @description [前台用户登录并指定登录设备]
* @author Leocoder
*/
public static void login(Object id, String device) {
stpLogic.login(id, device);
}
/**
* @description [前台用户登出]
* @author Leocoder
*/
public static void logout() {
stpLogic.logout();
}
/**
* @description [前台用户登出并指定登录id]
* @author Leocoder
*/
public static void logout(Object loginId) {
stpLogic.logout(loginId);
}
/**
* @description [检查前台用户是否登录]
* @author Leocoder
*/
public static void checkLogin() {
stpLogic.checkLogin();
}
/**
* @description [判断前台用户是否登录]
* @author Leocoder
*/
public static boolean isLogin() {
return stpLogic.isLogin();
}
/**
* @description [获取前台用户登录id]
* @author Leocoder
*/
public static Object getLoginId() {
return stpLogic.getLoginId();
}
/**
* @description [获取前台用户登录id转换为Long类型]
* @author Leocoder
*/
public static Long getLoginIdAsLong() {
return stpLogic.getLoginIdAsLong();
}
/**
* @description [获取前台用户Token]
* @author Leocoder
*/
public static String getTokenValue() {
return stpLogic.getTokenValue();
}
/**
* @description [获取前台用户Token名称]
* @author Leocoder
*/
public static String getTokenName() {
return stpLogic.getTokenName();
}
/**
* @description [检查前台用户是否拥有指定权限]
* @author Leocoder
*/
public static void checkPermission(String permission) {
stpLogic.checkPermission(permission);
}
/**
* @description [检查前台用户是否拥有指定权限多个权限]
* @author Leocoder
*/
public static void checkPermissionAnd(String... permissions) {
stpLogic.checkPermissionAnd(permissions);
}
/**
* @description [检查前台用户是否拥有指定权限任意一个]
* @author Leocoder
*/
public static void checkPermissionOr(String... permissions) {
stpLogic.checkPermissionOr(permissions);
}
/**
* @description [判断前台用户是否拥有指定权限]
* @author Leocoder
*/
public static boolean hasPermission(String permission) {
return stpLogic.hasPermission(permission);
}
/**
* @description [获取前台用户权限列表]
* @author Leocoder
*/
public static List<String> getPermissionList() {
return stpLogic.getPermissionList();
}
/**
* @description [检查前台用户是否拥有指定角色]
* @author Leocoder
*/
public static void checkRole(String role) {
stpLogic.checkRole(role);
}
/**
* @description [检查前台用户是否拥有指定角色多个角色]
* @author Leocoder
*/
public static void checkRoleAnd(String... roles) {
stpLogic.checkRoleAnd(roles);
}
/**
* @description [检查前台用户是否拥有指定角色任意一个]
* @author Leocoder
*/
public static void checkRoleOr(String... roles) {
stpLogic.checkRoleOr(roles);
}
/**
* @description [判断前台用户是否拥有指定角色]
* @author Leocoder
*/
public static boolean hasRole(String role) {
return stpLogic.hasRole(role);
}
/**
* @description [获取前台用户角色列表]
* @author Leocoder
*/
public static List<String> getRoleList() {
return stpLogic.getRoleList();
}
/**
* @description [踢下线指定用户]
* @author Leocoder
*/
public static void kickout(Object loginId) {
stpLogic.kickout(loginId);
}
/**
* @description [封禁指定用户]
* @author Leocoder
*/
public static void disable(Object loginId, long disableTime) {
stpLogic.disable(loginId, disableTime);
}
/**
* @description [解封指定用户]
* @author Leocoder
*/
public static void untieDisable(Object loginId) {
stpLogic.untieDisable(loginId);
}
/**
* @description [判断指定用户是否被封禁]
* @author Leocoder
*/
public static boolean isDisable(Object loginId) {
return stpLogic.isDisable(loginId);
}
}