commit 6a93f233644afd87336b72db1a89aa37fc9460e9
Author: bai <173792339@qq.com>
Date: Sun Jul 28 16:43:17 2024 +0800
初始化
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 0000000..18ac118
Binary files /dev/null and b/target/classes/com/muyu/common/security/annotation/EnableCustomConfig.class differ
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 0000000..264e1d4
Binary files /dev/null and b/target/classes/com/muyu/common/security/annotation/EnableMyFeignClients.class differ
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 0000000..78c876b
Binary files /dev/null and b/target/classes/com/muyu/common/security/annotation/InnerAuth.class differ
diff --git a/target/classes/com/muyu/common/security/annotation/Logical.class b/target/classes/com/muyu/common/security/annotation/Logical.class
new file mode 100644
index 0000000..6ba594a
Binary files /dev/null and b/target/classes/com/muyu/common/security/annotation/Logical.class differ
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 0000000..a9c1528
Binary files /dev/null and b/target/classes/com/muyu/common/security/annotation/RequiresLogin.class differ
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 0000000..dd2e6a9
Binary files /dev/null and b/target/classes/com/muyu/common/security/annotation/RequiresPermissions.class differ
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 0000000..f164119
Binary files /dev/null and b/target/classes/com/muyu/common/security/annotation/RequiresRoles.class differ
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 0000000..f9bfb72
Binary files /dev/null and b/target/classes/com/muyu/common/security/aspect/InnerAuthAspect.class differ
diff --git a/target/classes/com/muyu/common/security/aspect/PreAuthorizeAspect.class b/target/classes/com/muyu/common/security/aspect/PreAuthorizeAspect.class
new file mode 100644
index 0000000..a4781cf
Binary files /dev/null and b/target/classes/com/muyu/common/security/aspect/PreAuthorizeAspect.class differ
diff --git a/target/classes/com/muyu/common/security/auth/AuthLogic.class b/target/classes/com/muyu/common/security/auth/AuthLogic.class
new file mode 100644
index 0000000..0c05f46
Binary files /dev/null and b/target/classes/com/muyu/common/security/auth/AuthLogic.class differ
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 0000000..ecfde4e
Binary files /dev/null and b/target/classes/com/muyu/common/security/auth/AuthUtil.class differ
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 0000000..ef426ec
Binary files /dev/null and b/target/classes/com/muyu/common/security/config/ApplicationConfig.class differ
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 0000000..0512c7e
Binary files /dev/null and b/target/classes/com/muyu/common/security/config/WebMvcConfig.class differ
diff --git a/target/classes/com/muyu/common/security/feign/FeignAutoConfiguration.class b/target/classes/com/muyu/common/security/feign/FeignAutoConfiguration.class
new file mode 100644
index 0000000..b07b7ab
Binary files /dev/null and b/target/classes/com/muyu/common/security/feign/FeignAutoConfiguration.class differ
diff --git a/target/classes/com/muyu/common/security/feign/FeignRequestInterceptor.class b/target/classes/com/muyu/common/security/feign/FeignRequestInterceptor.class
new file mode 100644
index 0000000..a508c4d
Binary files /dev/null and b/target/classes/com/muyu/common/security/feign/FeignRequestInterceptor.class differ
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 0000000..a19e777
Binary files /dev/null and b/target/classes/com/muyu/common/security/handler/GlobalExceptionHandler.class differ
diff --git a/target/classes/com/muyu/common/security/interceptor/HeaderInterceptor.class b/target/classes/com/muyu/common/security/interceptor/HeaderInterceptor.class
new file mode 100644
index 0000000..bc417e9
Binary files /dev/null and b/target/classes/com/muyu/common/security/interceptor/HeaderInterceptor.class differ
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 0000000..a688576
Binary files /dev/null and b/target/classes/com/muyu/common/security/service/TokenService.class differ
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 0000000..0ec7ae8
Binary files /dev/null and b/target/classes/com/muyu/common/security/utils/DictUtils.class differ
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 0000000..5acb8ec
Binary files /dev/null and b/target/classes/com/muyu/common/security/utils/SecurityUtils.class differ
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 0000000..20521c7
Binary files /dev/null and b/target/cloud-common-security-3.6.3.jar differ
diff --git a/target/maven-archiver/pom.properties b/target/maven-archiver/pom.properties
new file mode 100644
index 0000000..b4cf32e
--- /dev/null
+++ b/target/maven-archiver/pom.properties
@@ -0,0 +1,3 @@
+artifactId=cloud-common-security
+groupId=com.muyu
+version=3.6.3
diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
new file mode 100644
index 0000000..c31af96
--- /dev/null
+++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
@@ -0,0 +1,20 @@
+com\muyu\common\security\aspect\PreAuthorizeAspect.class
+com\muyu\common\security\interceptor\HeaderInterceptor.class
+com\muyu\common\security\feign\FeignAutoConfiguration.class
+com\muyu\common\security\annotation\InnerAuth.class
+com\muyu\common\security\aspect\InnerAuthAspect.class
+com\muyu\common\security\auth\AuthLogic.class
+com\muyu\common\security\config\ApplicationConfig.class
+com\muyu\common\security\annotation\EnableCustomConfig.class
+com\muyu\common\security\annotation\RequiresPermissions.class
+com\muyu\common\security\feign\FeignRequestInterceptor.class
+com\muyu\common\security\annotation\EnableMyFeignClients.class
+com\muyu\common\security\annotation\RequiresLogin.class
+com\muyu\common\security\annotation\RequiresRoles.class
+com\muyu\common\security\auth\AuthUtil.class
+com\muyu\common\security\handler\GlobalExceptionHandler.class
+com\muyu\common\security\annotation\Logical.class
+com\muyu\common\security\utils\DictUtils.class
+com\muyu\common\security\utils\SecurityUtils.class
+com\muyu\common\security\config\WebMvcConfig.class
+com\muyu\common\security\service\TokenService.class
diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
new file mode 100644
index 0000000..c120cac
--- /dev/null
+++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
@@ -0,0 +1,20 @@
+D:\master\yjs-cloud-2112\cloud-common-security\src\main\java\com\muyu\common\security\annotation\InnerAuth.java
+D:\master\yjs-cloud-2112\cloud-common-security\src\main\java\com\muyu\common\security\utils\SecurityUtils.java
+D:\master\yjs-cloud-2112\cloud-common-security\src\main\java\com\muyu\common\security\feign\FeignRequestInterceptor.java
+D:\master\yjs-cloud-2112\cloud-common-security\src\main\java\com\muyu\common\security\aspect\InnerAuthAspect.java
+D:\master\yjs-cloud-2112\cloud-common-security\src\main\java\com\muyu\common\security\auth\AuthLogic.java
+D:\master\yjs-cloud-2112\cloud-common-security\src\main\java\com\muyu\common\security\feign\FeignAutoConfiguration.java
+D:\master\yjs-cloud-2112\cloud-common-security\src\main\java\com\muyu\common\security\annotation\EnableMyFeignClients.java
+D:\master\yjs-cloud-2112\cloud-common-security\src\main\java\com\muyu\common\security\interceptor\HeaderInterceptor.java
+D:\master\yjs-cloud-2112\cloud-common-security\src\main\java\com\muyu\common\security\annotation\Logical.java
+D:\master\yjs-cloud-2112\cloud-common-security\src\main\java\com\muyu\common\security\handler\GlobalExceptionHandler.java
+D:\master\yjs-cloud-2112\cloud-common-security\src\main\java\com\muyu\common\security\utils\DictUtils.java
+D:\master\yjs-cloud-2112\cloud-common-security\src\main\java\com\muyu\common\security\annotation\RequiresRoles.java
+D:\master\yjs-cloud-2112\cloud-common-security\src\main\java\com\muyu\common\security\annotation\EnableCustomConfig.java
+D:\master\yjs-cloud-2112\cloud-common-security\src\main\java\com\muyu\common\security\annotation\RequiresLogin.java
+D:\master\yjs-cloud-2112\cloud-common-security\src\main\java\com\muyu\common\security\annotation\RequiresPermissions.java
+D:\master\yjs-cloud-2112\cloud-common-security\src\main\java\com\muyu\common\security\config\ApplicationConfig.java
+D:\master\yjs-cloud-2112\cloud-common-security\src\main\java\com\muyu\common\security\aspect\PreAuthorizeAspect.java
+D:\master\yjs-cloud-2112\cloud-common-security\src\main\java\com\muyu\common\security\config\WebMvcConfig.java
+D:\master\yjs-cloud-2112\cloud-common-security\src\main\java\com\muyu\common\security\auth\AuthUtil.java
+D:\master\yjs-cloud-2112\cloud-common-security\src\main\java\com\muyu\common\security\service\TokenService.java