m2pool项目提交

Signed-off-by: jxy_duyi <2826961034@qq.com>
This commit is contained in:
jxy_duyi
2025-04-10 18:44:21 +08:00
parent faaeb24007
commit c4f34073c4
508 changed files with 50991 additions and 0 deletions

View File

@@ -0,0 +1,27 @@
package com.m2pool.common.security.annotation;
import com.m2pool.common.security.config.ApplicationConfig;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.cloud.openfeign.FeignAutoConfiguration;
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.m2pool.**.com.m2pool.chat.mapper")
// 开启线程异步执行
@EnableAsync
// 自动加载类
@Import({ ApplicationConfig.class, FeignAutoConfiguration.class })
public @interface EnableCustomConfig
{
}

View File

@@ -0,0 +1,28 @@
package com.m2pool.common.security.annotation;
import org.springframework.cloud.openfeign.EnableFeignClients;
import java.lang.annotation.*;
/**
* @Description 自定义feign注解
* * 添加basePackages 主类包路径
* @Date 2024/6/13 11:12
* @Author dy
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EnableFeignClients
public @interface EnableM2PoolFeignClients {
String[] value() default {};
String[] basePackages() default { "com.m2pool" };
Class<?>[] basePackageClasses() default {};
Class<?>[] defaultConfiguration() default {};
Class<?>[] clients() default {};
}

View File

@@ -0,0 +1,20 @@
package com.m2pool.common.security.annotation;
import java.lang.annotation.*;
/**
* @Description 内部认证注解
* @Date 2024/6/13 11:16
* @Author dy
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface InnerAuth {
/**
* 是否校验用户信息
*/
boolean isUser() default false;
}

View File

@@ -0,0 +1,19 @@
package com.m2pool.common.security.annotation;
/**
* @Description 权限注解验证模式设定
* @Date 2024/6/14 9:33
* @Author dy
*/
public enum Logical
{
/**
* 必须具有所有的元素
*/
AND,
/**
* 只需具有其中一个元素
*/
OR
}

View File

@@ -0,0 +1,27 @@
package com.m2pool.common.security.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Description oepn api key权限认证必须拥有该方法所需权限标识才能进入方法
* @Date 2024/6/14 10:12
* @Author dy
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface OpenApiPermissions {
/**
* 需要校验的角色标识
*/
String[] value() default {};
/**
* 验证逻辑AND | OR默认AND
*/
Logical logical() default Logical.AND;
}

View File

@@ -0,0 +1,16 @@
package com.m2pool.common.security.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Description 登录认证:登录成功后才能进入该方法
* @Date 2024/6/14 10:11
* @Author dy
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresLogin {
}

View File

@@ -0,0 +1,27 @@
package com.m2pool.common.security.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Description 权限认证:必须拥有该方法所需权限标识才能进入方法
* @Date 2024/6/14 10:12
* @Author dy
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresPermissions {
/**
* 需要校验的角色标识
*/
String[] value() default {};
/**
* 验证逻辑AND | OR默认AND
*/
Logical logical() default Logical.AND;
}

View File

@@ -0,0 +1,27 @@
package com.m2pool.common.security.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Description 角色认证:必须拥有该方法所需角色标识才能进入方法
* @Date 2024/6/14 10:25
* @Author 杜懿
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresRoles {
/**
* 需要校验的角色标识
*/
String[] value() default {};
/**
* 验证逻辑AND | OR默认AND
*/
Logical logical() default Logical.AND;
}

View File

@@ -0,0 +1,50 @@
package com.m2pool.common.security.aspect;
import com.m2pool.common.core.constant.SecurityConstants;
import com.m2pool.common.core.exception.InnerAuthException;
import com.m2pool.common.core.utils.ServletUtils;
import com.m2pool.common.core.utils.StringUtils;
import com.m2pool.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;
/**
* @Description 内部服务调用验证处理
* @Date 2024/6/13 18:08
* @Author dy
*/
@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;
}
}

View File

