From 6a93f233644afd87336b72db1a89aa37fc9460e9 Mon Sep 17 00:00:00 2001 From: bai <173792339@qq.com> Date: Sun, 28 Jul 2024 16:43:17 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/.gitignore | 8 + .idea/compiler.xml | 13 + .idea/encodings.xml | 7 + .idea/jarRepositories.xml | 30 ++ .idea/misc.xml | 18 + .idea/vcs.xml | 6 + pom.xml | 39 +++ .../annotation/EnableCustomConfig.java | 26 ++ .../annotation/EnableMyFeignClients.java | 27 ++ .../common/security/annotation/InnerAuth.java | 18 + .../common/security/annotation/Logical.java | 18 + .../security/annotation/RequiresLogin.java | 16 + .../annotation/RequiresPermissions.java | 25 ++ .../security/annotation/RequiresRoles.java | 25 ++ .../security/aspect/InnerAuthAspect.java | 46 +++ .../security/aspect/PreAuthorizeAspect.java | 89 +++++ .../muyu/common/security/auth/AuthLogic.java | 327 ++++++++++++++++++ .../muyu/common/security/auth/AuthUtil.java | 154 +++++++++ .../security/config/ApplicationConfig.java | 21 ++ .../common/security/config/WebMvcConfig.java | 32 ++ .../feign/FeignAutoConfiguration.java | 18 + .../feign/FeignRequestInterceptor.java | 48 +++ .../handler/GlobalExceptionHandler.java | 147 ++++++++ .../interceptor/HeaderInterceptor.java | 50 +++ .../common/security/service/TokenService.java | 153 ++++++++ .../muyu/common/security/utils/DictUtils.java | 71 ++++ .../common/security/utils/SecurityUtils.java | 109 ++++++ ...ot.autoconfigure.AutoConfiguration.imports | 5 + ...ot.autoconfigure.AutoConfiguration.imports | 5 + .../annotation/EnableCustomConfig.class | Bin 0 -> 913 bytes .../annotation/EnableMyFeignClients.class | Bin 0 -> 827 bytes .../security/annotation/InnerAuth.class | Bin 0 -> 504 bytes .../common/security/annotation/Logical.class | Bin 0 -> 1127 bytes .../security/annotation/RequiresLogin.class | Bin 0 -> 427 bytes .../annotation/RequiresPermissions.class | Bin 0 -> 633 bytes .../security/annotation/RequiresRoles.class | Bin 0 -> 621 bytes .../security/aspect/InnerAuthAspect.class | Bin 0 -> 2122 bytes .../security/aspect/PreAuthorizeAspect.class | Bin 0 -> 2733 bytes .../muyu/common/security/auth/AuthLogic.class | Bin 0 -> 7638 bytes .../muyu/common/security/auth/AuthUtil.class | Bin 0 -> 2401 bytes .../security/config/ApplicationConfig.class | Bin 0 -> 1631 bytes .../common/security/config/WebMvcConfig.class | Bin 0 -> 1684 bytes .../feign/FeignAutoConfiguration.class | Bin 0 -> 691 bytes .../feign/FeignRequestInterceptor.class | Bin 0 -> 2067 bytes .../handler/GlobalExceptionHandler.class | Bin 0 -> 7704 bytes .../interceptor/HeaderInterceptor.class | Bin 0 -> 2356 bytes .../security/service/TokenService.class | Bin 0 -> 5797 bytes .../common/security/utils/DictUtils.class | Bin 0 -> 2819 bytes .../common/security/utils/SecurityUtils.class | Bin 0 -> 2697 bytes target/cloud-common-security-3.6.3.jar | Bin 0 -> 27533 bytes target/maven-archiver/pom.properties | 3 + .../compile/default-compile/createdFiles.lst | 20 ++ .../compile/default-compile/inputFiles.lst | 20 ++ 53 files changed, 1594 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/compiler.xml create mode 100644 .idea/encodings.xml create mode 100644 .idea/jarRepositories.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/vcs.xml create mode 100644 pom.xml create mode 100644 src/main/java/com/muyu/common/security/annotation/EnableCustomConfig.java create mode 100644 src/main/java/com/muyu/common/security/annotation/EnableMyFeignClients.java create mode 100644 src/main/java/com/muyu/common/security/annotation/InnerAuth.java create mode 100644 src/main/java/com/muyu/common/security/annotation/Logical.java create mode 100644 src/main/java/com/muyu/common/security/annotation/RequiresLogin.java create mode 100644 src/main/java/com/muyu/common/security/annotation/RequiresPermissions.java create mode 100644 src/main/java/com/muyu/common/security/annotation/RequiresRoles.java create mode 100644 src/main/java/com/muyu/common/security/aspect/InnerAuthAspect.java create mode 100644 src/main/java/com/muyu/common/security/aspect/PreAuthorizeAspect.java create mode 100644 src/main/java/com/muyu/common/security/auth/AuthLogic.java create mode 100644 src/main/java/com/muyu/common/security/auth/AuthUtil.java create mode 100644 src/main/java/com/muyu/common/security/config/ApplicationConfig.java create mode 100644 src/main/java/com/muyu/common/security/config/WebMvcConfig.java create mode 100644 src/main/java/com/muyu/common/security/feign/FeignAutoConfiguration.java create mode 100644 src/main/java/com/muyu/common/security/feign/FeignRequestInterceptor.java create mode 100644 src/main/java/com/muyu/common/security/handler/GlobalExceptionHandler.java create mode 100644 src/main/java/com/muyu/common/security/interceptor/HeaderInterceptor.java create mode 100644 src/main/java/com/muyu/common/security/service/TokenService.java create mode 100644 src/main/java/com/muyu/common/security/utils/DictUtils.java create mode 100644 src/main/java/com/muyu/common/security/utils/SecurityUtils.java create mode 100644 src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports create mode 100644 target/classes/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports create mode 100644 target/classes/com/muyu/common/security/annotation/EnableCustomConfig.class create mode 100644 target/classes/com/muyu/common/security/annotation/EnableMyFeignClients.class create mode 100644 target/classes/com/muyu/common/security/annotation/InnerAuth.class create mode 100644 target/classes/com/muyu/common/security/annotation/Logical.class create mode 100644 target/classes/com/muyu/common/security/annotation/RequiresLogin.class create mode 100644 target/classes/com/muyu/common/security/annotation/RequiresPermissions.class create mode 100644 target/classes/com/muyu/common/security/annotation/RequiresRoles.class create mode 100644 target/classes/com/muyu/common/security/aspect/InnerAuthAspect.class create mode 100644 target/classes/com/muyu/common/security/aspect/PreAuthorizeAspect.class create mode 100644 target/classes/com/muyu/common/security/auth/AuthLogic.class create mode 100644 target/classes/com/muyu/common/security/auth/AuthUtil.class create mode 100644 target/classes/com/muyu/common/security/config/ApplicationConfig.class create mode 100644 target/classes/com/muyu/common/security/config/WebMvcConfig.class create mode 100644 target/classes/com/muyu/common/security/feign/FeignAutoConfiguration.class create mode 100644 target/classes/com/muyu/common/security/feign/FeignRequestInterceptor.class create mode 100644 target/classes/com/muyu/common/security/handler/GlobalExceptionHandler.class create mode 100644 target/classes/com/muyu/common/security/interceptor/HeaderInterceptor.class create mode 100644 target/classes/com/muyu/common/security/service/TokenService.class create mode 100644 target/classes/com/muyu/common/security/utils/DictUtils.class create mode 100644 target/classes/com/muyu/common/security/utils/SecurityUtils.class create mode 100644 target/cloud-common-security-3.6.3.jar create mode 100644 target/maven-archiver/pom.properties create mode 100644 target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst create mode 100644 target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..35410ca --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..7f16c29 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..aa00ffa --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..872d41f --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..b4cc47a --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,18 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..f1a480a --- /dev/null +++ b/pom.xml @@ -0,0 +1,39 @@ + + + + com.muyu + cloud-common + 3.6.3 + + 4.0.0 + + cloud-common-security + + + cloud-common-security安全模块 + + + + + + + org.springframework + spring-webmvc + + + + + com.muyu + cloud-common-redis + + + + + com.muyu + cloud-common-system + + + + + diff --git a/src/main/java/com/muyu/common/security/annotation/EnableCustomConfig.java b/src/main/java/com/muyu/common/security/annotation/EnableCustomConfig.java new file mode 100644 index 0000000..ca7a07a --- /dev/null +++ b/src/main/java/com/muyu/common/security/annotation/EnableCustomConfig.java @@ -0,0 +1,26 @@ +package com.muyu.common.security.annotation; + +import com.muyu.common.security.config.ApplicationConfig; +import com.muyu.common.security.feign.FeignAutoConfiguration; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.context.annotation.Import; +import org.springframework.scheduling.annotation.EnableAsync; + +import java.lang.annotation.*; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +// 表示通过aop框架暴露该代理对象,AopContext能够访问 +@EnableAspectJAutoProxy(exposeProxy = true) +// 指定要扫描的Mapper类的包的路径 +@MapperScan("com.muyu.**.mapper") +// 开启线程异步执行 +@EnableAsync +// 自动加载类 +@Import({ApplicationConfig.class, FeignAutoConfiguration.class}) +public @interface EnableCustomConfig { + +} diff --git a/src/main/java/com/muyu/common/security/annotation/EnableMyFeignClients.java b/src/main/java/com/muyu/common/security/annotation/EnableMyFeignClients.java new file mode 100644 index 0000000..7a59fa4 --- /dev/null +++ b/src/main/java/com/muyu/common/security/annotation/EnableMyFeignClients.java @@ -0,0 +1,27 @@ +package com.muyu.common.security.annotation; + +import org.springframework.cloud.openfeign.EnableFeignClients; + +import java.lang.annotation.*; + +/** + * 自定义feign注解 + * 添加basePackages路径 + * + * @author muyu + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@EnableFeignClients +public @interface EnableMyFeignClients { + String[] value () default {}; + + String[] basePackages () default {"com.muyu"}; + + Class[] basePackageClasses () default {}; + + Class[] defaultConfiguration () default {}; + + Class[] clients () default {}; +} diff --git a/src/main/java/com/muyu/common/security/annotation/InnerAuth.java b/src/main/java/com/muyu/common/security/annotation/InnerAuth.java new file mode 100644 index 0000000..092a573 --- /dev/null +++ b/src/main/java/com/muyu/common/security/annotation/InnerAuth.java @@ -0,0 +1,18 @@ +package com.muyu.common.security.annotation; + +import java.lang.annotation.*; + +/** + * 内部认证注解 + * + * @author muyu + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface InnerAuth { + /** + * 是否校验用户信息 + */ + boolean isUser () default false; +} diff --git a/src/main/java/com/muyu/common/security/annotation/Logical.java b/src/main/java/com/muyu/common/security/annotation/Logical.java new file mode 100644 index 0000000..0be306a --- /dev/null +++ b/src/main/java/com/muyu/common/security/annotation/Logical.java @@ -0,0 +1,18 @@ +package com.muyu.common.security.annotation; + +/** + * 权限注解的验证模式 + * + * @author muyu + */ +public enum Logical { + /** + * 必须具有所有的元素 + */ + AND, + + /** + * 只需具有其中一个元素 + */ + OR +} diff --git a/src/main/java/com/muyu/common/security/annotation/RequiresLogin.java b/src/main/java/com/muyu/common/security/annotation/RequiresLogin.java new file mode 100644 index 0000000..4eff911 --- /dev/null +++ b/src/main/java/com/muyu/common/security/annotation/RequiresLogin.java @@ -0,0 +1,16 @@ +package com.muyu.common.security.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 登录认证:只有登录之后才能进入该方法 + * + * @author muyu + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.TYPE}) +public @interface RequiresLogin { +} diff --git a/src/main/java/com/muyu/common/security/annotation/RequiresPermissions.java b/src/main/java/com/muyu/common/security/annotation/RequiresPermissions.java new file mode 100644 index 0000000..8d95bb4 --- /dev/null +++ b/src/main/java/com/muyu/common/security/annotation/RequiresPermissions.java @@ -0,0 +1,25 @@ +package com.muyu.common.security.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 权限认证:必须具有指定权限才能进入该方法 + * + * @author muyu + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.TYPE}) +public @interface RequiresPermissions { + /** + * 需要校验的权限码 + */ + String[] value () default {}; + + /** + * 验证模式:AND | OR,默认AND + */ + Logical logical () default Logical.AND; +} diff --git a/src/main/java/com/muyu/common/security/annotation/RequiresRoles.java b/src/main/java/com/muyu/common/security/annotation/RequiresRoles.java new file mode 100644 index 0000000..78911cc --- /dev/null +++ b/src/main/java/com/muyu/common/security/annotation/RequiresRoles.java @@ -0,0 +1,25 @@ +package com.muyu.common.security.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 角色认证:必须具有指定角色标识才能进入该方法 + * + * @author muyu + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.TYPE}) +public @interface RequiresRoles { + /** + * 需要校验的角色标识 + */ + String[] value () default {}; + + /** + * 验证逻辑:AND | OR,默认AND + */ + Logical logical () default Logical.AND; +} diff --git a/src/main/java/com/muyu/common/security/aspect/InnerAuthAspect.java b/src/main/java/com/muyu/common/security/aspect/InnerAuthAspect.java new file mode 100644 index 0000000..1707742 --- /dev/null +++ b/src/main/java/com/muyu/common/security/aspect/InnerAuthAspect.java @@ -0,0 +1,46 @@ +package com.muyu.common.security.aspect; + +import com.muyu.common.core.constant.SecurityConstants; +import com.muyu.common.core.exception.InnerAuthException; +import com.muyu.common.core.utils.ServletUtils; +import com.muyu.common.core.utils.StringUtils; +import com.muyu.common.security.annotation.InnerAuth; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.core.Ordered; +import org.springframework.stereotype.Component; + +/** + * 内部服务调用验证处理 + * + * @author muyu + */ +@Aspect +@Component +public class InnerAuthAspect implements Ordered { + @Around("@annotation(innerAuth)") + public Object innerAround (ProceedingJoinPoint point, InnerAuth innerAuth) throws Throwable { + String source = ServletUtils.getRequest().getHeader(SecurityConstants.FROM_SOURCE); + // 内部请求验证 + if (!StringUtils.equals(SecurityConstants.INNER, source)) { + throw new InnerAuthException("没有内部访问权限,不允许访问"); + } + + String userid = ServletUtils.getRequest().getHeader(SecurityConstants.DETAILS_USER_ID); + String username = ServletUtils.getRequest().getHeader(SecurityConstants.DETAILS_USERNAME); + // 用户信息验证 + if (innerAuth.isUser() && (StringUtils.isEmpty(userid) || StringUtils.isEmpty(username))) { + throw new InnerAuthException("没有设置用户信息,不允许访问 "); + } + return point.proceed(); + } + + /** + * 确保在权限认证aop执行前执行 + */ + @Override + public int getOrder () { + return Ordered.HIGHEST_PRECEDENCE + 1; + } +} diff --git a/src/main/java/com/muyu/common/security/aspect/PreAuthorizeAspect.java b/src/main/java/com/muyu/common/security/aspect/PreAuthorizeAspect.java new file mode 100644 index 0000000..4cdd933 --- /dev/null +++ b/src/main/java/com/muyu/common/security/aspect/PreAuthorizeAspect.java @@ -0,0 +1,89 @@ +package com.muyu.common.security.aspect; + +import com.muyu.common.security.annotation.RequiresLogin; +import com.muyu.common.security.annotation.RequiresPermissions; +import com.muyu.common.security.annotation.RequiresRoles; +import com.muyu.common.security.auth.AuthUtil; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Method; + +/** + * 基于 Spring Aop 的注解鉴权 + * + * @author kong + */ +@Aspect +@Component +public class PreAuthorizeAspect { + /** + * 定义AOP签名 (切入所有使用鉴权注解的方法) + */ + public static final String POINTCUT_SIGN = " @annotation(com.muyu.common.security.annotation.RequiresLogin) || " + + "@annotation(com.muyu.common.security.annotation.RequiresPermissions) || " + + "@annotation(com.muyu.common.security.annotation.RequiresRoles)"; + + /** + * 构建 + */ + public PreAuthorizeAspect () { + } + + /** + * 声明AOP签名 + */ + @Pointcut(POINTCUT_SIGN) + public void pointcut () { + } + + /** + * 环绕切入 + * + * @param joinPoint 切面对象 + * + * @return 底层方法执行后的返回值 + * + * @throws Throwable 底层方法抛出的异常 + */ + @Around("pointcut()") + public Object around (ProceedingJoinPoint joinPoint) throws Throwable { + // 注解鉴权 + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + checkMethodAnnotation(signature.getMethod()); + try { + // 执行原有逻辑 + Object obj = joinPoint.proceed(); + return obj; + } catch (Throwable e) { + throw e; + } + } + + /** + * 对一个Method对象进行注解检查 + */ + public void checkMethodAnnotation (Method method) { + // 校验 @RequiresLogin 注解 + RequiresLogin requiresLogin = method.getAnnotation(RequiresLogin.class); + if (requiresLogin != null) { + AuthUtil.checkLogin(); + } + + // 校验 @RequiresRoles 注解 + RequiresRoles requiresRoles = method.getAnnotation(RequiresRoles.class); + if (requiresRoles != null) { + AuthUtil.checkRole(requiresRoles); + } + + // 校验 @RequiresPermissions 注解 + RequiresPermissions requiresPermissions = method.getAnnotation(RequiresPermissions.class); + if (requiresPermissions != null) { + AuthUtil.checkPermi(requiresPermissions); + } + } +} diff --git a/src/main/java/com/muyu/common/security/auth/AuthLogic.java b/src/main/java/com/muyu/common/security/auth/AuthLogic.java new file mode 100644 index 0000000..beb3426 --- /dev/null +++ b/src/main/java/com/muyu/common/security/auth/AuthLogic.java @@ -0,0 +1,327 @@ +package com.muyu.common.security.auth; + +import com.muyu.common.core.context.SecurityContextHolder; +import com.muyu.common.core.exception.auth.NotLoginException; +import com.muyu.common.core.exception.auth.NotPermissionException; +import com.muyu.common.core.exception.auth.NotRoleException; +import com.muyu.common.core.utils.SpringUtils; +import com.muyu.common.core.utils.StringUtils; +import com.muyu.common.security.annotation.Logical; +import com.muyu.common.security.annotation.RequiresLogin; +import com.muyu.common.security.annotation.RequiresPermissions; +import com.muyu.common.security.annotation.RequiresRoles; +import com.muyu.common.security.service.TokenService; +import com.muyu.common.security.utils.SecurityUtils; +import com.muyu.common.system.domain.LoginUser; +import org.springframework.util.PatternMatchUtils; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +/** + * Token 权限验证,逻辑实现类 + * + * @author muyu + */ +public class AuthLogic { + /** + * 所有权限标识 + */ + private static final String ALL_PERMISSION = "*:*:*"; + + /** + * 管理员角色权限标识 + */ + private static final String SUPER_ADMIN = "admin"; + + public TokenService tokenService = SpringUtils.getBean(TokenService.class); + + /** + * 会话注销 + */ + public void logout () { + String token = SecurityUtils.getToken(); + if (token == null) { + return; + } + logoutByToken(token); + } + + /** + * 会话注销,根据指定Token + */ + public void logoutByToken (String token) { + tokenService.delLoginUser(token); + } + + /** + * 检验用户是否已经登录,如未登录,则抛出异常 + */ + public void checkLogin () { + getLoginUser(); + } + + /** + * 获取当前用户缓存信息, 如果未登录,则抛出异常 + * + * @return 用户缓存信息 + */ + public LoginUser getLoginUser () { + String token = SecurityUtils.getToken(); + if (token == null) { + throw new NotLoginException("未提供token"); + } + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (loginUser == null) { + throw new NotLoginException("无效的token"); + } + return loginUser; + } + + /** + * 获取当前用户缓存信息, 如果未登录,则抛出异常 + * + * @param token 前端传递的认证信息 + * + * @return 用户缓存信息 + */ + public LoginUser getLoginUser (String token) { + return tokenService.getLoginUser(token); + } + + /** + * 验证当前用户有效期, 如果相差不足120分钟,自动刷新缓存 + * + * @param loginUser 当前用户信息 + */ + public void verifyLoginUserExpire (LoginUser loginUser) { + tokenService.verifyToken(loginUser); + } + + /** + * 验证用户是否具备某权限 + * + * @param permission 权限字符串 + * + * @return 用户是否具备某权限 + */ + public boolean hasPermi (String permission) { + return hasPermi(getPermiList(), permission); + } + + /** + * 验证用户是否具备某权限, 如果验证未通过,则抛出异常: NotPermissionException + * + * @param permission 权限字符串 + * + * @return 用户是否具备某权限 + */ + public void checkPermi (String permission) { + if (!hasPermi(getPermiList(), permission)) { + throw new NotPermissionException(permission); + } + } + + /** + * 根据注解(@RequiresPermissions)鉴权, 如果验证未通过,则抛出异常: NotPermissionException + * + * @param requiresPermissions 注解对象 + */ + public void checkPermi (RequiresPermissions requiresPermissions) { + SecurityContextHolder.setPermission(StringUtils.join(requiresPermissions.value(), ",")); + if (requiresPermissions.logical() == Logical.AND) { + checkPermiAnd(requiresPermissions.value()); + } else { + checkPermiOr(requiresPermissions.value()); + } + } + + /** + * 验证用户是否含有指定权限,必须全部拥有 + * + * @param permissions 权限列表 + */ + public void checkPermiAnd (String... permissions) { + Set permissionList = getPermiList(); + for (String permission : permissions) { + if (!hasPermi(permissionList, permission)) { + throw new NotPermissionException(permission); + } + } + } + + /** + * 验证用户是否含有指定权限,只需包含其中一个 + * + * @param permissions 权限码数组 + */ + public void checkPermiOr (String... permissions) { + Set permissionList = getPermiList(); + for (String permission : permissions) { + if (hasPermi(permissionList, permission)) { + return; + } + } + if (permissions.length > 0) { + throw new NotPermissionException(permissions); + } + } + + /** + * 判断用户是否拥有某个角色 + * + * @param role 角色标识 + * + * @return 用户是否具备某角色 + */ + public boolean hasRole (String role) { + return hasRole(getRoleList(), role); + } + + /** + * 判断用户是否拥有某个角色, 如果验证未通过,则抛出异常: NotRoleException + * + * @param role 角色标识 + */ + public void checkRole (String role) { + if (!hasRole(role)) { + throw new NotRoleException(role); + } + } + + /** + * 根据注解(@RequiresRoles)鉴权 + * + * @param requiresRoles 注解对象 + */ + public void checkRole (RequiresRoles requiresRoles) { + if (requiresRoles.logical() == Logical.AND) { + checkRoleAnd(requiresRoles.value()); + } else { + checkRoleOr(requiresRoles.value()); + } + } + + /** + * 验证用户是否含有指定角色,必须全部拥有 + * + * @param roles 角色标识数组 + */ + public void checkRoleAnd (String... roles) { + Set roleList = getRoleList(); + for (String role : roles) { + if (!hasRole(roleList, role)) { + throw new NotRoleException(role); + } + } + } + + /** + * 验证用户是否含有指定角色,只需包含其中一个 + * + * @param roles 角色标识数组 + */ + public void checkRoleOr (String... roles) { + Set roleList = getRoleList(); + for (String role : roles) { + if (hasRole(roleList, role)) { + return; + } + } + if (roles.length > 0) { + throw new NotRoleException(roles); + } + } + + /** + * 根据注解(@RequiresLogin)鉴权 + * + * @param at 注解对象 + */ + public void checkByAnnotation (RequiresLogin at) { + this.checkLogin(); + } + + /** + * 根据注解(@RequiresRoles)鉴权 + * + * @param at 注解对象 + */ + public void checkByAnnotation (RequiresRoles at) { + String[] roleArray = at.value(); + if (at.logical() == Logical.AND) { + this.checkRoleAnd(roleArray); + } else { + this.checkRoleOr(roleArray); + } + } + + /** + * 根据注解(@RequiresPermissions)鉴权 + * + * @param at 注解对象 + */ + public void checkByAnnotation (RequiresPermissions at) { + String[] permissionArray = at.value(); + if (at.logical() == Logical.AND) { + this.checkPermiAnd(permissionArray); + } else { + this.checkPermiOr(permissionArray); + } + } + + /** + * 获取当前账号的角色列表 + * + * @return 角色列表 + */ + public Set getRoleList () { + try { + LoginUser loginUser = getLoginUser(); + return loginUser.getRoles(); + } catch (Exception e) { + return new HashSet<>(); + } + } + + /** + * 获取当前账号的权限列表 + * + * @return 权限列表 + */ + public Set getPermiList () { + try { + LoginUser loginUser = getLoginUser(); + return loginUser.getPermissions(); + } catch (Exception e) { + return new HashSet<>(); + } + } + + /** + * 判断是否包含权限 + * + * @param authorities 权限列表 + * @param permission 权限字符串 + * + * @return 用户是否具备某权限 + */ + public boolean hasPermi (Collection authorities, String permission) { + return authorities.stream().filter(StringUtils::hasText) + .anyMatch(x -> ALL_PERMISSION.equals(x) || PatternMatchUtils.simpleMatch(x, permission)); + } + + /** + * 判断是否包含角色 + * + * @param roles 角色列表 + * @param role 角色 + * + * @return 用户是否具备某角色权限 + */ + public boolean hasRole (Collection roles, String role) { + return roles.stream().filter(StringUtils::hasText) + .anyMatch(x -> SUPER_ADMIN.equals(x) || PatternMatchUtils.simpleMatch(x, role)); + } +} diff --git a/src/main/java/com/muyu/common/security/auth/AuthUtil.java b/src/main/java/com/muyu/common/security/auth/AuthUtil.java new file mode 100644 index 0000000..131d150 --- /dev/null +++ b/src/main/java/com/muyu/common/security/auth/AuthUtil.java @@ -0,0 +1,154 @@ +package com.muyu.common.security.auth; + +import com.muyu.common.security.annotation.RequiresPermissions; +import com.muyu.common.security.annotation.RequiresRoles; +import com.muyu.common.system.domain.LoginUser; + +/** + * Token 权限验证工具类 + * + * @author muyu + */ +public class AuthUtil { + /** + * 底层的 AuthLogic 对象 + */ + public static AuthLogic authLogic = new AuthLogic(); + + /** + * 会话注销 + */ + public static void logout () { + authLogic.logout(); + } + + /** + * 会话注销,根据指定Token + * + * @param token 指定token + */ + public static void logoutByToken (String token) { + authLogic.logoutByToken(token); + } + + /** + * 检验当前会话是否已经登录,如未登录,则抛出异常 + */ + public static void checkLogin () { + authLogic.checkLogin(); + } + + /** + * 获取当前登录用户信息 + * + * @param token 指定token + * + * @return 用户信息 + */ + public static LoginUser getLoginUser (String token) { + return authLogic.getLoginUser(token); + } + + /** + * 验证当前用户有效期 + * + * @param loginUser 用户信息 + */ + public static void verifyLoginUserExpire (LoginUser loginUser) { + authLogic.verifyLoginUserExpire(loginUser); + } + + /** + * 当前账号是否含有指定角色标识, 返回true或false + * + * @param role 角色标识 + * + * @return 是否含有指定角色标识 + */ + public static boolean hasRole (String role) { + return authLogic.hasRole(role); + } + + /** + * 当前账号是否含有指定角色标识, 如果验证未通过,则抛出异常: NotRoleException + * + * @param role 角色标识 + */ + public static void checkRole (String role) { + authLogic.checkRole(role); + } + + /** + * 根据注解传入参数鉴权, 如果验证未通过,则抛出异常: NotRoleException + * + * @param requiresRoles 角色权限注解 + */ + public static void checkRole (RequiresRoles requiresRoles) { + authLogic.checkRole(requiresRoles); + } + + /** + * 当前账号是否含有指定角色标识 [指定多个,必须全部验证通过] + * + * @param roles 角色标识数组 + */ + public static void checkRoleAnd (String... roles) { + authLogic.checkRoleAnd(roles); + } + + /** + * 当前账号是否含有指定角色标识 [指定多个,只要其一验证通过即可] + * + * @param roles 角色标识数组 + */ + public static void checkRoleOr (String... roles) { + authLogic.checkRoleOr(roles); + } + + /** + * 当前账号是否含有指定权限, 返回true或false + * + * @param permission 权限码 + * + * @return 是否含有指定权限 + */ + public static boolean hasPermi (String permission) { + return authLogic.hasPermi(permission); + } + + /** + * 当前账号是否含有指定权限, 如果验证未通过,则抛出异常: NotPermissionException + * + * @param permission 权限码 + */ + public static void checkPermi (String permission) { + authLogic.checkPermi(permission); + } + + /** + * 根据注解传入参数鉴权, 如果验证未通过,则抛出异常: NotPermissionException + * + * @param requiresPermissions 权限注解 + */ + public static void checkPermi (RequiresPermissions requiresPermissions) { + authLogic.checkPermi(requiresPermissions); + } + + /** + * 当前账号是否含有指定权限 [指定多个,必须全部验证通过] + * + * @param permissions 权限码数组 + */ + public static void checkPermiAnd (String... permissions) { + authLogic.checkPermiAnd(permissions); + } + + /** + * 当前账号是否含有指定权限 [指定多个,只要其一验证通过即可] + * + * @param permissions 权限码数组 + */ + public static void checkPermiOr (String... permissions) { + authLogic.checkPermiOr(permissions); + } +} diff --git a/src/main/java/com/muyu/common/security/config/ApplicationConfig.java b/src/main/java/com/muyu/common/security/config/ApplicationConfig.java new file mode 100644 index 0000000..b78abbf --- /dev/null +++ b/src/main/java/com/muyu/common/security/config/ApplicationConfig.java @@ -0,0 +1,21 @@ +package com.muyu.common.security.config; + +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.context.annotation.Bean; + +import java.util.TimeZone; + +/** + * 系统配置 + * + * @author muyu + */ +public class ApplicationConfig { + /** + * 时区配置 + */ + @Bean + public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization () { + return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(TimeZone.getDefault()); + } +} diff --git a/src/main/java/com/muyu/common/security/config/WebMvcConfig.java b/src/main/java/com/muyu/common/security/config/WebMvcConfig.java new file mode 100644 index 0000000..8acde35 --- /dev/null +++ b/src/main/java/com/muyu/common/security/config/WebMvcConfig.java @@ -0,0 +1,32 @@ +package com.muyu.common.security.config; + +import com.muyu.common.security.interceptor.HeaderInterceptor; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * 拦截器配置 + * + * @author muyu + */ +public class WebMvcConfig implements WebMvcConfigurer { + /** + * 不需要拦截地址 + */ + public static final String[] excludeUrls = {"/login", "/logout", "/refresh"}; + + @Override + public void addInterceptors (InterceptorRegistry registry) { + registry.addInterceptor(getHeaderInterceptor()) + .addPathPatterns("/**") + .excludePathPatterns(excludeUrls) + .order(-10); + } + + /** + * 自定义请求头拦截器 + */ + public HeaderInterceptor getHeaderInterceptor () { + return new HeaderInterceptor(); + } +} diff --git a/src/main/java/com/muyu/common/security/feign/FeignAutoConfiguration.java b/src/main/java/com/muyu/common/security/feign/FeignAutoConfiguration.java new file mode 100644 index 0000000..4bfda6d --- /dev/null +++ b/src/main/java/com/muyu/common/security/feign/FeignAutoConfiguration.java @@ -0,0 +1,18 @@ +package com.muyu.common.security.feign; + +import feign.RequestInterceptor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Feign 配置注册 + * + * @author muyu + **/ +@Configuration +public class FeignAutoConfiguration { + @Bean + public RequestInterceptor requestInterceptor () { + return new FeignRequestInterceptor(); + } +} diff --git a/src/main/java/com/muyu/common/security/feign/FeignRequestInterceptor.java b/src/main/java/com/muyu/common/security/feign/FeignRequestInterceptor.java new file mode 100644 index 0000000..8ae21da --- /dev/null +++ b/src/main/java/com/muyu/common/security/feign/FeignRequestInterceptor.java @@ -0,0 +1,48 @@ +package com.muyu.common.security.feign; + +import com.muyu.common.core.constant.SecurityConstants; +import com.muyu.common.core.utils.ServletUtils; +import com.muyu.common.core.utils.StringUtils; +import com.muyu.common.core.utils.ip.IpUtils; +import feign.RequestInterceptor; +import feign.RequestTemplate; +import org.springframework.stereotype.Component; + +import jakarta.servlet.http.HttpServletRequest; +import java.util.Map; + +/** + * feign 请求拦截器 + * + * @author muyu + */ +@Component +public class FeignRequestInterceptor implements RequestInterceptor { + @Override + public void apply (RequestTemplate requestTemplate) { + HttpServletRequest httpServletRequest = ServletUtils.getRequest(); + if (StringUtils.isNotNull(httpServletRequest)) { + Map headers = ServletUtils.getHeaders(httpServletRequest); + // 传递用户信息请求头,防止丢失 + String userId = headers.get(SecurityConstants.DETAILS_USER_ID); + if (StringUtils.isNotEmpty(userId)) { + requestTemplate.header(SecurityConstants.DETAILS_USER_ID, userId); + } + String userKey = headers.get(SecurityConstants.USER_KEY); + if (StringUtils.isNotEmpty(userKey)) { + requestTemplate.header(SecurityConstants.USER_KEY, userKey); + } + String userName = headers.get(SecurityConstants.DETAILS_USERNAME); + if (StringUtils.isNotEmpty(userName)) { + requestTemplate.header(SecurityConstants.DETAILS_USERNAME, userName); + } + String authentication = headers.get(SecurityConstants.AUTHORIZATION_HEADER); + if (StringUtils.isNotEmpty(authentication)) { + requestTemplate.header(SecurityConstants.AUTHORIZATION_HEADER, authentication); + } + + // 配置客户端IP + requestTemplate.header("X-Forwarded-For", IpUtils.getIpAddr()); + } + } +} diff --git a/src/main/java/com/muyu/common/security/handler/GlobalExceptionHandler.java b/src/main/java/com/muyu/common/security/handler/GlobalExceptionHandler.java new file mode 100644 index 0000000..170211c --- /dev/null +++ b/src/main/java/com/muyu/common/security/handler/GlobalExceptionHandler.java @@ -0,0 +1,147 @@ +package com.muyu.common.security.handler; + +import com.muyu.common.core.constant.HttpStatus; +import com.muyu.common.core.exception.DemoModeException; +import com.muyu.common.core.exception.InnerAuthException; +import com.muyu.common.core.exception.ServiceException; +import com.muyu.common.core.exception.auth.NotPermissionException; +import com.muyu.common.core.exception.auth.NotRoleException; +import com.muyu.common.core.utils.StringUtils; +import com.muyu.common.core.domain.Result; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.validation.BindException; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.MissingPathVariableException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; + +import jakarta.servlet.http.HttpServletRequest; + +/** + * 全局异常处理器 + * + * @author muyu + */ +@RestControllerAdvice +public class GlobalExceptionHandler { + private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); + + /** + * 权限码异常 + */ + @ExceptionHandler(NotPermissionException.class) + public Result handleNotPermissionException (NotPermissionException e, HttpServletRequest request) { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',权限码校验失败'{}'", requestURI, e.getMessage()); + return Result.error(HttpStatus.FORBIDDEN, "没有访问权限,请联系管理员授权"); + } + + /** + * 角色权限异常 + */ + @ExceptionHandler(NotRoleException.class) + public Result handleNotRoleException (NotRoleException e, HttpServletRequest request) { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',角色权限校验失败'{}'", requestURI, e.getMessage()); + return Result.error(HttpStatus.FORBIDDEN, "没有访问权限,请联系管理员授权"); + } + + /** + * 请求方式不支持 + */ + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) + public Result handleHttpRequestMethodNotSupported (HttpRequestMethodNotSupportedException e, HttpServletRequest request) { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod()); + return Result.error(e.getMessage()); + } + + /** + * 业务异常 + */ + @ExceptionHandler(ServiceException.class) + public Result handleServiceException (ServiceException e, HttpServletRequest request) { + log.error(e.getMessage(), e); + Integer code = e.getCode(); + return StringUtils.isNotNull(code) ? Result.error(code, e.getMessage()) : Result.error(e.getMessage()); + } + + /** + * 请求路径中缺少必需的路径变量 + */ + @ExceptionHandler(MissingPathVariableException.class) + public Result handleMissingPathVariableException (MissingPathVariableException e, HttpServletRequest request) { + String requestURI = request.getRequestURI(); + log.error("请求路径中缺少必需的路径变量'{}',发生系统异常.", requestURI, e); + return Result.error(String.format("请求路径中缺少必需的路径变量[%s]", e.getVariableName())); + } + + /** + * 请求参数类型不匹配 + */ + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + public Result handleMethodArgumentTypeMismatchException (MethodArgumentTypeMismatchException e, HttpServletRequest request) { + String requestURI = request.getRequestURI(); + log.error("请求参数类型不匹配'{}',发生系统异常.", requestURI, e); + return Result.error(String.format("请求参数类型不匹配,参数[%s]要求类型为:'%s',但输入值为:'%s'", e.getName(), e.getRequiredType().getName(), e.getValue())); + } + + /** + * 拦截未知的运行时异常 + */ + @ExceptionHandler(RuntimeException.class) + public Result handleRuntimeException (RuntimeException e, HttpServletRequest request) { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',发生未知异常.", requestURI, e); + return Result.error(e.getMessage()); + } + + /** + * 系统异常 + */ + @ExceptionHandler(Exception.class) + public Result handleException (Exception e, HttpServletRequest request) { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',发生系统异常.", requestURI, e); + return Result.error(e.getMessage()); + } + + /** + * 自定义验证异常 + */ + @ExceptionHandler(BindException.class) + public Result handleBindException (BindException e) { + log.error(e.getMessage(), e); + String message = e.getAllErrors().get(0).getDefaultMessage(); + return Result.error(message); + } + + /** + * 自定义验证异常 + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + public Object handleMethodArgumentNotValidException (MethodArgumentNotValidException e) { + log.error(e.getMessage(), e); + String message = e.getBindingResult().getFieldError().getDefaultMessage(); + return Result.error(message); + } + + /** + * 内部认证异常 + */ + @ExceptionHandler(InnerAuthException.class) + public Result handleInnerAuthException (InnerAuthException e) { + return Result.error(e.getMessage()); + } + + /** + * 演示模式异常 + */ + @ExceptionHandler(DemoModeException.class) + public Result handleDemoModeException (DemoModeException e) { + return Result.error("演示模式,不允许操作"); + } +} diff --git a/src/main/java/com/muyu/common/security/interceptor/HeaderInterceptor.java b/src/main/java/com/muyu/common/security/interceptor/HeaderInterceptor.java new file mode 100644 index 0000000..0b1938d --- /dev/null +++ b/src/main/java/com/muyu/common/security/interceptor/HeaderInterceptor.java @@ -0,0 +1,50 @@ +package com.muyu.common.security.interceptor; + +import com.muyu.common.core.constant.SecurityConstants; +import com.muyu.common.core.context.SecurityContextHolder; +import com.muyu.common.core.utils.ServletUtils; +import com.muyu.common.core.utils.StringUtils; +import com.muyu.common.security.auth.AuthUtil; +import com.muyu.common.security.utils.SecurityUtils; +import com.muyu.common.system.domain.LoginUser; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.AsyncHandlerInterceptor; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 自定义请求头拦截器,将Header数据封装到线程变量中方便获取 + * 注意:此拦截器会同时验证当前用户有效期自动刷新有效期 + * + * @author muyu + */ +public class HeaderInterceptor implements AsyncHandlerInterceptor { + + @Override + public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + if (!(handler instanceof HandlerMethod)) { + return true; + } + + SecurityContextHolder.setUserId(ServletUtils.getHeader(request, SecurityConstants.DETAILS_USER_ID)); + SecurityContextHolder.setUserName(ServletUtils.getHeader(request, SecurityConstants.DETAILS_USERNAME)); + SecurityContextHolder.setUserKey(ServletUtils.getHeader(request, SecurityConstants.USER_KEY)); + + String token = SecurityUtils.getToken(); + if (StringUtils.isNotEmpty(token)) { + LoginUser loginUser = AuthUtil.getLoginUser(token); + if (StringUtils.isNotNull(loginUser)) { + AuthUtil.verifyLoginUserExpire(loginUser); + SecurityContextHolder.set(SecurityConstants.LOGIN_USER, loginUser); + } + } + return true; + } + + @Override + public void afterCompletion (HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) + throws Exception { + SecurityContextHolder.remove(); + } +} diff --git a/src/main/java/com/muyu/common/security/service/TokenService.java b/src/main/java/com/muyu/common/security/service/TokenService.java new file mode 100644 index 0000000..3955ddc --- /dev/null +++ b/src/main/java/com/muyu/common/security/service/TokenService.java @@ -0,0 +1,153 @@ +package com.muyu.common.security.service; + +import com.muyu.common.core.constant.CacheConstants; +import com.muyu.common.core.constant.SecurityConstants; +import com.muyu.common.core.utils.JwtUtils; +import com.muyu.common.core.utils.ServletUtils; +import com.muyu.common.core.utils.StringUtils; +import com.muyu.common.core.utils.ip.IpUtils; +import com.muyu.common.core.utils.uuid.IdUtils; +import com.muyu.common.redis.service.RedisService; +import com.muyu.common.security.utils.SecurityUtils; +import com.muyu.common.system.domain.LoginUser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import jakarta.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * token验证处理 + * + * @author muyu + */ +@Component +public class TokenService { + protected static final long MILLIS_SECOND = 1000; + protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND; + private final static Long MILLIS_MINUTE_TEN = CacheConstants.REFRESH_TIME * MILLIS_MINUTE; + private static final Logger log = LoggerFactory.getLogger(TokenService.class); + private final static long expireTime = CacheConstants.EXPIRATION; + + private final static String ACCESS_TOKEN = CacheConstants.LOGIN_TOKEN_KEY; + @Autowired + private RedisService redisService; + + /** + * 创建令牌 + */ + public Map createToken (LoginUser loginUser) { + String token = IdUtils.fastUUID(); + Long userId = loginUser.getSysUser().getUserId(); + String userName = loginUser.getSysUser().getUserName(); + loginUser.setToken(token); + loginUser.setUserid(userId); + loginUser.setUsername(userName); + loginUser.setIpaddr(IpUtils.getIpAddr()); + refreshToken(loginUser); + + // Jwt存储信息 + Map claimsMap = new HashMap(); + claimsMap.put(SecurityConstants.USER_KEY, token); + claimsMap.put(SecurityConstants.DETAILS_USER_ID, userId); + claimsMap.put(SecurityConstants.DETAILS_USERNAME, userName); + + // 接口返回信息 + Map rspMap = new HashMap(); + rspMap.put("access_token", JwtUtils.createToken(claimsMap)); + rspMap.put("expires_in", expireTime); + return rspMap; + } + + /** + * 获取用户身份信息 + * + * @return 用户信息 + */ + public LoginUser getLoginUser () { + return getLoginUser(ServletUtils.getRequest()); + } + + /** + * 设置用户身份信息 + */ + public void setLoginUser (LoginUser loginUser) { + if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken())) { + refreshToken(loginUser); + } + } + + /** + * 获取用户身份信息 + * + * @return 用户信息 + */ + public LoginUser getLoginUser (HttpServletRequest request) { + // 获取请求携带的令牌 + String token = SecurityUtils.getToken(request); + return getLoginUser(token); + } + + /** + * 获取用户身份信息 + * + * @return 用户信息 + */ + public LoginUser getLoginUser (String token) { + LoginUser user = null; + try { + if (StringUtils.isNotEmpty(token)) { + String userkey = JwtUtils.getUserKey(token); + user = redisService.getCacheObject(getTokenKey(userkey)); + return user; + } + } catch (Exception e) { + log.error("获取用户信息异常'{}'", e.getMessage()); + } + return user; + } + + /** + * 删除用户缓存信息 + */ + public void delLoginUser (String token) { + if (StringUtils.isNotEmpty(token)) { + String userkey = JwtUtils.getUserKey(token); + redisService.deleteObject(getTokenKey(userkey)); + } + } + + /** + * 验证令牌有效期,相差不足120分钟,自动刷新缓存 + * + * @param loginUser + */ + public void verifyToken (LoginUser loginUser) { + long expireTime = loginUser.getExpireTime(); + long currentTime = System.currentTimeMillis(); + if (expireTime - currentTime <= MILLIS_MINUTE_TEN) { + refreshToken(loginUser); + } + } + + /** + * 刷新令牌有效期 + * + * @param loginUser 登录信息 + */ + public void refreshToken (LoginUser loginUser) { + loginUser.setLoginTime(System.currentTimeMillis()); + loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE); + // 根据uuid将loginUser缓存 + String userKey = getTokenKey(loginUser.getToken()); + redisService.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES); + } + + private String getTokenKey (String token) { + return ACCESS_TOKEN + token; + } +} diff --git a/src/main/java/com/muyu/common/security/utils/DictUtils.java b/src/main/java/com/muyu/common/security/utils/DictUtils.java new file mode 100644 index 0000000..04ee068 --- /dev/null +++ b/src/main/java/com/muyu/common/security/utils/DictUtils.java @@ -0,0 +1,71 @@ +package com.muyu.common.security.utils; + +import com.alibaba.fastjson2.JSONArray; +import com.muyu.common.core.constant.CacheConstants; +import com.muyu.common.core.utils.SpringUtils; +import com.muyu.common.core.utils.StringUtils; +import com.muyu.common.redis.service.RedisService; +import com.muyu.common.system.domain.SysDictData; + +import java.util.Collection; +import java.util.List; + +/** + * 字典工具类 + * + * @author muyu + */ +public class DictUtils { + /** + * 设置字典缓存 + * + * @param key 参数键 + * @param dictDatas 字典数据列表 + */ + public static void setDictCache (String key, List dictDatas) { + SpringUtils.getBean(RedisService.class).setCacheObject(getCacheKey(key), dictDatas); + } + + /** + * 获取字典缓存 + * + * @param key 参数键 + * + * @return dictDatas 字典数据列表 + */ + public static List getDictCache (String key) { + JSONArray arrayCache = SpringUtils.getBean(RedisService.class).getCacheObject(getCacheKey(key)); + if (StringUtils.isNotNull(arrayCache)) { + return arrayCache.toList(SysDictData.class); + } + return null; + } + + /** + * 删除指定字典缓存 + * + * @param key 字典键 + */ + public static void removeDictCache (String key) { + SpringUtils.getBean(RedisService.class).deleteObject(getCacheKey(key)); + } + + /** + * 清空字典缓存 + */ + public static void clearDictCache () { + Collection keys = SpringUtils.getBean(RedisService.class).keys(CacheConstants.SYS_DICT_KEY + "*"); + SpringUtils.getBean(RedisService.class).deleteObject(keys); + } + + /** + * 设置cache key + * + * @param configKey 参数键 + * + * @return 缓存键key + */ + public static String getCacheKey (String configKey) { + return CacheConstants.SYS_DICT_KEY + configKey; + } +} diff --git a/src/main/java/com/muyu/common/security/utils/SecurityUtils.java b/src/main/java/com/muyu/common/security/utils/SecurityUtils.java new file mode 100644 index 0000000..6e3f92b --- /dev/null +++ b/src/main/java/com/muyu/common/security/utils/SecurityUtils.java @@ -0,0 +1,109 @@ +package com.muyu.common.security.utils; + +import com.muyu.common.core.constant.SecurityConstants; +import com.muyu.common.core.constant.TokenConstants; +import com.muyu.common.core.context.SecurityContextHolder; +import com.muyu.common.core.utils.ServletUtils; +import com.muyu.common.core.utils.StringUtils; +import com.muyu.common.system.domain.LoginUser; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +import jakarta.servlet.http.HttpServletRequest; + +/** + * 权限获取工具类 + * + * @author muyu + */ +public class SecurityUtils { + /** + * 获取用户ID + */ + public static Long getUserId () { + return SecurityContextHolder.getUserId(); + } + + /** + * 获取用户名称 + */ + public static String getUsername () { + return SecurityContextHolder.getUserName(); + } + + /** + * 获取用户key + */ + public static String getUserKey () { + return SecurityContextHolder.getUserKey(); + } + + /** + * 获取登录用户信息 + */ + public static LoginUser getLoginUser () { + return SecurityContextHolder.get(SecurityConstants.LOGIN_USER, LoginUser.class); + } + + /** + * 获取请求token + */ + public static String getToken () { + return getToken(ServletUtils.getRequest()); + } + + /** + * 根据request获取请求token + */ + public static String getToken (HttpServletRequest request) { + // 从header获取token标识 + String token = request.getHeader(TokenConstants.AUTHENTICATION); + return replaceTokenPrefix(token); + } + + /** + * 裁剪token前缀 + */ + public static String replaceTokenPrefix (String token) { + // 如果前端设置了令牌前缀,则裁剪掉前缀 + if (StringUtils.isNotEmpty(token) && token.startsWith(TokenConstants.PREFIX)) { + token = token.replaceFirst(TokenConstants.PREFIX, ""); + } + return token; + } + + /** + * 是否为管理员 + * + * @param userId 用户ID + * + * @return 结果 + */ + public static boolean isAdmin (Long userId) { + return userId != null && 1L == userId; + } + + /** + * 生成BCryptPasswordEncoder密码 + * + * @param password 密码 + * + * @return 加密字符串 + */ + public static String encryptPassword (String password) { + BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + return passwordEncoder.encode(password); + } + + /** + * 判断密码是否相同 + * + * @param rawPassword 真实密码 + * @param encodedPassword 加密后字符 + * + * @return 结果 + */ + public static boolean matchesPassword (String rawPassword, String encodedPassword) { + BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + return passwordEncoder.matches(rawPassword, encodedPassword); + } +} diff --git a/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..f4e76b1 --- /dev/null +++ b/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,5 @@ +com.muyu.common.security.config.WebMvcConfig +com.muyu.common.security.service.TokenService +com.muyu.common.security.aspect.PreAuthorizeAspect +com.muyu.common.security.aspect.InnerAuthAspect +com.muyu.common.security.handler.GlobalExceptionHandler diff --git a/target/classes/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/target/classes/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..f4e76b1 --- /dev/null +++ b/target/classes/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,5 @@ +com.muyu.common.security.config.WebMvcConfig +com.muyu.common.security.service.TokenService +com.muyu.common.security.aspect.PreAuthorizeAspect +com.muyu.common.security.aspect.InnerAuthAspect +com.muyu.common.security.handler.GlobalExceptionHandler diff --git a/target/classes/com/muyu/common/security/annotation/EnableCustomConfig.class b/target/classes/com/muyu/common/security/annotation/EnableCustomConfig.class new file mode 100644 index 0000000000000000000000000000000000000000..18ac118d96497749c165f725d93884fc200b33b3 GIT binary patch literal 913 zcmah{%We}f6unMcrnEdtd6e=Hk3|<*#0w<4s1O>JR0^aJr2&bua@?D#%h(?IQ8K^A zf)C)M5O*dOjns))*fX}zIljK<{QmRv7XZA0CkeC|yp~pT9m~k+M_a=^%IH)mxiH2C z5ftrpjTq;+AAPWT-DtHD@`;E7Zgv&7OF<3blc_j6h&A4uk8ci6L1rm69 z^mWi>@Vps2#6VDL10i?*0~y#{$&!G{@bkw}?~qhqG*jML8LJZTgwP9a?wM00D{9Ew zzI7S*g;OS*IHB=}b>GP|QkaITUZuVuKi;P?*nzXts-$(C7S?0ET%rUp2JKo@mt(To zUx?3Z9g3nrcO*si_Du>%r%Fnv-R|i6gu!lOSzeyvNzAF#d9|6*sV|KrIo`-@cu-Fl z)>WRq>MzVH(I}KWEeh&?o!MDTiM6|!H9?gbeo)O-qh^%oYX0610|rYpR;X71ElBEl z6|U3w1FXUtovnWZhHwLJ!mVnBa2qz@4gof4+@-C1$8aAWTp&Gc!9zOOo~>IF9!Yro E7fR|OqyPW_ literal 0 HcmV?d00001 diff --git a/target/classes/com/muyu/common/security/annotation/EnableMyFeignClients.class b/target/classes/com/muyu/common/security/annotation/EnableMyFeignClients.class new file mode 100644 index 0000000000000000000000000000000000000000..264e1d41c1a5b3192fe64cbdc5a48d6f5b1b1051 GIT binary patch literal 827 zcmaiyU279T6o%hPlWtq*O z#kQ`IV@(&u8L8BCv?AkOgr>l~OL;A0ElnDK8edZ4BdiKMU0N$i-Y@bAw63KNB(U@H z)#!Xl4}7jndLpp3gimQIgZ2WOW9g_TlPj5$6IkPZkIKdcwkw{Fmd=&__TQ=JcFw#$ z;1Q$`IalAULEgN6$5eZAQ?+BwRHY%GuR$P6Ze|zQ7}$^}bfz@t*{j>nQ8^a-`oVaW z(HG?u?{GO6x42&!y|SpG%v18*#q)Z9S5w9>Ff3-oP;>aT*A+OZXZz$C$CchQ=5dc1)4B@d~WkJcC+&7rn$`Mo6WD{MB6Zl?TpN{6wMzA zRRwf%C?K%HUyJqFVl_or|eYE*}fhKmidc=hY53q}e<&Ch1M|jM@6W})}Ug0DF literal 0 HcmV?d00001 diff --git a/target/classes/com/muyu/common/security/annotation/InnerAuth.class b/target/classes/com/muyu/common/security/annotation/InnerAuth.class new file mode 100644 index 0000000000000000000000000000000000000000..78c876b5b03240bac251df50ea3b76294a9529a6 GIT binary patch literal 504 zcmaiw%}&BV6ot=Fp^A#&U*f``E{H6&4?tXyfRRMRU~z$~p!))sx7(uI33QHy{Hs~4x^vIIWlKrLPlbhWbsp}6}$8$DeaQ|D`kmW zG@5<(M;V`6YF0D9P?BDr=brQ2b5GB&-`{=$c#bC$G7L@AcQrRUi8T7-`kpqm%qXzK z6V32EKQuy{dINuAn}(AIgQy^jB?h5?@RFf5Sb*N;{G5V3=Vyj*czI2Mf@KD2Yt$dS z+t(S|?-!aWqll7(Wd&ta7_z40dlo~rt`3fjV?%QcZ=wyyN0u3OWmIuP!is{MxW!Po z1nhf}OCpX9C$feg8J^Ws>^;YrPP?gw9tO5I>84NM1LFBL1-G%ru++0XJKSMdNsmgH zuhmfwS()x%%OTI#bp;#PxHhdTZL zh*1~vo1rrBDGMVbu=&hnu;>{s&97W_i6Rqzvgty3-JWgtHH&@-LHr57n8 z_p-N;sGTl@tou=5TCZ$=PbEogn}6cn4jEjf%nQ4#JiGv{6hfd>*mAfYfNNoniG$yq!Mok}jdirQ>moX_aIioJ5EUk-9)R`vdGym_g7F#Tg|1 zs{S0>XeG3MLRea=CeB)R3cV%=Kv4CQJsM EKV2>P(f|Me literal 0 HcmV?d00001 diff --git a/target/classes/com/muyu/common/security/annotation/RequiresLogin.class b/target/classes/com/muyu/common/security/annotation/RequiresLogin.class new file mode 100644 index 0000000000000000000000000000000000000000..a9c15286298372b5c6a87c1845addf66ab6e3e26 GIT binary patch literal 427 zcmaiw%}T>S6ot>kw$b`myK^BH7hM`$iwnVsLTgKmg1DN7OPDg5(PXBOS99S5_)y}F z3#$-tF?a6I`Oby=@%i=+-~z)Cfxua2i>UCmk2oo;i7Lu`sazdNW2}=-aWA1)uS%*W zHdiJ@M_{m&D;a5N@@TeLQszSR1di9;U-adVkwE|6`ZA+yrAc7;$M>{>1r8H$oGR#1 zRcfJW4OarAN$Y}y95G|fxj=U%wP)|~Khn9?Dyta?lZU(Xb{Y#DwIiu4b8@UaX`jcM z3SOVqFT|qWG)`}3R|1{%X&#RS1UC3}`Q$)=@H=i|i~kYVeV%O-cCd@RCVfUg*vFs= F2Vbake4YRR literal 0 HcmV?d00001 diff --git a/target/classes/com/muyu/common/security/annotation/RequiresPermissions.class b/target/classes/com/muyu/common/security/annotation/RequiresPermissions.class new file mode 100644 index 0000000000000000000000000000000000000000..dd2e6a90a626f4c1e53e288a8cf6c2e43916afc9 GIT binary patch literal 633 zcmb7?O-}+b5Qg9K;VOQCisHdYJP%^=9zCc?O*AdEM*`KQ_LeSVNQO8k@L62#7i17<g6+3NkaCXbJGl4oLQt=o2P``l;Fc^tHB0s>hcdHx(3ep?Ic>}{}@9V3100ye(` jdBPU9k-<*NVi!eTt8A4Run2qD$3be2ks*|Em`+EZ#LKw` literal 0 HcmV?d00001 diff --git a/target/classes/com/muyu/common/security/annotation/RequiresRoles.class b/target/classes/com/muyu/common/security/annotation/RequiresRoles.class new file mode 100644 index 0000000000000000000000000000000000000000..f164119e4bf0dadfae97a1f0940459f97580cfca GIT binary patch literal 621 zcmb7?O-sZu5Qg7$U90Q&w;n|CAnQSkXYn9dQP^FjRzda@%UH6ct+t8KzvjUo;Exig zD0YPf^^jy{^33F&ByaC8uKB%(Bj5La40Xbr!2gY5}jk*ZGUy&SwJG+y5uuIO&}Vw5~IoM|7q%OI`fY{Xr#KU?Z@p zQ3>6uLfvbsRTl!=LyZkxLqLW&Creq@?k~tyrd3oj=ms~v@S^JrY>rL|Wge4Za%c3u zuPNbU!}5_B8t?ky`6YKa7T#U?y8;3Y9!>rb4!^343HB!0YxGe)a|=^nfhJ)ZGjK3l dv6#a=uiI=bFyImvv4rKy?870fV6~doJ^;|8v&;Yh literal 0 HcmV?d00001 diff --git a/target/classes/com/muyu/common/security/aspect/InnerAuthAspect.class b/target/classes/com/muyu/common/security/aspect/InnerAuthAspect.class new file mode 100644 index 0000000000000000000000000000000000000000..f9bfb72df964152d302098860acaef9ddd0f06c9 GIT binary patch literal 2122 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 literal 0 HcmV?d00001 diff --git a/target/classes/com/muyu/common/security/auth/AuthUtil.class b/target/classes/com/muyu/common/security/auth/AuthUtil.class new file mode 100644 index 0000000000000000000000000000000000000000..ecfde4eb9204dc7cfa83e1cb4946f616dac67101 GIT binary patch literal 2401 zcmb7E+j10D6kR75=8_%=L4tQcB$-5{1vE+~AOV9Hh6HoN%Zr^(JLxdfjXgb-^PrDf zKIxT$$6D++6-u(MhYjCVn5sV+rMK3~IEX_9j9NG>;wGOGN6pQ!5q4t1 zAF=Qpjw)1=(ADm0c*hGAj#lftwXVdGA2e!H>w-9D;dx0{cDFqDjwlHv{Gx@I@Unu{ z@M0ZYYkQHxB@@ry3cKytYuTG&%kcxd2iFz8YT-CeP|-av^1tr(elC1_*N;4fGu5Z2 zOA2_+!YRD2P}p+X%VCog#$vyaswON@7UfRa)eDes>!J| z+_8O5AVo*|j8vljDJoppd+MGzdMXq$7InlhW=`v_%uhF(TXqumbzkA)Go|f=vOOca z^psGXbDODkl~=+}J3=Hb*@U{Rw?+D-USr?MBxD{0#_O?z6*?V<1qs0P$^)BaUm-b zF#P~yy9pD-aTIW(FKH1U;v=1uo|STBlaA+;PH^KSlhVyn!1-*_7w(g=zshsQgfuO) z=vVThXDPat;rJMzXwmd3vxbwu=QJ#2@Hf(;>0M?Gr}G*XsbOg!j%hPTHP3OI9G_)4 z>bOarKZ(_uIo`-~d_@i?!?8$R!a?&hbJ%$fpB&o;j*=K<%8{Gt`Fua)4E|QyPkO3Z z!#jBm-%-Q&`*6&fIo`{2{6LN$GaR2{S@(k8Yv!osIesR`FBy)Nw4ZcoGsopT$FJo0 m&A?H_YTu=j3k!_=jf0;*#Qa|<2u*UE6Ehg)YK^f0tp5-6PuNoc literal 0 HcmV?d00001 diff --git a/target/classes/com/muyu/common/security/config/ApplicationConfig.class b/target/classes/com/muyu/common/security/config/ApplicationConfig.class new file mode 100644 index 0000000000000000000000000000000000000000..ef426ec00c9a169153e935c7be2ef79f947fa5db GIT binary patch literal 1631 zcmb_cTTc@~6#j-n3uO^33W7I8&=!T^;x$zhsTwpz6Cr5yX}TTC!0ygwcNWE8r7voD z@CO)wlkrSjuu22LxDRvN^PTUTbIzRaKX<fBP+O0LFW>2Ci&wNZ zTqH)jI#^e=B$9knxLdIf__M}5-{4smVJM@;M0r&imG&G*#Vj23WpKhlKL!|bb$y9Z z9$!_ye4_&*&(@@QB-cgak=!9ta?ptU}QDF>%9#87G{cEgyElhAD$85!}-BUCbU zn|%#$^X*vM2j!SN#SG3c%(>d(mtP`^gOZm3WkDGMaS+TTw<92QW@inag4r^A0XyoWXe-1k_+W z5yvvl;3mW5fgrWJZw9v+E*x%3y|NJ;q8g^YYQGei?W{D8(2mp0XzrT&0``d(9T=t2 z{s#JRmPV&xfO9xcJK}_G(H1;QJ8PHTMF#jtt5)=23p(9IU$oYQ83P8g_WrcCo)!pc yJ@rg|Mek>veD$}KR%bo9j0rMMVUorUT)|bEt;{@~agxQ!AxI{}4cx*VjQj#-5!~_s literal 0 HcmV?d00001 diff --git a/target/classes/com/muyu/common/security/config/WebMvcConfig.class b/target/classes/com/muyu/common/security/config/WebMvcConfig.class new file mode 100644 index 0000000000000000000000000000000000000000..0512c7e83e1dcaa4fc8bb05af45d4a15d905aae4 GIT binary patch literal 1684 zcmb_cTT|0O6#lkNNhlFYfg%bBUfY61QBf(E85tFvijGy7jxVO^5`t;MY!k4L{v$6l zWkyGTfIrAb$CI{77{Q{$!=BxaThS_h&DZEqaUHG74PRJZjd*9nGPQMH zdc0#SH5i6|lY7ftFLSZtcw88K+j9lOY&w%aQjxX)Q~N_U7e_aGWF%DdBFS*y6{c3* z7M5df2))90UGZAm*=0Fh(k!8anq`LqQ=mzm&>6ChPUZ_JJrj& zQ??IymdmsiYK7^qcp5l!GWrjsr{D!%Do9acv}`tx(-@L*M#WhSGpw{jdu+oDJrs-aSme)UHpw<`(^$@*kroz5X*t@=T%$?9PexW z4g(I}FR8eUD-1DLQ28;erdLijI~j(V|107+(ugXUq*l{x*R&i3Qw(x&c70F5H3mf! zd_(Z+ri^KZd;gx?a3A_22!OQYmN_LiZ#jI`uavlWu9s}ON#>k*AIz(`jXz&cExYeMI517Gi z;u!AWF3~`klcEUi;}h9BKCIg6kQ#|j4>E@!izT~G?C!JB|##QoRA=scsMsyiWI44OvwJu^H1c)vQ2oCR<4$juRRZNt{GdV7zKK^`^Jw=^Qj| zi=8WVPnnIDUY73DhE(_ci9n(*)r$PWlP#K@%w&rj#-`yaLvOWV`npn%zQiUx7lQK% zoI<~bfh1l-O5nQIjrx zo%Z3!^UjZhhW&{3Wy8s97!vTwILJ!Xwpz-t6p5*NuBo;b!i82G=LIy6Onh$EPGT4r zHN2k08@MFU*JYm~kVmp`SU?A3)bM7Pq~(AhpV84I#&Csk2Q1xbI%;blO>JyIQwCWL z6G=?sDnU=AS-131o>kd&8itZgll6c;f!V#jg;qY><6FmY@lZqgIIaoA12Q({Rvg#E znPoI(9B+{k!&B?FYkp}c)3)MxN1*>fGUT@4%++eHz+jM~otz1M)-WS*bHBA#sHffzXwl~rjwARe=D z@a2_lkEyX}`Wg=(ku=wQW(CqK9!YD;irF%m19O&TtAIfYGf}kNy54g9()YwA&ChIi zQ*SZvq^%gEzF;>U+al|Ej3Cc)FIe{x^ep+DeZZN{Ua*f&`~vYJ*uKry-v$OT$2Qpp zU>*yc34Dk*LXZ}KCR1;> z=R24R7W`-j<1oy zH^|~UauyBx)MK8qqp8qaNq{Q9G1 zB3Kyk6Yk*?3ic`YeHGevS7^7)mFtMU#FU0r4Z|8n{>JFO_8%VL7tg;bLljT+pc1qX L@Q}T)@gv}08)-2D literal 0 HcmV?d00001 diff --git a/target/classes/com/muyu/common/security/handler/GlobalExceptionHandler.class b/target/classes/com/muyu/common/security/handler/GlobalExceptionHandler.class new file mode 100644 index 0000000000000000000000000000000000000000..a19e77776261789acf127ee655aeba87ba7d3075 GIT binary patch literal 7704 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 BmWu!Y literal 0 HcmV?d00001 diff --git a/target/classes/com/muyu/common/security/service/TokenService.class b/target/classes/com/muyu/common/security/service/TokenService.class new file mode 100644 index 0000000000000000000000000000000000000000..a6885760d62ddd0474efb2a2cd0953007ddedb5a GIT binary patch literal 5797 zcmbVQd3+pY8GgQGli6-2X}h#3DOXC*9PMxvC{3xhA=s28#cq-cRKs+4l1#I^v&_t< z4S0cEA_79?P*G74#Dh}!rQwjn>!*0(Pk-KcQb5G}RzRQco5^HzK!f>XXTF*5d*A1M z-gENA%MU&R;0!TWLz%$pw39P(#gU@Ho1Bw3JS$yvZGVI}?y#Mg+RRD^!%Ql_A><*tZL|(P?Pdq zJ3r8pKoUo2SfFDeYG)Aajd;G5Gcr!jwDU%XGhpX?$bdj%!17ZgUa)AO=&6tNrkvQx$IQGIN%+Zi$ZLv6M`B zmalv!uyECMA|%I2EZ4CDE2)p0r9PYbjws!&)CGGRbTmrARpH*eQrL{u8rJAAB$jFB zsgMZ#WEYI~Lg*!FUVC9vCgVyFC+av!3YH++3#NK1uvW(@I8{J*t$x??218q#R~?4= z$Y(7Ly&BdD%vV7mK^R+1Z?MxWBygGx#ufn??UTZOatx=!B#6Q|&V)g{NT&LRtdT0b z4zJg+Nylci3RF)hq7nvjp~$#iS4w9P>@6kk2%nUOYWOIQHUUlPvNLhKAyh1BkE5N$ znCZ0Td40Z2%mmIxhlWlaUDzhDbVj=ZM{M2Whi<4!yO!x&A+t+Epo%FP&{B#iOrJR3 z$W*ZQ7Hqnt&(2q{gz&HI$pm^px_9V!6V4T=nMgbtL9}Do%oeR}{W83^mI@Bz`F!lu z@MaxvLEj8nSrSGmR-i0{HAr`vmD{ZgiGn^d9N-_pzjxqE&K`3g`1g| z0;Y~#NRvnkiK0fJTk%gNAfuW&Xu!iSnjYm)GHdVVF=N+HwVdWQvT?sST9j!qebOF~u1wd(;eMY=jUw#v8*px>@JD%z=w^T#UB{qmTjF@1z|qfLd+^Y8H$A)Wu4k?~`1GTh@K8g9{XD?Y$>KUK0oh2^>q`|8RWd^1CNWVgB;AJTD~ zjA%V$F}JP2L>d1d(eY8)UaN_qO%ZKJEZ=svcXYI;`a0XYdb->Cy4$)a zqy%?Z){?kK$9~yibv8Fu4gzFJ9MEx}#2i&0`9jsxvK!YxT6Ivzm^@m|qZ3xm!Gm~6 z!^1j0k4FTKQNERVDMzmmvms^maGYY{4G^XTjtk(u(pHA;<#u*Y$Sj9r$Q_l*9)e8Y?m7@LZ|vxluZLjz;2PKKc7 zcG!8VtC-tmx!oqktGrK}*ldPWvvPn)RzDboQNqioi**8rzWzw(==APUO~O8?Qlw27%7Z1amwT~ z8>V})ATidL1>-cYV; zx`+w+U{D8Pxl(FPOjkOS>Xw4-e$+~ zJ>NA8ot8i7WW2eRqAVfGMNAVFtWTm+VA(_$we!Oqx{V-3JupE>yHtAqD;m)xA{F41);FV6+p@>#9n@!oi)e;wR7WT=-hJI+z z=Pg$iM$6Mg6)Wa6J7opvWZ{gcqJt;pM@ExwdTeAKh z7R&z1cZ=m{qORXBdB2^{e}ErG=YK>QoY%Pi3HMg=`cq!3c;&wt`1=%B2CstGipGPe zxR*C&_!)oYX5bk7oWFXo0l&a6xf1+_`W1KauxLY!pn)sLP}SKqiut2h)D>KhSs!a^ z9z}iA>QO9fs*8P#%rUUKE=BCv{5HDx~R9Qzth|5mnT5VP_+qK0E<7p(LG8b9XAq z^}*IiauxoJzfe#GX$T0FB6qh}QWM}jK zJCW=aP6Q3lCRHOgN^y@upw4OiC?#6KxK z@JWsWBvaA~8naLu)2WOhX*A}8J0{$cRE7^T{`N4&_M#3KMFjG{e+rm%(&7lt;t)>g zq&h17R}>CcP@F6=OX&O$kK*=CW4PnASbc5GI)J;m-gn!yz^Ek!z@>;Y)~lHjM{xqK zX9mc;XjJHyM7o!R0TL%pS&__nYd;sEpR#WJmGT+!Q4I0X#5@MD~LeGy^Q z5kXxSLS2p{@E?`JW%6S*#MHv8q*ZHr5MKm8PR3OP1@UGjGDr_C5~+pE$MIrRSJx;p zKym@TLQC$$-AOTquil4iBy6FoRFE9?48|qo=_RG><)~KwS8n59973>03r)!IW!)AD Jp^F;S{SVg4K!^YU literal 0 HcmV?d00001 diff --git a/target/classes/com/muyu/common/security/utils/DictUtils.class b/target/classes/com/muyu/common/security/utils/DictUtils.class new file mode 100644 index 0000000000000000000000000000000000000000..0ec7ae8885c3f52007707442765da0e4bec90970 GIT binary patch literal 2819 zcmbtWZByGu5Pl95gdN2Mw;?6(X(3>fMlETZ29q~TXkHv>Fd;3pP0sQG!m`9jXJ-6Q z+JBLLp_$CIlh6H4olf_pGswmy?ev4CyY=4gv%Amk>CeC4`~lz=ybSt~GSF}07zP+l zZu4Dkx!l{dma5yrmJ9>4j_1hR45`Wds)0dpS1lzSWW4;p;g%l9B=cPS~D1%Le2^9F^o@^4=|Nn9)`ty zLK34aW^fY22684oz=sTz-F`x0Hv>nuw6A%`mfF*C@>JsXmUt{$1Tg8XA{C+zcUqKz zfe{m@@exBd)CSrKo9JQVFyj2GGIGYmS)3yyZ4_-I?*pRaHgJLAqKcNgPL)@=wZTKV z9s1t1^{BG6crOTeD}zh8Y~W)PpJ41gMTkPysYyHGqAwSluFG&L8I*ROUo&upA%7^@ ztx$@FRr4F%@vKTKROz1Qk{g)Rwdsv^S@4<&#ufyZ%|K*u9t9IqxJo3He%T3$jf+Vf zF&);+N5|i3v0X|6qL)aOx(cN4E=S{BDNQjLA71Ql!>60eAlI3bbK!YJfi>? z8AkQ7Zk_yEPBpiQ+o~5%H+WqT8k@`Kj@;VEMczw(O)w0V9ZxJa8&wgk@TyCaT-mp| zyUGJc?X|F9ZaE5TlhO|ne z;u1kQ*?pE_q!W2-r;Ywrm+tK0Fpgqv@is-0t~i@sL`#O{9_9L9=2grlO@KtI#(MbB zbMJt?#4D~ET&MT)I~pMx1Qf@KisW|v3E$CGeflr_{MTF!1)|~aibOO$dx?V1+OFV1 zLUAOK#{}yY?IJRCQGGU+M)#GbNK`Cp#SLdOn&_E|-wbTA;3#5em8)m!in*rD5ryV_ zUxqT^J5Pk%@@wJH0Dj8g1zsB9s)4EuV+S;Jyj{O8tk_lDQw!Xden52nIhjeV7#n@S zy_ze+v9j;in>)qBm=ltYgDb?}9_s4}{q`tMh_s0kcioC3MR5?dyHHN14a7r0U zMTXQT5O9XPpN))-#0Ri|Z%7Ud;{hI$gGaQE9Vy9Ul9Wl(hbQE45lggE4x8HHMAG3N zM&IImdFnMT{(|gOZanonCiakj#a_{o7)B2Lv>vAqm@5Qkf>!wioCytwK5y_Xo|5xr z(jEl+3*!b|lrfYFL0Q2w`d2}@Ly%OCI%40$r_T?P7{@+Mv0qOla#%Y{V|Bj@FSW6A z?YMH+_weQ8_O7spn~~i(m@EYa+(Z^Ll-1(C?JP-d;5q%*&Sot6jwEUI?WUoBPHzR> zD7?Wept6*#`u%-_q=N6GLZz@4X|3Z2dQ)cbBmHa$+U4l<1U9gBcN7llXkhdoD-`rH literal 0 HcmV?d00001 diff --git a/target/classes/com/muyu/common/security/utils/SecurityUtils.class b/target/classes/com/muyu/common/security/utils/SecurityUtils.class new file mode 100644 index 0000000000000000000000000000000000000000..5acb8ecfc645d977c8f002da616e1dce49ba7a21 GIT binary patch literal 2697 zcmbtWT~`xF7=9)MHVG?WC@RG&)`}32ZMChoU<*VLB_IM~wN<+$16h*n#@Pv){*K<* zKhT?Av^PlGb9#E!i~gvdzB9WCOGwY*oaSP7XZHPg-sk<8{Qb{we*n0Ry%b{TFc3G< zi7tk|U3<^A%C@^>J=ogidCAZ`$ex@;DRB!!{sBNi+crz^ypab&}GhZcczq!9uvLjW9SY= zSL_OBc;|@lx)e?*o;K01#8aX860aK=U{I*q!tJ#%`~=RDk+QerxG!pi(7+(W z)m9^Z-Iu}*b=xK>U{t8f(VArt5E9#KU=7;cV6VhiC$PO@d&_ot%q2b2>77cp$$ zl8N_mxeWkKa!opApC}f4WujAUg80|?%NqB|`?zMSWD99q1W$=sMJcP+A}yg_C`=x& zV8p;x6QdYom^c=4m`SVQHArmeHbK#DcqQ&K+*To<%x5(4kQ@W!ZJal1DoC1kL>1M@ z*foYzvo%@tg!7#(9nbBCg%1sUWa2t*5PK)Y6V&A*w{?w2p|Onj;wr3J5+ z?L5~2R|Vg8UZ-#ipBebv#1y6(Mp|i&CQ?^(Bae>1;>r0+Rn{2>+Ch1m0Cl7>$8EvI za0)Y+H85u)tNcWoI3SiPgXj{{f9%L&0`m+|DtApR;vRz;B42QXDx6zw97f7E%vD=9 zmXI^BY+?lu7km{((Ll0ATdb?zaP#~~(}nRU z3tyRdf~Ul5#g_RZr;fVzpC;NcLYna`L1O65IWAwRRkpa;u(!%|lFoT~yS!-&NA1JI zxGXy4WHQ%U7|rBWA@sQEHY(?#=KAoJz3{6bjqOJ)5lq$ zc3hWszO-yt!zc~MpDk1znhyO@HubO{BhI?is8yFiU&NaJN<>n+qB&plbqTsXi7t-?37@K|VlfvhXSdNS#WUoja&Zu|frE1iHL^#=zu zf0LNjES(9Ykw<~flSDDcHrYz5DIVI~M2ChjlsUl7HyFsIZ|~#FH!$fXePt literal 0 HcmV?d00001 diff --git a/target/cloud-common-security-3.6.3.jar b/target/cloud-common-security-3.6.3.jar new file mode 100644 index 0000000000000000000000000000000000000000..20521c757b497debdde5022aaea198725b578355 GIT binary patch literal 27533 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