From 27996f5dc512838f4e9fdb9a942c938dd112f46f Mon Sep 17 00:00:00 2001 From: Lemon <1161030327@qq.com> Date: Sun, 13 Aug 2023 15:34:39 +0800 Subject: [PATCH] common-security --- .gitignore | 46 +++ pom.xml | 41 ++ .../annotation/EnableCustomConfig.java | 27 ++ .../annotation/EnableRyFeignClients.java | 27 ++ .../common/security/annotation/InnerAuth.java | 19 + .../common/security/annotation/Logical.java | 20 + .../security/annotation/RequiresLogin.java | 18 + .../annotation/RequiresPermissions.java | 27 ++ .../security/annotation/RequiresRoles.java | 26 ++ .../security/aspect/InnerAuthAspect.java | 51 +++ .../security/aspect/PreAuthorizeAspect.java | 97 +++++ .../luck/common/security/auth/AuthLogic.java | 374 ++++++++++++++++++ .../luck/common/security/auth/AuthUtil.java | 167 ++++++++ .../security/config/ApplicationConfig.java | 22 ++ .../common/security/config/WebMvcConfig.java | 33 ++ .../feign/FeignAutoConfiguration.java | 20 + .../feign/FeignRequestInterceptor.java | 54 +++ .../handler/GlobalExceptionHandler.java | 159 ++++++++ .../interceptor/HeaderInterceptor.java | 55 +++ .../common/security/service/TokenService.java | 170 ++++++++ .../luck/common/security/utils/DictUtils.java | 76 ++++ .../common/security/utils/SecurityUtils.java | 118 ++++++ ...ot.autoconfigure.AutoConfiguration.imports | 5 + 23 files changed, 1652 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/main/java/com/luck/common/security/annotation/EnableCustomConfig.java create mode 100644 src/main/java/com/luck/common/security/annotation/EnableRyFeignClients.java create mode 100644 src/main/java/com/luck/common/security/annotation/InnerAuth.java create mode 100644 src/main/java/com/luck/common/security/annotation/Logical.java create mode 100644 src/main/java/com/luck/common/security/annotation/RequiresLogin.java create mode 100644 src/main/java/com/luck/common/security/annotation/RequiresPermissions.java create mode 100644 src/main/java/com/luck/common/security/annotation/RequiresRoles.java create mode 100644 src/main/java/com/luck/common/security/aspect/InnerAuthAspect.java create mode 100644 src/main/java/com/luck/common/security/aspect/PreAuthorizeAspect.java create mode 100644 src/main/java/com/luck/common/security/auth/AuthLogic.java create mode 100644 src/main/java/com/luck/common/security/auth/AuthUtil.java create mode 100644 src/main/java/com/luck/common/security/config/ApplicationConfig.java create mode 100644 src/main/java/com/luck/common/security/config/WebMvcConfig.java create mode 100644 src/main/java/com/luck/common/security/feign/FeignAutoConfiguration.java create mode 100644 src/main/java/com/luck/common/security/feign/FeignRequestInterceptor.java create mode 100644 src/main/java/com/luck/common/security/handler/GlobalExceptionHandler.java create mode 100644 src/main/java/com/luck/common/security/interceptor/HeaderInterceptor.java create mode 100644 src/main/java/com/luck/common/security/service/TokenService.java create mode 100644 src/main/java/com/luck/common/security/utils/DictUtils.java create mode 100644 src/main/java/com/luck/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..8ff2d34 --- /dev/null +++ b/pom.xml @@ -0,0 +1,41 @@ + + + + com.luck + luck-common + 3.6.3 + + 4.0.0 + + luck-common-security + + 3.6.3 + + luck-common-security安全模块 + + + + + + + org.springframework + spring-webmvc + + + + + + + com.luck + luck-system-remote + + + + com.luck + luck-common-redis + + + + + diff --git a/src/main/java/com/luck/common/security/annotation/EnableCustomConfig.java b/src/main/java/com/luck/common/security/annotation/EnableCustomConfig.java new file mode 100644 index 0000000..5129827 --- /dev/null +++ b/src/main/java/com/luck/common/security/annotation/EnableCustomConfig.java @@ -0,0 +1,27 @@ +package com.luck.common.security.annotation; + +import com.luck.common.security.config.ApplicationConfig; +import com.luck.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.luck.**.mapper") +// 开启线程异步执行 +@EnableAsync +// 自动加载类 +@Import({ ApplicationConfig.class, FeignAutoConfiguration.class }) +public @interface EnableCustomConfig +{ + +} diff --git a/src/main/java/com/luck/common/security/annotation/EnableRyFeignClients.java b/src/main/java/com/luck/common/security/annotation/EnableRyFeignClients.java new file mode 100644 index 0000000..a6edbc6 --- /dev/null +++ b/src/main/java/com/luck/common/security/annotation/EnableRyFeignClients.java @@ -0,0 +1,27 @@ +package com.luck.common.security.annotation; + +import org.springframework.cloud.openfeign.EnableFeignClients; +import java.lang.annotation.*; + +/** + * 自定义feign注解 + * 添加basePackages路径 + * + * @author ruoyi + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@EnableFeignClients +public @interface EnableRyFeignClients +{ + String[] value() default {}; + + String[] basePackages() default { "com.luck" }; + + Class[] basePackageClasses() default {}; + + Class[] defaultConfiguration() default {}; + + Class[] clients() default {}; +} diff --git a/src/main/java/com/luck/common/security/annotation/InnerAuth.java b/src/main/java/com/luck/common/security/annotation/InnerAuth.java new file mode 100644 index 0000000..763ee7d --- /dev/null +++ b/src/main/java/com/luck/common/security/annotation/InnerAuth.java @@ -0,0 +1,19 @@ +package com.luck.common.security.annotation; + +import java.lang.annotation.*; + +/** + * 内部认证注解 + * + * @author ruoyi + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface InnerAuth +{ + /** + * 是否校验用户信息 + */ + boolean isUser() default false; +} \ No newline at end of file diff --git a/src/main/java/com/luck/common/security/annotation/Logical.java b/src/main/java/com/luck/common/security/annotation/Logical.java new file mode 100644 index 0000000..a56ea6c --- /dev/null +++ b/src/main/java/com/luck/common/security/annotation/Logical.java @@ -0,0 +1,20 @@ +package com.luck.common.security.annotation; + +/** + * 权限注解的验证模式 + * + * @author ruoyi + * + */ +public enum Logical +{ + /** + * 必须具有所有的元素 + */ + AND, + + /** + * 只需具有其中一个元素 + */ + OR +} diff --git a/src/main/java/com/luck/common/security/annotation/RequiresLogin.java b/src/main/java/com/luck/common/security/annotation/RequiresLogin.java new file mode 100644 index 0000000..aeb3756 --- /dev/null +++ b/src/main/java/com/luck/common/security/annotation/RequiresLogin.java @@ -0,0 +1,18 @@ +package com.luck.common.security.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 登录认证:只有登录之后才能进入该方法 + * + * @author ruoyi + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.TYPE }) +public @interface RequiresLogin +{ +} diff --git a/src/main/java/com/luck/common/security/annotation/RequiresPermissions.java b/src/main/java/com/luck/common/security/annotation/RequiresPermissions.java new file mode 100644 index 0000000..f7e482d --- /dev/null +++ b/src/main/java/com/luck/common/security/annotation/RequiresPermissions.java @@ -0,0 +1,27 @@ +package com.luck.common.security.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 权限认证:必须具有指定权限才能进入该方法 + * + * @author ruoyi + * + */ +@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/luck/common/security/annotation/RequiresRoles.java b/src/main/java/com/luck/common/security/annotation/RequiresRoles.java new file mode 100644 index 0000000..58eb041 --- /dev/null +++ b/src/main/java/com/luck/common/security/annotation/RequiresRoles.java @@ -0,0 +1,26 @@ +package com.luck.common.security.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 角色认证:必须具有指定角色标识才能进入该方法 + * + * @author ruoyi + */ +@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/luck/common/security/aspect/InnerAuthAspect.java b/src/main/java/com/luck/common/security/aspect/InnerAuthAspect.java new file mode 100644 index 0000000..1953e43 --- /dev/null +++ b/src/main/java/com/luck/common/security/aspect/InnerAuthAspect.java @@ -0,0 +1,51 @@ +package com.luck.common.security.aspect; + +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; +import com.luck.common.core.constant.SecurityConstants; +import com.luck.common.core.exception.InnerAuthException; +import com.luck.common.core.utils.ServletUtils; +import com.luck.common.core.utils.StringUtils; +import com.luck.common.security.annotation.InnerAuth; + +/** + * 内部服务调用验证处理 + * + * @author ruoyi + */ +@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/luck/common/security/aspect/PreAuthorizeAspect.java b/src/main/java/com/luck/common/security/aspect/PreAuthorizeAspect.java new file mode 100644 index 0000000..c40e49b --- /dev/null +++ b/src/main/java/com/luck/common/security/aspect/PreAuthorizeAspect.java @@ -0,0 +1,97 @@ +package com.luck.common.security.aspect; + +import java.lang.reflect.Method; +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 com.luck.common.security.annotation.RequiresLogin; +import com.luck.common.security.annotation.RequiresPermissions; +import com.luck.common.security.annotation.RequiresRoles; +import com.luck.common.security.auth.AuthUtil; + +/** + * 基于 Spring Aop 的注解鉴权 + * + * @author kong + */ +@Aspect +@Component +public class PreAuthorizeAspect +{ + /** + * 构建 + */ + public PreAuthorizeAspect() + { + } + + /** + * 定义AOP签名 (切入所有使用鉴权注解的方法) + */ + public static final String POINTCUT_SIGN = " @annotation(com.luck.common.security.annotation.RequiresLogin) || " + + "@annotation(com.luck.common.security.annotation.RequiresPermissions) || " + + "@annotation(com.luck.common.security.annotation.RequiresRoles)"; + + /** + * 声明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/luck/common/security/auth/AuthLogic.java b/src/main/java/com/luck/common/security/auth/AuthLogic.java new file mode 100644 index 0000000..76caedd --- /dev/null +++ b/src/main/java/com/luck/common/security/auth/AuthLogic.java @@ -0,0 +1,374 @@ +package com.luck.common.security.auth; + +import com.luck.common.core.context.SecurityContextHolder; +import com.luck.common.core.exception.auth.NotLoginException; +import com.luck.common.core.exception.auth.NotPermissionException; +import com.luck.common.core.exception.auth.NotRoleException; +import com.luck.common.core.utils.SpringUtils; +import com.luck.common.core.utils.StringUtils; +import com.luck.common.security.annotation.Logical; +import com.luck.common.security.annotation.RequiresLogin; +import com.luck.common.security.annotation.RequiresPermissions; +import com.luck.common.security.annotation.RequiresRoles; +import com.luck.common.security.service.TokenService; +import com.luck.common.security.utils.SecurityUtils; +import com.luck.system.model.LoginUser; +import org.springframework.util.PatternMatchUtils; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +/** + * Token 权限验证,逻辑实现类 + * + * @author ruoyi + */ +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.contains(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.contains(x) || PatternMatchUtils.simpleMatch(x, role)); + } +} diff --git a/src/main/java/com/luck/common/security/auth/AuthUtil.java b/src/main/java/com/luck/common/security/auth/AuthUtil.java new file mode 100644 index 0000000..626cb39 --- /dev/null +++ b/src/main/java/com/luck/common/security/auth/AuthUtil.java @@ -0,0 +1,167 @@ +package com.luck.common.security.auth; + +import com.luck.common.security.annotation.RequiresPermissions; +import com.luck.common.security.annotation.RequiresRoles; +import com.luck.system.model.LoginUser; + +/** + * Token 权限验证工具类 + * + * @author ruoyi + */ +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/luck/common/security/config/ApplicationConfig.java b/src/main/java/com/luck/common/security/config/ApplicationConfig.java new file mode 100644 index 0000000..41ef1e4 --- /dev/null +++ b/src/main/java/com/luck/common/security/config/ApplicationConfig.java @@ -0,0 +1,22 @@ +package com.luck.common.security.config; + +import java.util.TimeZone; +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.context.annotation.Bean; + +/** + * 系统配置 + * + * @author ruoyi + */ +public class ApplicationConfig +{ + /** + * 时区配置 + */ + @Bean + public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization() + { + return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(TimeZone.getDefault()); + } +} diff --git a/src/main/java/com/luck/common/security/config/WebMvcConfig.java b/src/main/java/com/luck/common/security/config/WebMvcConfig.java new file mode 100644 index 0000000..f6843fa --- /dev/null +++ b/src/main/java/com/luck/common/security/config/WebMvcConfig.java @@ -0,0 +1,33 @@ +package com.luck.common.security.config; + +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import com.luck.common.security.interceptor.HeaderInterceptor; + +/** + * 拦截器配置 + * + * @author ruoyi + */ +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/luck/common/security/feign/FeignAutoConfiguration.java b/src/main/java/com/luck/common/security/feign/FeignAutoConfiguration.java new file mode 100644 index 0000000..36fc3bf --- /dev/null +++ b/src/main/java/com/luck/common/security/feign/FeignAutoConfiguration.java @@ -0,0 +1,20 @@ +package com.luck.common.security.feign; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import feign.RequestInterceptor; + +/** + * Feign 配置注册 + * + * @author ruoyi + **/ +@Configuration +public class FeignAutoConfiguration +{ + @Bean + public RequestInterceptor requestInterceptor() + { + return new FeignRequestInterceptor(); + } +} diff --git a/src/main/java/com/luck/common/security/feign/FeignRequestInterceptor.java b/src/main/java/com/luck/common/security/feign/FeignRequestInterceptor.java new file mode 100644 index 0000000..7db0492 --- /dev/null +++ b/src/main/java/com/luck/common/security/feign/FeignRequestInterceptor.java @@ -0,0 +1,54 @@ +package com.luck.common.security.feign; + +import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import org.springframework.stereotype.Component; +import com.luck.common.core.constant.SecurityConstants; +import com.luck.common.core.utils.ServletUtils; +import com.luck.common.core.utils.StringUtils; +import com.luck.common.core.utils.ip.IpUtils; +import feign.RequestInterceptor; +import feign.RequestTemplate; + +/** + * feign 请求拦截器 + * + * @author ruoyi + */ +@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()); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/luck/common/security/handler/GlobalExceptionHandler.java b/src/main/java/com/luck/common/security/handler/GlobalExceptionHandler.java new file mode 100644 index 0000000..2407fd9 --- /dev/null +++ b/src/main/java/com/luck/common/security/handler/GlobalExceptionHandler.java @@ -0,0 +1,159 @@ +package com.luck.common.security.handler; + +import com.luck.common.core.constant.HttpStatus; +import com.luck.common.core.exception.DemoModeException; +import com.luck.common.core.exception.InnerAuthException; +import com.luck.common.core.exception.ServiceException; +import com.luck.common.core.exception.auth.NotPermissionException; +import com.luck.common.core.exception.auth.NotRoleException; +import com.luck.common.core.utils.StringUtils; +import com.luck.common.core.web.domain.AjaxResult; +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 javax.servlet.http.HttpServletRequest; + +/** + * 全局异常处理器 + * + * @author ruoyi + */ +@RestControllerAdvice +public class GlobalExceptionHandler +{ + private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); + + /** + * 权限码异常 + */ + @ExceptionHandler(NotPermissionException.class) + public AjaxResult handleNotPermissionException(NotPermissionException e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',权限码校验失败'{}'", requestURI, e.getMessage()); + return AjaxResult.error(HttpStatus.FORBIDDEN, "没有访问权限,请联系管理员授权"); + } + + /** + * 角色权限异常 + */ + @ExceptionHandler(NotRoleException.class) + public AjaxResult handleNotRoleException(NotRoleException e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',角色权限校验失败'{}'", requestURI, e.getMessage()); + return AjaxResult.error(HttpStatus.FORBIDDEN, "没有访问权限,请联系管理员授权"); + } + + /** + * 请求方式不支持 + */ + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) + public AjaxResult handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod()); + return AjaxResult.error(e.getMessage()); + } + + /** + * 业务异常 + */ + @ExceptionHandler(ServiceException.class) + public AjaxResult handleServiceException(ServiceException e, HttpServletRequest request) + { + log.error(e.getMessage(), e); + Integer code = e.getCode(); + return StringUtils.isNotNull(code) ? AjaxResult.error(code, e.getMessage()) : AjaxResult.error(e.getMessage()); + } + + /** + * 请求路径中缺少必需的路径变量 + */ + @ExceptionHandler(MissingPathVariableException.class) + public AjaxResult handleMissingPathVariableException(MissingPathVariableException e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求路径中缺少必需的路径变量'{}',发生系统异常.", requestURI, e); + return AjaxResult.error(String.format("请求路径中缺少必需的路径变量[%s]", e.getVariableName())); + } + + /** + * 请求参数类型不匹配 + */ + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + public AjaxResult handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求参数类型不匹配'{}',发生系统异常.", requestURI, e); + return AjaxResult.error(String.format("请求参数类型不匹配,参数[%s]要求类型为:'%s',但输入值为:'%s'", e.getName(), e.getRequiredType().getName(), e.getValue())); + } + + /** + * 拦截未知的运行时异常 + */ + @ExceptionHandler(RuntimeException.class) + public AjaxResult handleRuntimeException(RuntimeException e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',发生未知异常.", requestURI, e); + return AjaxResult.error(e.getMessage()); + } + + /** + * 系统异常 + */ + @ExceptionHandler(Exception.class) + public AjaxResult handleException(Exception e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',发生系统异常.", requestURI, e); + return AjaxResult.error(e.getMessage()); + } + + /** + * 自定义验证异常 + */ + @ExceptionHandler(BindException.class) + public AjaxResult handleBindException(BindException e) + { + log.error(e.getMessage(), e); + String message = e.getAllErrors().get(0).getDefaultMessage(); + return AjaxResult.error(message); + } + + /** + * 自定义验证异常 + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e) + { + log.error(e.getMessage(), e); + String message = e.getBindingResult().getFieldError().getDefaultMessage(); + return AjaxResult.error(message); + } + + /** + * 内部认证异常 + */ + @ExceptionHandler(InnerAuthException.class) + public AjaxResult handleInnerAuthException(InnerAuthException e) + { + return AjaxResult.error(e.getMessage()); + } + + /** + * 演示模式异常 + */ + @ExceptionHandler(DemoModeException.class) + public AjaxResult handleDemoModeException(DemoModeException e) + { + return AjaxResult.error("演示模式,不允许操作"); + } +} diff --git a/src/main/java/com/luck/common/security/interceptor/HeaderInterceptor.java b/src/main/java/com/luck/common/security/interceptor/HeaderInterceptor.java new file mode 100644 index 0000000..fe15f85 --- /dev/null +++ b/src/main/java/com/luck/common/security/interceptor/HeaderInterceptor.java @@ -0,0 +1,55 @@ +package com.luck.common.security.interceptor; + +import com.luck.common.core.constant.SecurityConstants; +import com.luck.common.core.context.SecurityContextHolder; +import com.luck.common.core.utils.ServletUtils; +import com.luck.common.core.utils.StringUtils; +import com.luck.common.security.auth.AuthUtil; +import com.luck.common.security.utils.SecurityUtils; +import com.luck.system.model.LoginUser; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.AsyncHandlerInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * 自定义请求头拦截器,将Header数据封装到线程变量中方便获取 + * 注意:此拦截器会同时验证当前用户有效期自动刷新有效期 + * + * @author ruoyi + */ +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/luck/common/security/service/TokenService.java b/src/main/java/com/luck/common/security/service/TokenService.java new file mode 100644 index 0000000..f99fa85 --- /dev/null +++ b/src/main/java/com/luck/common/security/service/TokenService.java @@ -0,0 +1,170 @@ +package com.luck.common.security.service; + +import com.luck.common.core.constant.CacheConstants; +import com.luck.common.core.constant.SecurityConstants; +import com.luck.common.core.utils.JwtUtils; +import com.luck.common.core.utils.ServletUtils; +import com.luck.common.core.utils.StringUtils; +import com.luck.common.core.utils.ip.IpUtils; +import com.luck.common.core.utils.uuid.IdUtils; +import com.luck.common.redis.service.RedisService; +import com.luck.common.security.utils.SecurityUtils; +import com.luck.system.model.LoginUser; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * token验证处理 + * + * @author ruoyi + */ +@Component +public class TokenService +{ + @Autowired + private RedisService redisService; + + protected static final long MILLIS_SECOND = 1000; + + protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND; + + private final static long expireTime = CacheConstants.EXPIRATION; + + private final static String ACCESS_TOKEN = CacheConstants.LOGIN_TOKEN_KEY; + + private final static Long MILLIS_MINUTE_TEN = CacheConstants.REFRESH_TIME * MILLIS_MINUTE; + + /** + * 创建令牌 + */ + 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()); + } + + /** + * 获取用户身份信息 + * + * @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) + { + } + return user; + } + + /** + * 设置用户身份信息 + */ + public void setLoginUser(LoginUser loginUser) + { + if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken())) + { + refreshToken(loginUser); + } + } + + /** + * 删除用户缓存信息 + */ + 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; + } +} \ No newline at end of file diff --git a/src/main/java/com/luck/common/security/utils/DictUtils.java b/src/main/java/com/luck/common/security/utils/DictUtils.java new file mode 100644 index 0000000..28e8acb --- /dev/null +++ b/src/main/java/com/luck/common/security/utils/DictUtils.java @@ -0,0 +1,76 @@ +package com.luck.common.security.utils; + +import com.alibaba.fastjson2.JSONArray; +import com.luck.common.core.constant.CacheConstants; +import com.luck.common.core.utils.SpringUtils; +import com.luck.common.core.utils.StringUtils; +import com.luck.common.redis.service.RedisService; +import com.luck.system.domain.SysDictData; + +import java.util.Collection; +import java.util.List; + +/** + * 字典工具类 + * + * @author ruoyi + */ +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/luck/common/security/utils/SecurityUtils.java b/src/main/java/com/luck/common/security/utils/SecurityUtils.java new file mode 100644 index 0000000..778fbda --- /dev/null +++ b/src/main/java/com/luck/common/security/utils/SecurityUtils.java @@ -0,0 +1,118 @@ +package com.luck.common.security.utils; + +import com.luck.common.core.constant.SecurityConstants; +import com.luck.common.core.constant.TokenConstants; +import com.luck.common.core.context.SecurityContextHolder; +import com.luck.common.core.utils.ServletUtils; +import com.luck.common.core.utils.StringUtils; +import com.luck.system.model.LoginUser; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +import javax.servlet.http.HttpServletRequest; + +/** + * 权限获取工具类 + * + * @author ruoyi + */ +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..03ae5ac --- /dev/null +++ b/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,5 @@ +com.luck.common.security.config.WebMvcConfig +com.luck.common.security.service.TokenService +com.luck.common.security.aspect.PreAuthorizeAspect +com.luck.common.security.aspect.InnerAuthAspect +com.luck.common.security.handler.GlobalExceptionHandler