@@ -0,0 +1,105 @@
package com.m2pool.common.security.aspect;
import com.m2pool.common.security.annotation.OpenApiPermissions;
import com.m2pool.common.security.annotation.RequiresLogin;
import com.m2pool.common.security.annotation.RequiresPermissions;
import com.m2pool.common.security.annotation.RequiresRoles;
import com.m2pool.common.security.auth.ApiUtil;
import com.m2pool.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;
/**
* @Description 基于 Spring Aop 的注解鉴权
* @Date 2024/6/13 18:47
* @Author dy
*/
@Aspect
@Component
public class PreAuthAspect {
/**
* 构建
*/
public PreAuthAspect(){
}
/**
* 定义AOP签名 (切入所有使用鉴权注解的方法)
*/
public static final String POINTCUT_SIGN = " @annotation(com.m2pool.common.security.annotation.RequiresLogin) || "
+ "@annotation(com.m2pool.common.security.annotation.RequiresPermissions) || "
+ "@annotation(com.m2pool.common.security.annotation.RequiresRoles) || "
+ "@annotation(com.m2pool.common.security.annotation.OpenApiPermissions)";
/**
* 声明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.checkPermission(requiresPermissions);
}
//校验 @OpenApiPermissions 注解
OpenApiPermissions openApiPermissions = method.getAnnotation(OpenApiPermissions.class);
if(openApiPermissions != null){
System.out.println("校验 @OpenApiPermissions 注解");
ApiUtil.checkPermission(openApiPermissions);
}
}
}

View File

@@ -0,0 +1,94 @@
package com.m2pool.common.security.aspect;
import com.m2pool.common.core.annotation.RateLimiter;
import com.m2pool.common.core.constant.HttpStatus;
import com.m2pool.common.core.enums.LimitType;
import com.m2pool.common.core.exception.ServiceException;
import com.m2pool.common.core.utils.ServletUtils;
import com.m2pool.common.core.utils.StringUtils;
import com.m2pool.common.core.utils.ip.IpUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
/**
* @Description 限流处理
* @Date 2024/6/13 18:38
* @Author dy
*/
@Aspect
@Component
public class RateLimiterAspect {
private static final Logger log = LoggerFactory.getLogger(RateLimiterAspect.class);
private RedisTemplate<Object, Object> redisTemplate;
private RedisScript<Long> limitScript;
@Autowired
public void setRedisTemplate1(RedisTemplate<Object, Object> redisTemplate)
{
this.redisTemplate = redisTemplate;
}
@Autowired
public void setLimitScript(RedisScript<Long> limitScript)
{
this.limitScript = limitScript;
}
@Before("@annotation(rateLimiter)")
public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable
{
String key = rateLimiter.key();
int time = rateLimiter.time();
int count = rateLimiter.count();
String combineKey = getCombineKey(rateLimiter, point);
List<Object> keys = Collections.singletonList(combineKey);
try
{
Long number = redisTemplate.execute(limitScript, keys, count, time);
if (StringUtils.isNull(number) || number.intValue() > count)
{
throw new ServiceException("访问过于频繁,请稍候再试", HttpStatus.TOO_MANY_REQUESTS);
}
log.info("限制请求'{}',当前请求'{}',缓存key'{}'", count, number.intValue(), combineKey);
}
catch (ServiceException e)
{
throw e;
}
catch (Exception e)
{
throw new RuntimeException("服务器限流异常,请稍候再试");
}
}
public String getCombineKey(RateLimiter rateLimiter, JoinPoint point)
{
StringBuffer stringBuffer = new StringBuffer(rateLimiter.key());
if (rateLimiter.limitType() == LimitType.IP)
{
stringBuffer.append(IpUtils.getIpAddr(ServletUtils.getRequest())).append("-");
}
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
Class<?> targetClass = method.getDeclaringClass();
stringBuffer.append(targetClass.getName()).append("-").append(method.getName());
return stringBuffer.toString();
}
}

View File

@@ -0,0 +1,106 @@
package com.m2pool.common.security.auth;
import com.m2pool.common.security.annotation.OpenApiPermissions;
import com.m2pool.common.security.annotation.RequiresPermissions;
import com.m2pool.system.api.model.OpenApiKeyInfo;
/**
* @Description api 验证工具类
* @Date 2024/6/13 19:17
* @Author dy
*/
public class ApiUtil {
/**
* 底层的oaReq对象
*/
public static OpenApiRequest oaReq = new OpenApiRequest();
/**
* 检查当前apikey是否有效
*/
public static void checkApiKey(){
oaReq.checkApiKey();
}
/**
* 获取当前api-key信息
* @param apiKey
* @return
*/
public static OpenApiKeyInfo getApiKeyInfo(String apiKey){return oaReq.getOpenApiKeyInfo(apiKey);}
/**
* 验证当前api-key有效期
* @param openApiKeyInfo
*/
public static void verifyApiKeyExpire(OpenApiKeyInfo openApiKeyInfo){
oaReq.verifyApiKeyExpire(openApiKeyInfo);
}
/**
* 判断当前账号是否含有指定权限
*
* @param permission 权限码
* @return 是否含有指定权限
*/
public static boolean hasPermission(String permission)
{
return oaReq.hasPermission(permission);
}
/**
* 当前账号是否含有指定权限, 如果验证未通过,则抛出异常: NotPermissionException
*
* @param permission 权限码
*/
public static void checkPermission(String permission)
{
oaReq.checkPermission(permission);
}
/**
* 根据注解传入参数鉴权, 如果验证未通过,则抛出异常: NotPermissionException
*
* @param openApiPermissions 权限注解
*/
public static void checkPermission(OpenApiPermissions openApiPermissions)
{
oaReq. checkPermission(openApiPermissions);
}
/**
* 当前账号是否含有指定权限 [指定多个,必须全部验证通过]
*
* @param permissions 权限码数组
*/
public static void checkPermissionAnd(String... permissions)
{
oaReq.checkPermissionAnd(permissions);
}
/**
* 当前账号是否含有指定权限 [指定多个,只要其一验证通过即可]
*
* @param permissions 权限码数组
*/
public static void checkPermissionOr(String... permissions)
{
oaReq.checkPermissionOr(permissions);
}
/**
* 会话注销
*/
public static void logout(){oaReq.logout();}
/**
* 根据token注销会话
* @param apiKey
*/
public static void logoutByApiKey(String apiKey){oaReq.logoutByApiKey(apiKey);}
}

View File

@@ -0,0 +1,373 @@
package com.m2pool.common.security.auth;
import com.m2pool.common.core.exception.auth.NotLoginException;
import com.m2pool.common.core.exception.auth.NotPermissionException;
import com.m2pool.common.core.exception.auth.NotRoleException;
import com.m2pool.common.core.utils.SpringUtils;
import com.m2pool.common.core.utils.StringUtils;
import com.m2pool.common.security.annotation.Logical;
import com.m2pool.common.security.annotation.RequiresLogin;
import com.m2pool.common.security.annotation.RequiresPermissions;
import com.m2pool.common.security.annotation.RequiresRoles;
import com.m2pool.common.security.service.TokenService;
import com.m2pool.common.security.utils.SecurityUtils;
import com.m2pool.system.api.model.LoginUser;
import org.springframework.util.PatternMatchUtils;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
/**
* @Description 权限验证的逻辑实现类
* @Date 2024/6/13 19:04
* @Author dy
*/
public class AuthLogic {
/** 所有权限标识 */
private static final String ALL_PERMISSION = "*:*:*";
/** 管理员角色权限标识 */
private static final String SUPER_ADMIN = "admin";
private 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);
}
/**
* 根据注解(@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.checkPermissionAnd(permissionArray);
}
else
{
this.checkPermissionOr(permissionArray);
}
}
/**
* 获取当前账号的角色列表
*
* @return 角色列表
*/
public Set<String> getRoleList()
{
try
{
LoginUser loginUser = getLoginUser();
return loginUser.getRoles();
}
catch (Exception e)
{
return new HashSet<>();
}
}
/**
* 获取当前账号的权限列表
*
* @return 权限列表
*/
public Set<String> getPermissionList()
{
try
{
LoginUser loginUser = getLoginUser();
return loginUser.getPermissions();
}
catch (Exception e)
{
return new HashSet<>();
}
}
/**
* 判断用户是否拥有某个角色
*
* @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);
}
}
/**
* 验证用户是否具备某权限
*
* @param permission 权限字符串
* @return 用户是否具备某权限
*/
public boolean hasPermission(String permission)
{
return hasPermission(getPermissionList(), permission);
}
/**
* 验证用户是否具备某权限, 如果验证未通过,则抛出异常: NotPermissionException
*
* @param permission 权限字符串
* @return 用户是否具备某权限
*/
public void checkPermission(String permission)
{
System.out.println("被调用的实际是我");
if (!hasPermission(getPermissionList(), permission))
{
throw new NotPermissionException(permission);
}
}
/**
* 根据注解(@RequiresPermissions)鉴权, 如果验证未通过,则抛出异常: NotPermissionException
*
* @param requiresPermissions 注解对象
*/
public void checkPermission(RequiresPermissions requiresPermissions)
{
if (requiresPermissions.logical() == Logical.AND)
{
checkPermissionAnd(requiresPermissions.value());
}
else
{
checkPermissionOr(requiresPermissions.value());
}
}
/**
* 验证用户是否含有指定权限,必须全部拥有
*
* @param permissions 权限列表
*/
public void checkPermissionAnd(String... permissions)
{
Set<String> permissionList = getPermissionList();
for (String permission : permissions)
{
if (!hasPermission(permissionList, permission))
{
throw new NotPermissionException(permission);
}
}
}
/**
* 验证用户是否含有指定权限,只需包含其中一个
*
* @param permissions 权限码数组
*/
public void checkPermissionOr(String... permissions)
{
Set<String> permissionList = getPermissionList();
for (String permission : permissions)
{
if (hasPermission(permissionList, permission))
{
return;
}
}
if (permissions.length > 0)
{
throw new NotPermissionException(permissions);
}
}
/**
* 判断是否包含权限
*
* @param authorities 权限列表
* @param permission 权限字符串
* @return 用户是否具备某权限
*/
public boolean hasPermission(Collection<String> 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<String> roles, String role)
{
return roles.stream().filter(StringUtils::hasText)
.anyMatch(x -> SUPER_ADMIN.contains(x) || PatternMatchUtils.simpleMatch(x, role));
}
}

