From 45975660630052f49b2334c4e1b442d17dd15edf Mon Sep 17 00:00:00 2001
From: chaiyapeng <3535863041@qq.com>
Date: Fri, 26 Jul 2024 19:57: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
---
.gitignore | 46 +++
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 +
23 files changed, 1510 insertions(+)
create mode 100644 .gitignore
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
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..09bdfea
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,46 @@
+######################################################################
+# Build Tools
+
+.gradle
+/build/
+!gradle/wrapper/gradle-wrapper.jar
+
+target/
+!.mvn/wrapper/maven-wrapper.jar
+
+######################################################################
+# IDE
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### JRebel ###
+rebel.xml
+### NetBeans ###
+nbproject/private/
+build/*
+nbbuild/
+dist/
+nbdist/
+.nb-gradle/
+
+######################################################################
+# Others
+*.log
+*.xml.versionsBackup
+*.swp
+
+!*/build/*.java
+!*/build/*.html
+!*/build/*.xml
\ 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