diff --git a/heritage-plugins/heritage-sa-token/pom.xml b/heritage-plugins/heritage-sa-token/pom.xml
index 8a51280..f2420e8 100644
--- a/heritage-plugins/heritage-sa-token/pom.xml
+++ b/heritage-plugins/heritage-sa-token/pom.xml
@@ -53,6 +53,12 @@
cn.dev33
sa-token-spring-aop
+
+
+ org.mindrot
+ jbcrypt
+ 0.4
+
\ No newline at end of file
diff --git a/heritage-plugins/heritage-sa-token/src/main/java/org/leocoder/heritage/satoken/anno/EnableCoderSaToken.java b/heritage-plugins/heritage-sa-token/src/main/java/org/leocoder/heritage/satoken/anno/EnableCoderSaToken.java
index 578d04e..d9c35d0 100755
--- a/heritage-plugins/heritage-sa-token/src/main/java/org/leocoder/heritage/satoken/anno/EnableCoderSaToken.java
+++ b/heritage-plugins/heritage-sa-token/src/main/java/org/leocoder/heritage/satoken/anno/EnableCoderSaToken.java
@@ -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 {
}
diff --git a/heritage-plugins/heritage-sa-token/src/main/java/org/leocoder/heritage/satoken/config/CoderSaTokenInterceptor.java b/heritage-plugins/heritage-sa-token/src/main/java/org/leocoder/heritage/satoken/config/CoderSaTokenInterceptor.java
deleted file mode 100755
index ce09bf7..0000000
--- a/heritage-plugins/heritage-sa-token/src/main/java/org/leocoder/heritage/satoken/config/CoderSaTokenInterceptor.java
+++ /dev/null
@@ -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 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("/**");
-
- }
-
-}
diff --git a/heritage-plugins/heritage-sa-token/src/main/java/org/leocoder/heritage/satoken/config/CoderSaTokenPasswordUtil.java b/heritage-plugins/heritage-sa-token/src/main/java/org/leocoder/heritage/satoken/config/CoderSaTokenPasswordUtil.java
index df381eb..7db9c4f 100755
--- a/heritage-plugins/heritage-sa-token/src/main/java/org/leocoder/heritage/satoken/config/CoderSaTokenPasswordUtil.java
+++ b/heritage-plugins/heritage-sa-token/src/main/java/org/leocoder/heritage/satoken/config/CoderSaTokenPasswordUtil.java
@@ -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"));
diff --git a/heritage-plugins/heritage-sa-token/src/main/java/org/leocoder/heritage/satoken/config/CoderSaTokenStpInterfaceImpl.java b/heritage-plugins/heritage-sa-token/src/main/java/org/leocoder/heritage/satoken/config/CoderSaTokenStpInterfaceImpl.java
index 7820982..e550fc6 100755
--- a/heritage-plugins/heritage-sa-token/src/main/java/org/leocoder/heritage/satoken/config/CoderSaTokenStpInterfaceImpl.java
+++ b/heritage-plugins/heritage-sa-token/src/main/java/org/leocoder/heritage/satoken/config/CoderSaTokenStpInterfaceImpl.java
@@ -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 {
diff --git a/heritage-plugins/heritage-sa-token/src/main/java/org/leocoder/heritage/satoken/config/SaTokenConfigure.java b/heritage-plugins/heritage-sa-token/src/main/java/org/leocoder/heritage/satoken/config/SaTokenConfigure.java
index 7c5767d..6efff85 100755
--- a/heritage-plugins/heritage-sa-token/src/main/java/org/leocoder/heritage/satoken/config/SaTokenConfigure.java
+++ b/heritage-plugins/heritage-sa-token/src/main/java/org/leocoder/heritage/satoken/config/SaTokenConfigure.java
@@ -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/**"
);
}
diff --git a/heritage-plugins/heritage-sa-token/src/main/java/org/leocoder/heritage/satoken/service/hrt/HrtStpInterfaceImpl.java b/heritage-plugins/heritage-sa-token/src/main/java/org/leocoder/heritage/satoken/service/hrt/HrtStpInterfaceImpl.java
new file mode 100644
index 0000000..7117dfd
--- /dev/null
+++ b/heritage-plugins/heritage-sa-token/src/main/java/org/leocoder/heritage/satoken/service/hrt/HrtStpInterfaceImpl.java
@@ -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 getRoleList(Object loginId, String loginType) {
+ // 只处理前台用户(loginType="hrt")
+ if (!"hrt".equals(loginType)) {
+ return new ArrayList<>();
+ }
+
+ // 前台用户统一为普通用户角色
+ List roleList = new ArrayList<>();
+ roleList.add("user");
+
+ log.debug("前台用户[{}]角色列表:{}", loginId, roleList);
+ return roleList;
+ }
+
+ /**
+ * @description [返回前台用户拥有的权限码集合]
+ * @author Leocoder
+ */
+ @Override
+ public List getPermissionList(Object loginId, String loginType) {
+ // 只处理前台用户(loginType="hrt")
+ if (!"hrt".equals(loginType)) {
+ return new ArrayList<>();
+ }
+
+ // 前台用户拥有所有权限(通配符 "*")
+ List permissionList = new ArrayList<>();
+ permissionList.add("*");
+
+ log.debug("前台用户[{}]权限列表:{}", loginId, permissionList);
+ return permissionList;
+ }
+
+}
diff --git a/heritage-plugins/heritage-sa-token/src/main/java/org/leocoder/heritage/satoken/util/HrtStpUtil.java b/heritage-plugins/heritage-sa-token/src/main/java/org/leocoder/heritage/satoken/util/HrtStpUtil.java
new file mode 100644
index 0000000..24165ed
--- /dev/null
+++ b/heritage-plugins/heritage-sa-token/src/main/java/org/leocoder/heritage/satoken/util/HrtStpUtil.java
@@ -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 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 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);
+ }
+
+}