View File

@@ -0,0 +1,149 @@
package com.m2pool.common.security.auth;
import com.m2pool.common.security.annotation.RequiresPermissions;
import com.m2pool.common.security.annotation.RequiresRoles;
import com.m2pool.system.api.model.LoginUser;
/**
* @Description 权限验证工具类
* @Date 2024/6/13 19:17
* @Author dy
*/
public class AuthUtil {
/**
* 底层的AuthLogic对象
*/
public static AuthLogic authLogic = new AuthLogic();
/**
* 检查当前用户是否登陆
*/
public static void checkLogin(){
authLogic.checkLogin();
}
/**
* 获取当前登陆用户信息
* @param token
* @return
*/
public static LoginUser getLoginUser(String token){return authLogic.getLoginUser(token);}
/**
* 验证当前用户有效期
* @param loginUser
*/
public static void verifyLoginUserExpire(LoginUser loginUser){
authLogic.verifyLoginUserExpire(loginUser);
}
/**
*判断当前用户是否有指定的角色标识
* @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);
}
/**
* 判断当前账号是否含有指定权限
*
* @param permission 权限码
* @return 是否含有指定权限
*/
public static boolean hasPermission(String permission)
{
return authLogic.hasPermission(permission);
}
/**
* 当前账号是否含有指定权限, 如果验证未通过,则抛出异常: NotPermissionException
*
* @param permission 权限码
*/
public static void checkPermission(String permission)
{
authLogic.checkPermission(permission);
}
/**
* 根据注解传入参数鉴权, 如果验证未通过,则抛出异常: NotPermissionException
*
* @param requiresPermissions 权限注解
*/
public static void checkPermission(RequiresPermissions requiresPermissions)
{
authLogic. checkPermission(requiresPermissions);
}
/**
* 当前账号是否含有指定权限 [指定多个,必须全部验证通过]
*
* @param permissions 权限码数组
*/
public static void checkPermissionAnd(String... permissions)
{
authLogic.checkPermissionAnd(permissions);
}
/**
* 当前账号是否含有指定权限 [指定多个,只要其一验证通过即可]
*
* @param permissions 权限码数组
*/
public static void checkPermissionOr(String... permissions)
{
authLogic.checkPermissionOr(permissions);
}
/**
* 会话注销
*/
public static void logout(){authLogic.logout();}
/**
* 根据token注销会话
* @param token
*/
public static void logoutByToken(String token){authLogic.logoutByToken(token);}
}

