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); + } + +}