初始化
commit
6a93f23364
|
@ -0,0 +1,8 @@
|
|||
# 默认忽略的文件
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# 基于编辑器的 HTTP 客户端请求
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<annotationProcessing>
|
||||
<profile name="Maven default annotation processors profile" enabled="true">
|
||||
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||
<outputRelativeToContentRoot value="true" />
|
||||
<module name="cloud-common-security" />
|
||||
</profile>
|
||||
</annotationProcessing>
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding">
|
||||
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RemoteRepositoriesConfiguration">
|
||||
<remote-repository>
|
||||
<option name="id" value="public" />
|
||||
<option name="name" value="aliyun nexus" />
|
||||
<option name="url" value="https://maven.aliyun.com/repository/public" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Central Repository" />
|
||||
<option name="url" value="http://8.153.39.174:8081/repository/maven-public/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Central Repository" />
|
||||
<option name="url" value="http://maven.aliyun.com/nexus/content/groups/public/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Maven Central repository" />
|
||||
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="jboss.community" />
|
||||
<option name="name" value="JBoss Community repository" />
|
||||
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||
</remote-repository>
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="MavenProjectsManager">
|
||||
<option name="originalFiles">
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/pom.xml" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="MavenRunner">
|
||||
<option name="delegateBuildToMaven" value="true" />
|
||||
<option name="jreName" value="1.8" />
|
||||
<option name="skipTests" value="true" />
|
||||
<option name="vmOptions" value="-DarchetypeCatalog=internal" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17 (2)" project-jdk-type="JavaSDK" />
|
||||
</project>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,39 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>com.muyu</groupId>
|
||||
<artifactId>cloud-common</artifactId>
|
||||
<version>3.6.3</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>cloud-common-security</artifactId>
|
||||
|
||||
<description>
|
||||
cloud-common-security安全模块
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- Spring Web -->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- MuYu Common Redis-->
|
||||
<dependency>
|
||||
<groupId>com.muyu</groupId>
|
||||
<artifactId>cloud-common-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- MuYu Common System-->
|
||||
<dependency>
|
||||
<groupId>com.muyu</groupId>
|
||||
<artifactId>cloud-common-system</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -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 {
|
||||
|
||||
}
|
|
@ -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 {};
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package com.muyu.common.security.annotation;
|
||||
|
||||
/**
|
||||
* 权限注解的验证模式
|
||||
*
|
||||
* @author muyu
|
||||
*/
|
||||
public enum Logical {
|
||||
/**
|
||||
* 必须具有所有的元素
|
||||
*/
|
||||
AND,
|
||||
|
||||
/**
|
||||
* 只需具有其中一个元素
|
||||
*/
|
||||
OR
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String> permissionList = getPermiList();
|
||||
for (String permission : permissions) {
|
||||
if (!hasPermi(permissionList, permission)) {
|
||||
throw new NotPermissionException(permission);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证用户是否含有指定权限,只需包含其中一个
|
||||
*
|
||||
* @param permissions 权限码数组
|
||||
*/
|
||||
public void checkPermiOr (String... permissions) {
|
||||
Set<String> 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<String> roleList = getRoleList();
|
||||
for (String role : roles) {
|
||||
if (!hasRole(roleList, role)) {
|
||||
throw new NotRoleException(role);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证用户是否含有指定角色,只需包含其中一个
|
||||
*
|
||||
* @param roles 角色标识数组
|
||||
*/
|
||||
public void checkRoleOr (String... roles) {
|
||||
Set<String> 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<String> getRoleList () {
|
||||
try {
|
||||
LoginUser loginUser = getLoginUser();
|
||||
return loginUser.getRoles();
|
||||
} catch (Exception e) {
|
||||
return new HashSet<>();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前账号的权限列表
|
||||
*
|
||||
* @return 权限列表
|
||||
*/
|
||||
public Set<String> getPermiList () {
|
||||
try {
|
||||
LoginUser loginUser = getLoginUser();
|
||||
return loginUser.getPermissions();
|
||||
} catch (Exception e) {
|
||||
return new HashSet<>();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否包含权限
|
||||
*
|
||||
* @param authorities 权限列表
|
||||
* @param permission 权限字符串
|
||||
*
|
||||
* @return 用户是否具备某权限
|
||||
*/
|
||||
public boolean hasPermi (Collection<String> 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<String> roles, String role) {
|
||||
return roles.stream().filter(StringUtils::hasText)
|
||||
.anyMatch(x -> SUPER_ADMIN.equals(x) || PatternMatchUtils.simpleMatch(x, role));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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<String, String> 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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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("演示模式,不允许操作");
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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<String, Object> 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<String, Object> claimsMap = new HashMap<String, Object>();
|
||||
claimsMap.put(SecurityConstants.USER_KEY, token);
|
||||
claimsMap.put(SecurityConstants.DETAILS_USER_ID, userId);
|
||||
claimsMap.put(SecurityConstants.DETAILS_USERNAME, userName);
|
||||
|
||||
// 接口返回信息
|
||||
Map<String, Object> rspMap = new HashMap<String, Object>();
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -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<SysDictData> dictDatas) {
|
||||
SpringUtils.getBean(RedisService.class).setCacheObject(getCacheKey(key), dictDatas);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字典缓存
|
||||
*
|
||||
* @param key 参数键
|
||||
*
|
||||
* @return dictDatas 字典数据列表
|
||||
*/
|
||||
public static List<SysDictData> 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<String> 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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,3 @@
|
|||
artifactId=cloud-common-security
|
||||
groupId=com.muyu
|
||||
version=3.6.3
|
|
@ -0,0 +1,20 @@
|
|||
com\muyu\common\security\aspect\PreAuthorizeAspect.class
|
||||
com\muyu\common\security\interceptor\HeaderInterceptor.class
|
||||
com\muyu\common\security\feign\FeignAutoConfiguration.class
|
||||
com\muyu\common\security\annotation\InnerAuth.class
|
||||
com\muyu\common\security\aspect\InnerAuthAspect.class
|
||||
com\muyu\common\security\auth\AuthLogic.class
|
||||
com\muyu\common\security\config\ApplicationConfig.class
|
||||
com\muyu\common\security\annotation\EnableCustomConfig.class
|
||||
com\muyu\common\security\annotation\RequiresPermissions.class
|
||||
com\muyu\common\security\feign\FeignRequestInterceptor.class
|
||||
com\muyu\common\security\annotation\EnableMyFeignClients.class
|
||||
com\muyu\common\security\annotation\RequiresLogin.class
|
||||
com\muyu\common\security\annotation\RequiresRoles.class
|
||||
com\muyu\common\security\auth\AuthUtil.class
|
||||
com\muyu\common\security\handler\GlobalExceptionHandler.class
|
||||
com\muyu\common\security\annotation\Logical.class
|
||||
com\muyu\common\security\utils\DictUtils.class
|
||||
com\muyu\common\security\utils\SecurityUtils.class
|
||||
com\muyu\common\security\config\WebMvcConfig.class
|
||||
com\muyu\common\security\service\TokenService.class
|
|
@ -0,0 +1,20 @@
|
|||
D:\master\yjs-cloud-2112\cloud-common-security\src\main\java\com\muyu\common\security\annotation\InnerAuth.java
|
||||
D:\master\yjs-cloud-2112\cloud-common-security\src\main\java\com\muyu\common\security\utils\SecurityUtils.java
|
||||
D:\master\yjs-cloud-2112\cloud-common-security\src\main\java\com\muyu\common\security\feign\FeignRequestInterceptor.java
|
||||
D:\master\yjs-cloud-2112\cloud-common-security\src\main\java\com\muyu\common\security\aspect\InnerAuthAspect.java
|
||||
D:\master\yjs-cloud-2112\cloud-common-security\src\main\java\com\muyu\common\security\auth\AuthLogic.java
|
||||
D:\master\yjs-cloud-2112\cloud-common-security\src\main\java\com\muyu\common\security\feign\FeignAutoConfiguration.java
|
||||
D:\master\yjs-cloud-2112\cloud-common-security\src\main\java\com\muyu\common\security\annotation\EnableMyFeignClients.java
|
||||
D:\master\yjs-cloud-2112\cloud-common-security\src\main\java\com\muyu\common\security\interceptor\HeaderInterceptor.java
|
||||
D:\master\yjs-cloud-2112\cloud-common-security\src\main\java\com\muyu\common\security\annotation\Logical.java
|
||||
D:\master\yjs-cloud-2112\cloud-common-security\src\main\java\com\muyu\common\security\handler\GlobalExceptionHandler.java
|
||||
D:\master\yjs-cloud-2112\cloud-common-security\src\main\java\com\muyu\common\security\utils\DictUtils.java
|
||||
D:\master\yjs-cloud-2112\cloud-common-security\src\main\java\com\muyu\common\security\annotation\RequiresRoles.java
|
||||
D:\master\yjs-cloud-2112\cloud-common-security\src\main\java\com\muyu\common\security\annotation\EnableCustomConfig.java
|
||||
D:\master\yjs-cloud-2112\cloud-common-security\src\main\java\com\muyu\common\security\annotation\RequiresLogin.java
|
||||
D:\master\yjs-cloud-2112\cloud-common-security\src\main\java\com\muyu\common\security\annotation\RequiresPermissions.java
|
||||
D:\master\yjs-cloud-2112\cloud-common-security\src\main\java\com\muyu\common\security\config\ApplicationConfig.java
|
||||
D:\master\yjs-cloud-2112\cloud-common-security\src\main\java\com\muyu\common\security\aspect\PreAuthorizeAspect.java
|
||||
D:\master\yjs-cloud-2112\cloud-common-security\src\main\java\com\muyu\common\security\config\WebMvcConfig.java
|
||||
D:\master\yjs-cloud-2112\cloud-common-security\src\main\java\com\muyu\common\security\auth\AuthUtil.java
|
||||
D:\master\yjs-cloud-2112\cloud-common-security\src\main\java\com\muyu\common\security\service\TokenService.java
|
Loading…
Reference in New Issue