View File

@@ -0,0 +1,237 @@
package com.m2pool.common.security.auth;
import com.m2pool.common.core.exception.auth.NotLoginException;
import com.m2pool.common.core.exception.auth.NotPermissionException;
import com.m2pool.common.core.utils.SpringUtils;
import com.m2pool.common.core.utils.StringUtils;
import com.m2pool.common.security.annotation.Logical;
import com.m2pool.common.security.annotation.OpenApiPermissions;
import com.m2pool.common.security.annotation.RequiresPermissions;
import com.m2pool.common.security.service.OpenApiService;
import com.m2pool.common.security.utils.OpenApiUtils;
import com.m2pool.system.api.model.LoginUser;
import com.m2pool.system.api.model.OpenApiKeyInfo;
import org.springframework.util.PatternMatchUtils;
import javax.sound.midi.Soundbank;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
/**
* @Description 权限验证的逻辑实现类
* @Date 2024/6/13 19:04
* @Author dy
*/
public class OpenApiRequest {
/** 所有权限标识 */
private static final String ALL_PERMISSION = "*:*:*";
private OpenApiService openApiService = SpringUtils.getBean(OpenApiService.class);
/**
* 会话注销
*/
public void logout()
{
String apiKey = OpenApiUtils.getApiKey();
if (apiKey == null)
{
return;
}
logoutByApiKey(apiKey);
}
/**
* 会话注销根据指定Token
*/
public void logoutByApiKey(String apiKey)
{
openApiService.delOpenApiKey(apiKey);
}
/**
* 检验用户是否已经生成过apiKey如未生成则抛出异常
*/
public void checkApiKey(){getApiKey();}
/**
* 获取当前用户缓存信息, 如果未登录,则抛出异常
*
* @return 用户缓存信息
*/
public OpenApiKeyInfo getApiKey()
{
String apiKey = OpenApiUtils.getApiKey();
if (apiKey == null)
{
throw new NotLoginException("未提供API-KEY");
}
OpenApiKeyInfo apiKeyInfo = OpenApiUtils.getOpenApiKeyInfo();
if (apiKeyInfo == null)
{
throw new NotLoginException("无效的API-KEY");
}
return apiKeyInfo;
}
/**
* 获取当前用户缓存信息, 如果未登录,则抛出异常
*
* @param apiKey 前端传递的认证信息
* @return 用户缓存信息
*/
public OpenApiKeyInfo getOpenApiKeyInfo(String apiKey)
{
return openApiService.getOpenApiKeyInfo(apiKey);
}
/**
* 验证当前api-key有效期, 如果相差不足120分钟自动刷新缓存
*
* @param openApiKey 当前用户信息
*/
public void verifyApiKeyExpire(OpenApiKeyInfo openApiKey)
{
openApiService.verifyApiKey(openApiKey);
}
/**
* 根据注解(@RequiresPermissions)鉴权
*
* @param at 注解对象
*/
public void checkByAnnotation(RequiresPermissions at)
{
String[] permissionArray = at.value();
if (at.logical() == Logical.AND)
{
this.checkPermissionAnd(permissionArray);
}
else
{
this.checkPermissionOr(permissionArray);
}
}
/**
* 获取当前账号的权限列表
*
* @return 权限列表
*/
public Set<String> getPermissionList()
{
try
{
OpenApiKeyInfo apiKeyInfo = getApiKey();
return apiKeyInfo.getPermissions();
}
catch (Exception e)
{
return new HashSet<>();
}
}
/**
* 验证用户是否具备某权限
*
* @param permission 权限字符串
* @return 用户是否具备某权限
*/
public boolean hasPermission(String permission)
{
System.out.println("hasPermission");
return hasPermission(getPermissionList(), permission);
}
/**
* 验证用户是否具备某权限, 如果验证未通过,则抛出异常: NotPermissionException
*
* @param permission 权限字符串
* @return 用户是否具备某权限
*/
public void checkPermission(String permission)
{
System.out.println("OpenApiRequest.checkPermission");
if (!hasPermission(getPermissionList(), permission))
{
throw new NotPermissionException(permission);
}
}
/**
* 根据注解(@RequiresPermissions)鉴权, 如果验证未通过,则抛出异常: NotPermissionException
*
* @param openApiPermissions 注解对象
*/
public void checkPermission(OpenApiPermissions openApiPermissions)
{
if (openApiPermissions.logical() == Logical.AND)
{
checkPermissionAnd(openApiPermissions.value());
}
else
{
checkPermissionOr(openApiPermissions.value());
}
}
/**
* 验证用户是否含有指定权限,必须全部拥有
*
* @param permissions 权限列表
*/
public void checkPermissionAnd(String... permissions)
{
Set<String> permissionList = getPermissionList();
for (String permission : permissions)
{
if (!hasPermission(permissionList, permission))
{
throw new NotPermissionException(permission);
}
}
}
/**
* 验证用户是否含有指定权限,只需包含其中一个
*
* @param permissions 权限码数组
*/
public void checkPermissionOr(String... permissions)
{
System.out.println("OpenApiRequest.checkPermissionOr");
Set<String> permissionList = getPermissionList();
for (String permission : permissions)
{
if (hasPermission(permissionList, permission))
{
return;
}
}
if (permissions.length > 0)
{
throw new NotPermissionException(permissions);
}
}
/**
* 判断是否包含权限
*
* @param authorities 权限列表
* @param permission 权限字符串
* @return 用户是否具备某权限
*/
public boolean hasPermission(Collection<String> authorities, String permission)
{
System.out.println("权限列表:"+authorities);
System.out.println("所需权限:"+permission);
return authorities.stream().filter(StringUtils::hasText)
.anyMatch(x -> ALL_PERMISSION.contains(x) || PatternMatchUtils.simpleMatch(x, permission));
}
}

View File

@@ -0,0 +1,24 @@
package com.m2pool.common.security.config;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import java.util.TimeZone;
/**
* @Description 系统配置
* @Date 2024/6/13 10:59
* @Author dy
*/
public class ApplicationConfig {
/**
* 时区配置
*/
@Bean
public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization()
{
return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(TimeZone.getDefault());
}
}

View File

@@ -0,0 +1,48 @@
package com.m2pool.common.security.config;
import com.m2pool.common.security.interceptor.HeaderInterceptor;
import com.m2pool.common.security.interceptor.OpenApiHeaderInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @Description 拦截器配置
* @Date 2024/6/13 19:56
* @Author dy
*/
public class WebMvcConfig implements WebMvcConfigurer {
/** token 不需要拦截的地址 */
public static final String[] excludeUrls = {"/login","/logout","/refresh",
"/account/hashrate_real","/account/hashrate_history","/account/hashrate_last24h",
"/account/miners_list","/account/watch","/account/test1",
"/miner/hashrate_real","/miner/hashrate_history","/miner/hashrate_last24h",
"/pool/hashrate","/pool/hashrate_history","/pool/miners_list","/pool/watch","/oapi/**"};
public static final String[] matchUrls = {
"/account/hashrate_real","/account/hashrate_history","/account/hashrate_last24h",
"/account/miners_list","/account/watch","/account/test1",
"/miner/hashrate_real","/miner/hashrate_history","/miner/hashrate_last24h",
"/pool/hashrate","/pool/hashrate_history","/pool/miners_list","/pool/watch","/oapi/**"};
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(getHeaderInterceptor())
.addPathPatterns("/**")
.excludePathPatterns(excludeUrls)
.order(-10);
registry.addInterceptor(getOpenApiHeaderInterceptor())
.addPathPatterns(matchUrls).
excludePathPatterns("/auth/**,/system/**,/pool/**")
.order(-15);
}
/**
* 自定义请求头拦截器
*/
public HeaderInterceptor getHeaderInterceptor(){return new HeaderInterceptor();}
public OpenApiHeaderInterceptor getOpenApiHeaderInterceptor(){return new OpenApiHeaderInterceptor();}
}

View File

@@ -0,0 +1,18 @@
package com.m2pool.common.security.feign;
import feign.RequestInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Description fegin 配置注册
* @Date 2024/6/13 20:33
* @Author dy
*/
@Configuration
public class FeignAutoConfig {
@Bean
public RequestInterceptor requestInterceptor(){return new FeignRequestInterceptor();}
}

View File

@@ -0,0 +1,51 @@
package com.m2pool.common.security.feign;
import com.m2pool.common.core.constant.SecurityConstants;
import com.m2pool.common.core.utils.ServletUtils;
import com.m2pool.common.core.utils.StringUtils;
import com.m2pool.common.core.utils.ip.IpUtils;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
/**
* @Description fegin 请求拦截器
* @Date 2024/6/13 20:12
* @Author dy
*/
@Component
public class FeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
System.out.println("feign拦截器开始执行");
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))
{
template.header(SecurityConstants.DETAILS_USER_ID, userId);
}
String userName = headers.get(SecurityConstants.DETAILS_USERNAME);
if (StringUtils.isNotEmpty(userName))
{
template.header(SecurityConstants.DETAILS_USERNAME, userName);
}
String authentication = headers.get(SecurityConstants.AUTHORIZATION_HEADER);
if (StringUtils.isNotEmpty(authentication))
{
template.header(SecurityConstants.AUTHORIZATION_HEADER, authentication);
}
System.out.println("userId:"+userId);
System.out.println("userName:"+userName);
System.out.println("ip:"+IpUtils.getIpAddr(ServletUtils.getRequest()));
// 配置客户端IP
template.header("X-Forwarded-For", IpUtils.getIpAddr(ServletUtils.getRequest()));
}
}
}

View File

@@ -0,0 +1,181 @@
package com.m2pool.common.security.handler;
import com.m2pool.common.core.constant.HttpStatus;
import com.m2pool.common.core.exception.InnerAuthException;
import com.m2pool.common.core.exception.ServiceException;
import com.m2pool.common.core.exception.auth.NotPermissionException;
import com.m2pool.common.core.exception.auth.NotRoleException;
import com.m2pool.common.core.exception.file.FileNameLengthLimitExceededException;
import com.m2pool.common.core.utils.StringUtils;
import com.m2pool.common.core.web.Result.AjaxResult;
import com.m2pool.common.security.utils.SecurityUtils;
import com.m2pool.system.api.model.LoginUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.validation.BindException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MultipartException;
import javax.servlet.http.HttpServletRequest;
import java.sql.SQLException;
/**
* @Description 全局异常处理器
* @Date 2024/6/13 20:36
* @Author dy
*/
@Order(Ordered.HIGHEST_PRECEDENCE)
@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();
LoginUser loginUser = SecurityUtils.getLoginUser();
System.out.println(loginUser.getRoles());
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(RuntimeException.class)
public AjaxResult handleRuntimeException(RuntimeException e, HttpServletRequest request)
{
String requestURI = request.getRequestURI();
log.error("请求地址'{}',发生未知异常.", requestURI, e);
return AjaxResult.error(e.getMessage());
}
/**
* 拦截文件异常异常
*/
@ExceptionHandler(MultipartException.class)
public AjaxResult handleMultException(MultipartException e, HttpServletRequest request)
{
String requestURI = request.getRequestURI();
log.error("请求地址'{}',发生文件异常.", requestURI, e);
return AjaxResult.error("文件过大单个文件最大2M");
}
/**
* 拦截文件名超长异常
*/
@ExceptionHandler(FileNameLengthLimitExceededException.class)
public AjaxResult handleFileNameLengthException(FileNameLengthLimitExceededException 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());
}
/**
* sql查询验证异常
*/
@ExceptionHandler(SQLException.class)
public AjaxResult handleSQLException(SQLException e, HttpServletRequest request)
{
String requestURI = request.getRequestURI();
log.error("请求地址'{}',发生sql执行异常.", requestURI, e);
return AjaxResult.error("请求地址'{}',发生sql执行异常.");
}
@ExceptionHandler(BadSqlGrammarException.class)
public AjaxResult handleBadSqlGrammarException(BadSqlGrammarException e, HttpServletRequest request)
{
String requestURI = request.getRequestURI();
log.error("请求地址'{}',发生SQL异常.", requestURI, e);
return AjaxResult.error(HttpStatus.ERROR, "数据库异常!");
}
//todo 根据业务需求增加异常
}

View File

@@ -0,0 +1,61 @@
package com.m2pool.common.security.interceptor;
import com.m2pool.common.core.constant.SecurityConstants;
import com.m2pool.common.core.context.SecurityContextHolder;
import com.m2pool.common.core.utils.ServletUtils;
import com.m2pool.common.core.utils.StringUtils;
import com.m2pool.common.core.utils.ip.IpUtils;
import com.m2pool.common.security.auth.AuthUtil;
import com.m2pool.common.security.utils.SecurityUtils;
import com.m2pool.system.api.model.LoginUser;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.AsyncHandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @Description 自定义请求头拦截器,将请求头数据封装到线程变量中方便后续获取使用
* 同时获取当前用户有效时间并刷新有效期
* @Date 2024/6/13 20:29
* @Author dy
*/
public class HeaderInterceptor implements AsyncHandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("调用默认拦截器,路径:"+request.getRequestURI());
System.out.println("调用默认拦截器,路径:"+ IpUtils.getIpAddr(request));
System.out.println(handler);
if(!(handler instanceof HandlerMethod)){
System.out.println("默认拦截器放行");
return true;
}
System.out.println("默认拦截器处理");
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();
}
}

View File

@@ -0,0 +1,68 @@
package com.m2pool.common.security.interceptor;
import com.m2pool.common.core.constant.OpenApiKeyConstants;
import com.m2pool.common.core.context.OpenApiContextHolder;
import com.m2pool.common.core.utils.ServletUtils;
import com.m2pool.common.core.utils.StringUtils;
import com.m2pool.common.core.utils.ip.IpUtils;
import com.m2pool.common.security.auth.ApiUtil;
import com.m2pool.common.security.utils.OpenApiUtils;
import com.m2pool.system.api.model.OpenApiKeyInfo;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.AsyncHandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @Description 自定义请求头拦截器,将请求头数据封装到线程变量中方便后续获取使用
* 同时获取当前用户有效时间并刷新有效期
* @Date 2024/6/13 20:29
* @Author dy
*/
public class OpenApiHeaderInterceptor implements AsyncHandlerInterceptor {
private static final Map<String, AtomicInteger> limitCounter = new ConcurrentHashMap<>();
private static final int MAX_REQUESTS_SECOND = 5;
private static final int MAX_REQUESTS_DAY = 5000;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("调用Oapi拦截器,路径:"+request.getRequestURI());
System.out.println(IpUtils.getIpAddr(request));
if(!(handler instanceof HandlerMethod)){
return true;
}
//System.out.println("OpenApiHeaderInterceptor.preHandle 开始赋值");
OpenApiContextHolder.setApiUser(ServletUtils.getHeader(request, OpenApiKeyConstants.API_USER));
OpenApiContextHolder.setApiIp(ServletUtils.getHeader(request,OpenApiKeyConstants.API_IP));
OpenApiContextHolder.setApiKey(ServletUtils.getHeader(request,OpenApiKeyConstants.API_KEY));
String apiKey = OpenApiUtils.getApiKey();
if(StringUtils.isNotEmpty(apiKey)){
OpenApiKeyInfo apiKeyInfo = ApiUtil.getApiKeyInfo(apiKey);
if(StringUtils.isNotNull(apiKeyInfo)){
//验证当前用户有效时间
ApiUtil.verifyApiKeyExpire(apiKeyInfo);
OpenApiContextHolder.set(OpenApiKeyConstants.API_KEY_INFO,apiKeyInfo);
}
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
OpenApiContextHolder.remove();
}
}

View File

@@ -0,0 +1,186 @@
package com.m2pool.common.security.service;
import com.m2pool.common.core.constant.CacheConstants;
import com.m2pool.common.core.constant.OpenApiKeyConstants;
import com.m2pool.common.core.utils.OpenApiJwtUtils;
import com.m2pool.common.core.utils.ServletUtils;
import com.m2pool.common.core.utils.StringUtils;
import com.m2pool.common.core.utils.ip.IpUtils;
import com.m2pool.common.redis.service.RedisService;
import com.m2pool.common.security.utils.OpenApiUtils;
import com.m2pool.system.api.model.OpenApiKeyInfo;
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;
/**
* @Description open api key 验证处理
* @Date 2024/6/13 21:06
* @Author dy
*/
@Component
public class OpenApiService {
@Autowired
private RedisService redisService;
/** 秒 */
protected static final long MILLIS_SECOND = 1000;
/** 分 */
protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;
/** 过期时间/缓存有效期 设置为1年*/
private final static long expireTime = CacheConstants.EXPIRATION * 2;
/** 权限缓存前缀 */
private final static String ACCESS_API_KEY = CacheConstants.OPEN_API_KEY;
/** 缓存刷新时间 */
private final static Long MILLIS_MINUTE_TEN = CacheConstants.REFRESH_TIME * MILLIS_MINUTE;
/**
* 创建令牌
*/
public Map<String, Object> createApiKey(OpenApiKeyInfo openApiKey)
{
String apiKey = openApiKey.getKey();
System.out.println("生成的apiKey:"+apiKey);
String user = openApiKey.getUser();
String apiIp = openApiKey.getApiIp();
openApiKey.setKey(apiKey);
openApiKey.setUser(user);
if (StringUtils.isNotBlank(apiIp)){
openApiKey.setApiIp(apiIp);
}else {
openApiKey.setApiIp(IpUtils.getIpAddr(ServletUtils.getRequest()));
}
refreshApiKey(openApiKey);
// Jwt存储信息
Map<String, Object> claimsMap = new HashMap<String, Object>();
claimsMap.put(OpenApiKeyConstants.API_KEY, apiKey);
claimsMap.put(OpenApiKeyConstants.API_USER, user);
claimsMap.put(OpenApiKeyConstants.API_IP, openApiKey.getApiIp());
// 接口返回信息
Map<String, Object> rspMap = new HashMap<String, Object>();
rspMap.put("api_key", OpenApiJwtUtils.createApiKey(claimsMap));
rspMap.put("uuid生成的api_key", apiKey);
rspMap.put("expires_in", expireTime);
rspMap.put("user", user);
return rspMap;
}
/**
* 获取apiKey信息
*
* @return apiKey信息
*/
public OpenApiKeyInfo getOpenApiKeyInfo()
{
return getOpenApiKeyInfo(ServletUtils.getRequest());
}
/**
* 获取apiKey信息
*
* @return apiKey信息
*/
public OpenApiKeyInfo getOpenApiKeyInfo(HttpServletRequest request)
{
// 获取请求携带的令牌
String apiKey = OpenApiUtils.getApiKey(request);
return getOpenApiKeyInfo(apiKey);
}
/**
* 获取用户身份信息
*
* @return 用户信息
*/
public OpenApiKeyInfo getOpenApiKeyInfo(String apiKey)
{
OpenApiKeyInfo openApiKey = null;
try
{
if (StringUtils.isNotEmpty(apiKey))
{
String key = OpenApiJwtUtils.getApiKey(apiKey);
openApiKey = redisService.getCacheObject(getAPIRedisKey(key));
return openApiKey;
}
}
catch (Exception e)
{
System.out.println("oapiService.getOpenApiKeyInfo 异常");
e.printStackTrace();
}
return openApiKey;
}
/**
* 设置用户api_key信息
*/
public void setOpenApiKeyInfo(OpenApiKeyInfo openApiKey)
{
if (StringUtils.isNotNull(openApiKey) && StringUtils.isNotEmpty(openApiKey.getKey()))
{
refreshApiKey(openApiKey);
}
}
/**
* 删除api_key缓存信息
*/
public void delOpenApiKey(String apiKey)
{
if (StringUtils.isNotEmpty(apiKey))
{
String key = OpenApiJwtUtils.getApiKey(apiKey);
//System.out.println("用户登出从redis删除key"+api_key:...);
redisService.deleteObject(getAPIRedisKey(key));
}
}
/**
* 验证令牌有效期相差不足120分钟自动刷新缓存
*
* @param openApiKey
*/
public void verifyApiKey(OpenApiKeyInfo openApiKey)
{
long expireTime = openApiKey.getExpireTime();
long currentTime = System.currentTimeMillis();
//可以考虑直接刷新缓存 而不是小于才刷新缓存
if (expireTime - currentTime <= MILLIS_MINUTE_TEN)
{
refreshApiKey(openApiKey);
}
}
/**
* 刷新api-key有效期
*
* @param openApiKey api-key请求信息
*/
public void refreshApiKey(OpenApiKeyInfo openApiKey)
{
openApiKey.setLoginTime(System.currentTimeMillis());
openApiKey.setExpireTime(openApiKey.getLoginTime() + expireTime * MILLIS_MINUTE);
// 根据uuid将api-key缓存
String apiKey = getAPIRedisKey(openApiKey.getKey());
redisService.setCacheObject(apiKey, openApiKey, expireTime, TimeUnit.MINUTES);
}
private String getAPIRedisKey(String token)
{
return ACCESS_API_KEY + token;
}
}

View File

@@ -0,0 +1,179 @@
package com.m2pool.common.security.service;
import com.m2pool.common.core.constant.CacheConstants;
import com.m2pool.common.core.constant.SecurityConstants;
import com.m2pool.common.core.utils.JwtUtils;
import com.m2pool.common.core.utils.ServletUtils;
import com.m2pool.common.core.utils.StringUtils;
import com.m2pool.common.core.utils.ip.IpUtils;
import com.m2pool.common.core.utils.uuid.IdUtils;
import com.m2pool.common.redis.service.RedisService;
import com.m2pool.common.security.utils.SecurityUtils;
import com.m2pool.system.api.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;
/**
* @Description token验证处理
* @Date 2024/6/13 21:06
* @Author dy
*/
@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<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(ServletUtils.getRequest()));
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);
rspMap.put("userName", loginUser.getUsername());
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);
//System.out.println("用户登出从redis删除key"+userkey);
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;
}
}

View File

@@ -0,0 +1,86 @@
package com.m2pool.common.security.utils;
import com.m2pool.common.core.constant.OpenApiKeyConstants;
import com.m2pool.common.core.constant.TokenConstants;
import com.m2pool.common.core.context.OpenApiContextHolder;
import com.m2pool.common.core.utils.ServletUtils;
import com.m2pool.common.core.utils.StringUtils;
import com.m2pool.system.api.model.OpenApiKeyInfo;
import javax.servlet.http.HttpServletRequest;
/**
* @Description 权限获取工具类
* @Date 2024/6/11 16:58
* @Author dy
*/
public class OpenApiUtils {
/**
* 获取api用户
*/
public static String getApiUser()
{
return OpenApiContextHolder.getApiUser();
}
/**
* 获取api绑定的ip
*/
public static String getApiIp()
{
return OpenApiContextHolder.getApiIp();
}
/**
* 获取api key
*/
public static String getKey()
{
return OpenApiContextHolder.getApiKey();
}
/**
* 获取apiKey对应的apiKey详细信息
*/
public static OpenApiKeyInfo getOpenApiKeyInfo()
{
OpenApiKeyInfo info = OpenApiContextHolder.get(OpenApiKeyConstants.API_KEY_INFO, OpenApiKeyInfo.class);
return info;
}
/**
* 获取请求token
*/
public static String getApiKey()
{
return getApiKey(ServletUtils.getRequest());
}
/**
* 根据request获取请求api-key
*/
public static String getApiKey(HttpServletRequest request)
{
// 从header获取token标识
String token = request.getHeader(TokenConstants.API_KEY);
return token;
}
/**
* 是否为管理员
*
* @param user 用户邮箱
* @return 结果
*/
public static boolean isAdmin(String user)
{
return StringUtils.isNotBlank(user) && "".equals(user);
}
}

View File

@@ -0,0 +1,120 @@
package com.m2pool.common.security.utils;
import com.m2pool.common.core.constant.SecurityConstants;
import com.m2pool.common.core.constant.TokenConstants;
import com.m2pool.common.core.context.SecurityContextHolder;
import com.m2pool.common.core.utils.ServletUtils;
import com.m2pool.common.core.utils.StringUtils;
import com.m2pool.system.api.model.LoginUser;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import javax.servlet.http.HttpServletRequest;
/**
* @Description 权限获取工具类
* @Date 2024/6/11 16:58
* @Author dy
*/
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);
}
}

View File

@@ -0,0 +1,7 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.m2pool.common.security.config.WebMvcConfig,\
com.m2pool.common.security.service.TokenService,\
com.m2pool.common.security.service.OpenApiService,\
com.m2pool.common.security.aspect.PreAuthAspect,\
com.m2pool.common.security.aspect.InnerAuthAspect,\
com.m2pool.common.security.handler.GlobalExceptionHandler