feat: 新增heritage-plugins安全插件(第2部分)
- 新增heritage-limit:接口限流插件,基于Redis实现 - 新增heritage-repect:防重复提交插件,防止表单重复提交 - 新增heritage-sa-token:Sa-Token认证插件,提供登录认证和权限验证
This commit is contained in:
parent
09457ecebd
commit
2c31bf6d53
32
heritage-plugins/heritage-limit/pom.xml
Normal file
32
heritage-plugins/heritage-limit/pom.xml
Normal file
@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.leocoder.heritage</groupId>
|
||||
<artifactId>heritage-plugins</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
|
||||
<name>heritage-limit</name>
|
||||
<artifactId>heritage-limit</artifactId>
|
||||
<description>限流插件</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- 全局公共模块 -->
|
||||
<dependency>
|
||||
<groupId>org.leocoder.heritage</groupId>
|
||||
<artifactId>heritage-common</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<!-- Aop依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
</project>
|
||||
@ -0,0 +1,15 @@
|
||||
package org.leocoder.heritage.limit.anno;
|
||||
|
||||
import org.leocoder.heritage.limit.config.CoderRedisLimitUtil;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
@Import({CoderRedisLimitUtil.class})
|
||||
public @interface EnableCoderLimit {
|
||||
|
||||
}
|
||||
@ -0,0 +1,83 @@
|
||||
package org.leocoder.heritage.limit.aspect;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.leocoder.heritage.common.anno.CoderLimit;
|
||||
import org.leocoder.heritage.common.enmus.limit.LimitType;
|
||||
import org.leocoder.heritage.common.exception.coder.ParamsException;
|
||||
import org.leocoder.heritage.common.utils.ip.IpUtil;
|
||||
import org.leocoder.heritage.limit.config.CoderRedisLimitUtil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [CoderLimitAspect限流处理]
|
||||
*/
|
||||
@Slf4j
|
||||
@Aspect
|
||||
@Order(2)
|
||||
@Component
|
||||
public class CoderLimitAspect {
|
||||
|
||||
@Autowired
|
||||
private CoderRedisLimitUtil CoderRedisLimitUtil;
|
||||
|
||||
/**
|
||||
* @description [前置通知,判断是否超出限流次数】
|
||||
*/
|
||||
@Before("@annotation(limit)")
|
||||
public void doBefore(JoinPoint point, CoderLimit limit) {
|
||||
try {
|
||||
// log.info("限流开始进入 =>");
|
||||
// 拼接key
|
||||
String key = getCombineKey(limit, point);
|
||||
// 判断是否超出限流次数
|
||||
if (!CoderRedisLimitUtil.limit(key, limit.count(), limit.time())) {
|
||||
throw new ParamsException(limit.message());
|
||||
}
|
||||
} catch (ParamsException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("接口限流异常,请稍候再试");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [根据限流类型拼接key】
|
||||
*/
|
||||
public String getCombineKey(CoderLimit limit, JoinPoint point) {
|
||||
// 获取服务请求的对象
|
||||
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
HttpServletRequest request = requestAttributes.getRequest();
|
||||
StringBuilder sb = new StringBuilder(limit.prefix());
|
||||
// 按照IP限流
|
||||
if (limit.type() == LimitType.IP) {
|
||||
String ipAddr = IpUtil.getIpAddr(request);
|
||||
// 检查字符串中是否包含逗号,这种情况设置waf会出现多个IP,第一个是真实IP,后面的都是阿里云IP
|
||||
int commaIndex = ipAddr.indexOf(',');
|
||||
if (commaIndex > -1) {
|
||||
// 如果有逗号,取逗号前的部分
|
||||
ipAddr = ipAddr.substring(0, commaIndex);
|
||||
}
|
||||
sb.append(ipAddr).append(":");
|
||||
// log.info("限流IP:{}", ipAddr);
|
||||
}
|
||||
// 拼接类名和方法名
|
||||
MethodSignature signature = (MethodSignature) point.getSignature();
|
||||
Method method = signature.getMethod();
|
||||
Class<?> targetClass = method.getDeclaringClass();
|
||||
sb.append(targetClass.getName()).append("-").append(method.getName());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,54 @@
|
||||
package org.leocoder.heritage.limit.config;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.data.redis.core.script.DefaultRedisScript;
|
||||
import org.springframework.data.redis.core.script.RedisScript;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [RedisLimitUtil接口限流]
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class CoderRedisLimitUtil {
|
||||
|
||||
@Autowired
|
||||
private StringRedisTemplate redisTemplate;
|
||||
|
||||
/**
|
||||
* @param key 键
|
||||
* @param count 限流次数
|
||||
* @param times 限流时间
|
||||
* @description [限流]
|
||||
* 通过 Lua 脚本,根据 Redis 中缓存的键值判断限流时间(也是 key 的过期时间)内,访问次数是否超出了限流次数,没超出则访问次数 +1,返回 true,超出了则返回 false。
|
||||
*/
|
||||
public boolean limit(String key, int count, int times) {
|
||||
try {
|
||||
String script = "local lockKey = KEYS[1]\n" +
|
||||
"local lockCount = KEYS[2]\n" +
|
||||
"local lockExpire = KEYS[3]\n" +
|
||||
"local currentCount = tonumber(redis.call('get', lockKey) or \"0\")\n" +
|
||||
"if currentCount < tonumber(lockCount)\n" +
|
||||
"then\n" +
|
||||
" redis.call(\"INCRBY\", lockKey, \"1\")\n" +
|
||||
" redis.call(\"expire\", lockKey, lockExpire)\n" +
|
||||
" return true\n" +
|
||||
"else\n" +
|
||||
" return false\n" +
|
||||
"end";
|
||||
RedisScript<Boolean> redisScript = new DefaultRedisScript<>(script, Boolean.class);
|
||||
List<String> keys = Arrays.asList(key, String.valueOf(count), String.valueOf(times));
|
||||
return Boolean.TRUE.equals(redisTemplate.execute(redisScript, keys));
|
||||
} catch (Exception e) {
|
||||
log.error("限流脚本执行失败:{}", e.getMessage());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
30
heritage-plugins/heritage-repect/pom.xml
Normal file
30
heritage-plugins/heritage-repect/pom.xml
Normal file
@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.leocoder.heritage</groupId>
|
||||
<artifactId>heritage-plugins</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
|
||||
<name>heritage-repect</name>
|
||||
<artifactId>heritage-repect</artifactId>
|
||||
<description>防重复提交插件</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- 通用公共模块 -->
|
||||
<dependency>
|
||||
<groupId>org.leocoder.heritage</groupId>
|
||||
<artifactId>heritage-common</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<!-- Aop依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@ -0,0 +1,16 @@
|
||||
package org.leocoder.heritage.repect.anno;
|
||||
|
||||
import org.leocoder.heritage.repect.aspect.RedisService;
|
||||
import org.leocoder.heritage.repect.aspect.RepeatSubmitAspect;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
@Import({RepeatSubmitAspect.class, RedisService.class})
|
||||
public @interface EnableCoderRepeatSubmit {
|
||||
|
||||
}
|
||||
@ -0,0 +1,206 @@
|
||||
package org.leocoder.heritage.repect.aspect;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.redis.core.*;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [RedisService]
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class RedisService {
|
||||
|
||||
public final RedisTemplate redisTemplate;
|
||||
|
||||
/**
|
||||
* @param key 缓存的键值
|
||||
* @param value 缓存的值
|
||||
* @return 缓存的对象
|
||||
* @description [缓存基本的对象,Integer、String、实体类等]
|
||||
*/
|
||||
public <T> ValueOperations<String, T> setCacheObject(String key, T value) {
|
||||
ValueOperations<String, T> operation = redisTemplate.opsForValue();
|
||||
operation.set(key, value);
|
||||
return operation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key 缓存的键值
|
||||
* @param value 缓存的值
|
||||
* @param timeout 时间
|
||||
* @param timeUnit 时间颗粒度
|
||||
* @return 缓存的对象
|
||||
* @description [缓存基本的对象,Integer、String、实体类等]
|
||||
*/
|
||||
public <T> ValueOperations<String, T> setCacheObject(String key, T value, Integer timeout, TimeUnit timeUnit) {
|
||||
ValueOperations<String, T> operation = redisTemplate.opsForValue();
|
||||
operation.set(key, value, timeout, timeUnit);
|
||||
return operation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key 缓存键值
|
||||
* @return 缓存键值对应的数据
|
||||
* @description [获得缓存的基本对象]
|
||||
*/
|
||||
public <T> T getCacheObject(String key) {
|
||||
ValueOperations<String, T> operation = redisTemplate.opsForValue();
|
||||
return operation.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [删除单个对象]
|
||||
*/
|
||||
public void deleteObject(String key) {
|
||||
redisTemplate.delete(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [删除集合对象]
|
||||
*/
|
||||
public void deleteObject(Collection collection) {
|
||||
redisTemplate.delete(collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key 缓存的键值
|
||||
* @param dataList 待缓存的List数据
|
||||
* @return 缓存的对象
|
||||
* @description [缓存List数据]
|
||||
*/
|
||||
public <T> ListOperations<String, T> setCacheList(String key, List<T> dataList) {
|
||||
ListOperations listOperation = redisTemplate.opsForList();
|
||||
if (null != dataList) {
|
||||
int size = dataList.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
listOperation.leftPush(key, dataList.get(i));
|
||||
}
|
||||
}
|
||||
return listOperation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key 缓存的键值
|
||||
* @return 缓存键值对应的数据
|
||||
* @description [获得缓存的list对象]
|
||||
*/
|
||||
public <T> List<T> getCacheList(String key) {
|
||||
List<T> dataList = new ArrayList<>();
|
||||
ListOperations<String, T> listOperation = redisTemplate.opsForList();
|
||||
Long size = listOperation.size(key);
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
dataList.add(listOperation.index(key, i));
|
||||
}
|
||||
return dataList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key 缓存键值
|
||||
* @param dataSet 缓存的数据
|
||||
* @return 缓存数据的对象
|
||||
* @description [缓存Set]
|
||||
*/
|
||||
public <T> BoundSetOperations<String, T> setCacheSet(String key, Set<T> dataSet) {
|
||||
BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
|
||||
Iterator<T> it = dataSet.iterator();
|
||||
while (it.hasNext()) {
|
||||
setOperation.add(it.next());
|
||||
}
|
||||
return setOperation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [获得缓存的set]
|
||||
*/
|
||||
public <T> Set<T> getCacheSet(String key) {
|
||||
Set<T> dataSet = new HashSet<>();
|
||||
BoundSetOperations<String, T> operation = redisTemplate.boundSetOps(key);
|
||||
dataSet = operation.members();
|
||||
return dataSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [缓存Map]
|
||||
*/
|
||||
public <T> HashOperations<String, String, T> setCacheMap(String key, Map<String, T> dataMap) {
|
||||
HashOperations hashOperations = redisTemplate.opsForHash();
|
||||
if (null != dataMap) {
|
||||
for (Map.Entry<String, T> entry : dataMap.entrySet()) {
|
||||
hashOperations.put(key, entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
return hashOperations;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [获得缓存的Map]
|
||||
*/
|
||||
public <T> Map<String, T> getCacheMap(String key) {
|
||||
Map<String, T> map = redisTemplate.opsForHash().entries(key);
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param pattern 字符串前缀
|
||||
* @return 对象列表
|
||||
* @description [获得缓存的基本对象列表]
|
||||
*/
|
||||
public Collection<String> keys(String pattern) {
|
||||
return redisTemplate.keys(pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [此key是否存在]
|
||||
*/
|
||||
public boolean haskey(String key) {
|
||||
return redisTemplate.hasKey(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [获取key的过期时间]
|
||||
*/
|
||||
public Long getExpire(String key) {
|
||||
return redisTemplate.getExpire(key);
|
||||
}
|
||||
|
||||
|
||||
public <T> ValueOperations<String, T> setBillObject(String key, List<Map<String, Object>> value) {
|
||||
ValueOperations<String, T> operation = redisTemplate.opsForValue();
|
||||
operation.set(key, (T) value);
|
||||
return operation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key 缓存的键值
|
||||
* @param value 缓存的值
|
||||
* @param timeout 时间
|
||||
* @param timeUnit 时间颗粒度
|
||||
* @return 缓存的对象
|
||||
* @description [缓存list<Map < String, Object>>]
|
||||
*/
|
||||
public <T> ValueOperations<String, T> setBillObject(String key, List<Map<String, Object>> value, Integer timeout, TimeUnit timeUnit) {
|
||||
ValueOperations<String, T> operation = redisTemplate.opsForValue();
|
||||
operation.set(key, (T) value, timeout, timeUnit);
|
||||
return operation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [缓存Map]
|
||||
*/
|
||||
public <T> HashOperations<String, String, T> setCKdBillMap(String key, Map<String, T> dataMap) {
|
||||
HashOperations hashOperations = redisTemplate.opsForHash();
|
||||
if (null != dataMap) {
|
||||
for (Map.Entry<String, T> entry : dataMap.entrySet()) {
|
||||
hashOperations.put(key, entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
return hashOperations;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,91 @@
|
||||
package org.leocoder.heritage.repect.aspect;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.leocoder.heritage.common.anno.CoderRepeatSubmit;
|
||||
import org.leocoder.heritage.common.constants.CoderCacheConstants;
|
||||
import org.leocoder.heritage.common.exception.RepeatSubmitException;
|
||||
import org.leocoder.heritage.common.utils.ip.IpUtil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [NoRepeatSubmitAop]
|
||||
*/
|
||||
@Aspect
|
||||
@Component
|
||||
@Order(1)
|
||||
@Slf4j
|
||||
public class RepeatSubmitAspect {
|
||||
|
||||
@Autowired
|
||||
private RedisService redisService;
|
||||
|
||||
@Value("${sa-token.token-name}")
|
||||
private String tokenName;
|
||||
|
||||
@Value("${sa-token.token-prefix}")
|
||||
private String tokenPrefix;
|
||||
|
||||
@Around(value = "@annotation(repeatSubmit)")
|
||||
public Object around(ProceedingJoinPoint joinPoint, CoderRepeatSubmit repeatSubmit) throws Throwable {
|
||||
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
assert attributes != null;
|
||||
// 请求地址
|
||||
HttpServletRequest request = attributes.getRequest();
|
||||
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
||||
// System.out.println("方法名称:"+ signature.getMethod().getName());
|
||||
// System.out.println("方法类型:"+ signature.getReturnType());
|
||||
// System.out.println("参数名称:"+ Arrays.toString(signature.getParameterNames()));
|
||||
// System.out.println("参数类型:"+ Arrays.toString(signature.getParameterTypes()));
|
||||
// 获得客户端的IP地址
|
||||
String userIp = IpUtil.getIpAddr(request);
|
||||
// 检查字符串中是否包含逗号,这种情况设置waf会出现多个IP,第一个是真实IP,后面的都是阿里云IP
|
||||
int commaIndex = userIp.indexOf(',');
|
||||
if (commaIndex > -1) {
|
||||
// 如果有逗号,取逗号前的部分
|
||||
userIp = userIp.substring(0, commaIndex);
|
||||
}
|
||||
// 针对某个人的话,就是用token,如果没有token就使用客户端IP进行辨别
|
||||
String authorization = request.getHeader(tokenName);
|
||||
String header = null;
|
||||
if (StringUtils.isNotBlank(authorization)) {
|
||||
header = authorization.replace(" ", "").replace(tokenPrefix, "");
|
||||
}
|
||||
// 定义redis的key
|
||||
String key = null;
|
||||
if (StringUtils.isNotBlank(header)) {
|
||||
// 这里是唯一标识,根据情况而定,里面添加用户ID或者IP地址最好,否则同一个接口一秒只能使用一次
|
||||
key = repeatSubmit.prefix() + userIp + ":[" + signature.getMethod().getName() + "-" + request.getServletPath() + "-" + header + "]";
|
||||
} else {
|
||||
// 这里是唯一标识,根据情况而定,里面添加用户ID或者IP地址最好,否则同一个接口一秒只能使用一次
|
||||
key = repeatSubmit.prefix() + userIp + ":[" + signature.getMethod().getName() + "-" + request.getServletPath() + "]";
|
||||
}
|
||||
// log.info("重复提交操作电脑IP:{}", userIp);
|
||||
// log.info("重复提交redis-key:{}", key);
|
||||
// 如果缓存中有这个url视为重复提交
|
||||
if (!redisService.haskey(key)) {
|
||||
// 通过,执行下一步
|
||||
Object o = joinPoint.proceed();
|
||||
// 然后存入redis并且设置1s倒计时
|
||||
redisService.setCacheObject(key, CoderCacheConstants.REPEAT_SUBMIT_KEY, repeatSubmit.value(), TimeUnit.MILLISECONDS);
|
||||
// 返回结果
|
||||
return o;
|
||||
} else {
|
||||
throw new RepeatSubmitException(500, repeatSubmit.message());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
58
heritage-plugins/heritage-sa-token/pom.xml
Normal file
58
heritage-plugins/heritage-sa-token/pom.xml
Normal file
@ -0,0 +1,58 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.leocoder.heritage</groupId>
|
||||
<artifactId>heritage-plugins</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
|
||||
<name>heritage-sa-token</name>
|
||||
<artifactId>heritage-sa-token</artifactId>
|
||||
<description>Sa-Token模块</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- 全局公共模块 -->
|
||||
<dependency>
|
||||
<groupId>org.leocoder.heritage</groupId>
|
||||
<artifactId>heritage-common</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<!-- MyBatisPlus模块 -->
|
||||
<dependency>
|
||||
<groupId>org.leocoder.heritage</groupId>
|
||||
<artifactId>heritage-mybatisplus</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<!-- Aop依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
<!-- Sa-Token 权限认证 start -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot3-starter</artifactId>
|
||||
</dependency>
|
||||
<!-- Sa-Token 整合 Redis[使用 jackson 序列化方式] -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-redis-jackson</artifactId>
|
||||
</dependency>
|
||||
<!-- 注意:无论使用哪种序列化方式,你都必须为项目提供一个 Redis 实例化方案 -->
|
||||
<!-- 提供Redis连接池[Sa-Token] -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-pool2</artifactId>
|
||||
</dependency>
|
||||
<!-- Sa-Token 整合 SpringAOP 实现注解鉴权 -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-aop</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@ -0,0 +1,16 @@
|
||||
package org.leocoder.heritage.satoken.anno;
|
||||
|
||||
import org.leocoder.heritage.satoken.config.CoderSaTokenFilter;
|
||||
import org.leocoder.heritage.satoken.config.CoderSaTokenInterceptor;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
@Import({ CoderSaTokenInterceptor.class, CoderSaTokenFilter.class })
|
||||
public @interface EnableCoderSaToken {
|
||||
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
package org.leocoder.heritage.satoken.config;
|
||||
|
||||
import cn.dev33.satoken.context.SaHolder;
|
||||
import cn.dev33.satoken.filter.SaServletFilter;
|
||||
import cn.dev33.satoken.router.SaHttpMethod;
|
||||
import cn.dev33.satoken.router.SaRouter;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [Sa-Token代码方式进行配置]
|
||||
*/
|
||||
@Configuration
|
||||
public class CoderSaTokenFilter {
|
||||
|
||||
/**
|
||||
* @description [全局过滤器-只用来设置跨域资源和开启浏览器默认XSS防护]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Bean
|
||||
public SaServletFilter getSaServletFilter() {
|
||||
return new SaServletFilter()
|
||||
// 前置函数:在每次认证函数之前执行
|
||||
.setBeforeAuth(r -> {
|
||||
// ---------- 设置一些安全响应头 ----------
|
||||
SaHolder.getResponse()
|
||||
// 允许指定域访问跨域资源
|
||||
.setHeader("Access-Control-Allow-Origin", "*")
|
||||
.setHeader("Access-Control-Allow-Methods", "*")
|
||||
.setHeader("Access-Control-Max-Age", "3600")
|
||||
.setHeader("Access-Control-Allow-Headers", "*")
|
||||
.setHeader("Content-Type", "application/json;charset=UTF-8")
|
||||
// 服务器名称
|
||||
.setServer("Coder-Admin")
|
||||
// 是否可以在iframe显示视图: DENY=不可以 | SAMEORIGIN=同域下可以 | ALLOW-FROM uri=指定域名下可以
|
||||
.setHeader("X-Frame-Options", "SAMEORIGIN")
|
||||
// 是否启用浏览器默认XSS防护: 0=禁用 | 1=启用 | 1; mode=block 启用, 并在检查到XSS攻击时,停止渲染页面
|
||||
.setHeader("X-XSS-Protection", "1; mode=block")
|
||||
// 禁用浏览器内容嗅探
|
||||
.setHeader("X-Content-Type-Options", "nosniff");
|
||||
// 如果是预检请求,则立即返回到前端
|
||||
SaRouter.match(SaHttpMethod.OPTIONS)
|
||||
.free(obj -> {
|
||||
})
|
||||
.back();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
package org.leocoder.heritage.satoken.config;
|
||||
|
||||
import cn.dev33.satoken.interceptor.SaInterceptor;
|
||||
import cn.dev33.satoken.router.SaRouter;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [Sa-Token代码方式进行配置]
|
||||
*/
|
||||
@Configuration
|
||||
public class CoderSaTokenInterceptor implements WebMvcConfigurer {
|
||||
|
||||
@Value("${coder.filePath}")
|
||||
private String baseFilePath;
|
||||
|
||||
/**
|
||||
* @description [注册拦截器]
|
||||
* 使用后必须携带 Authorization[此名称在yml中可自行配置]- Bearer token值[除非不被拦截,但是获取不到当前会话用户ID]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
|
||||
// 注册登录拦截器
|
||||
registry.addInterceptor(new SaInterceptor(handle -> {
|
||||
// 白名单
|
||||
List<String> ignoreUrls = new ArrayList<>();
|
||||
// favicon.ico浏览器标签Logo
|
||||
ignoreUrls.add("/favicon.ico");
|
||||
// 验证码
|
||||
ignoreUrls.add("/captcha/**");
|
||||
// 登录,退出登录
|
||||
ignoreUrls.add("/auth/**");
|
||||
// 测试接口
|
||||
// ignoreUrls.add("/coder/**");
|
||||
// 上传文件接口
|
||||
ignoreUrls.add("/CoderFile/**");
|
||||
// 后端项目详情
|
||||
ignoreUrls.add("/");
|
||||
// 静态资源
|
||||
// ignoreUrls.add("/*.html");
|
||||
// ignoreUrls.add("/**/*.html");
|
||||
// ignoreUrls.add("/**/*.css");
|
||||
// ignoreUrls.add("/**/*.js");
|
||||
// 上传路径
|
||||
ignoreUrls.add(baseFilePath + "/**");
|
||||
// Swagger API文档相关路径
|
||||
ignoreUrls.add("/swagger-ui/**");
|
||||
ignoreUrls.add("/v3/api-docs/**");
|
||||
ignoreUrls.add("/v3/api-docs");
|
||||
ignoreUrls.add("/swagger-ui.html");
|
||||
ignoreUrls.add("/webjars/**");
|
||||
|
||||
// 除白名单路径外均需要登录认证
|
||||
SaRouter.match("/**").notMatch(ignoreUrls).check(r -> StpUtil.checkLogin());
|
||||
})).addPathPatterns("/**");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,159 @@
|
||||
package org.leocoder.heritage.satoken.config;
|
||||
|
||||
import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface;
|
||||
import cn.dev33.satoken.config.SaTokenConfig;
|
||||
import cn.dev33.satoken.listener.SaTokenListener;
|
||||
import cn.dev33.satoken.stp.StpLogic;
|
||||
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.leocoder.heritage.common.constants.CoderConstants;
|
||||
import org.leocoder.heritage.mybatisplus.mapper.system.SysLoginUserMapper;
|
||||
import org.leocoder.heritage.satoken.service.loginlog.SaLoginLogService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [自定义侦听器的实现]
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
public class CoderSaTokenListener implements SaTokenListener {
|
||||
|
||||
private final SysLoginUserMapper sysLoginUserMapper;
|
||||
|
||||
private final SaLoginLogService saLoginLogService;
|
||||
|
||||
/**
|
||||
* @description [每次登录时触发]
|
||||
*/
|
||||
@Override
|
||||
public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginParameter loginParameter) {
|
||||
log.info("自定义侦听器实现-doLogin");
|
||||
log.info("登录类型:{}, 登录ID:{}, Token值:{}, 登录Model:{}", loginType, loginId, tokenValue, loginParameter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [每次注销时触发]
|
||||
*/
|
||||
@Override
|
||||
public void doLogout(String loginType, Object loginId, String tokenValue) {
|
||||
log.info("自定义侦听器实现 doLogout");
|
||||
// 保存注销日志
|
||||
saLoginLogService.addLoginLog(sysLoginUserMapper.selectById(Long.valueOf(String.valueOf(loginId))).getLoginName(), CoderConstants.ZERO_STRING, "退出登录");
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [每次被踢下线时触发]
|
||||
*/
|
||||
@Override
|
||||
public void doKickout(String loginType, Object loginId, String tokenValue) {
|
||||
log.info("自定义侦听器实现 doKickout");
|
||||
// 保存踢下线日志
|
||||
saLoginLogService.addLoginLog(sysLoginUserMapper.selectById(Long.valueOf(String.valueOf(loginId))).getLoginName(), CoderConstants.ZERO_STRING, "强退下线");
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [每次被顶下线时触发]
|
||||
*/
|
||||
@Override
|
||||
public void doReplaced(String loginType, Object loginId, String tokenValue) {
|
||||
log.info("自定义侦听器实现 doReplaced");
|
||||
// 保存被顶下线日志
|
||||
saLoginLogService.addLoginLog(sysLoginUserMapper.selectById(Long.valueOf(String.valueOf(loginId))).getLoginName(), CoderConstants.ZERO_STRING, "被顶下线");
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [每次被封禁时触发]
|
||||
*/
|
||||
@Override
|
||||
public void doDisable(String loginType, Object loginId, String service, int level, long disableTime) {
|
||||
log.info("自定义侦听器实现 doDisable");
|
||||
// 保存被封禁时日志
|
||||
saLoginLogService.addLoginLog(sysLoginUserMapper.selectById(Long.valueOf(String.valueOf(loginId))).getLoginName(), CoderConstants.ZERO_STRING, "账号被封禁");
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [每次被解封时触发]
|
||||
*/
|
||||
@Override
|
||||
public void doUntieDisable(String loginType, Object loginId, String service) {
|
||||
log.info("自定义侦听器实现 doUntieDisable");
|
||||
// 保存被解封时日志
|
||||
saLoginLogService.addLoginLog(sysLoginUserMapper.selectById(Long.valueOf(String.valueOf(loginId))).getLoginName(), "0", "账号被解封");
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [每次二级认证时触发]
|
||||
*/
|
||||
@Override
|
||||
public void doOpenSafe(String loginType, String tokenValue, String service, long safeTime) {
|
||||
log.info("自定义侦听器实现 doOpenSafe");
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [每次退出二级认证时触发]
|
||||
*/
|
||||
@Override
|
||||
public void doCloseSafe(String loginType, String tokenValue, String service) {
|
||||
log.info("自定义侦听器实现 doCloseSafe");
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [每次创建Session时触发]
|
||||
*/
|
||||
@Override
|
||||
public void doCreateSession(String id) {
|
||||
log.info("自定义侦听器实现 doCreateSession");
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [每次注销Session时触发]
|
||||
*/
|
||||
@Override
|
||||
public void doLogoutSession(String id) {
|
||||
log.info("自定义侦听器实现 doLogoutSession");
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [每次Token续期时触发]
|
||||
*/
|
||||
@Override
|
||||
public void doRenewTimeout(String loginType, Object loginId, String tokenValue, long timeout) {
|
||||
log.info("自定义侦听器实现 doRenewTimeout");
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [全局组件载入成功]
|
||||
*/
|
||||
@Override
|
||||
public void doRegisterComponent(String compName, Object compObj) {
|
||||
log.info("全局组件载入成功 doRegisterComponent");
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [注解扩展]
|
||||
*/
|
||||
@Override
|
||||
public void doRegisterAnnotationHandler(SaAnnotationHandlerInterface<?> handler) {
|
||||
log.info("注解扩展实现 doRegisterAnnotationHandler");
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [会话组件重置]
|
||||
*/
|
||||
@Override
|
||||
public void doSetStpLogic(StpLogic stpLogic) {
|
||||
log.info("会话组件重置成功 doSetStpLogic");
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [全局配置]
|
||||
*/
|
||||
@Override
|
||||
public void doSetConfig(SaTokenConfig config) {
|
||||
log.info("全局配置实现 doSetConfig");
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
package org.leocoder.heritage.satoken.config;
|
||||
|
||||
import cn.dev33.satoken.secure.SaSecureUtil;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [PasswordUtil-用户名+密码加密,然后跟数据库进行对比]
|
||||
*/
|
||||
public class CoderSaTokenPasswordUtil {
|
||||
|
||||
/**
|
||||
* MD5加密后迭代次数(默认2次)
|
||||
*/
|
||||
private static final int MD5ENCRYPTNUMBER = 2;
|
||||
|
||||
private CoderSaTokenPasswordUtil() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param salt 盐值随机数
|
||||
* @param password 密码
|
||||
* @description [字符串加密函数MD5实现]
|
||||
*/
|
||||
public static String getPassword(String password, String salt) {
|
||||
String initMd5Pwd = password + salt;
|
||||
for (int i = 0; i < MD5ENCRYPTNUMBER; i++) {
|
||||
initMd5Pwd = SaSecureUtil.md5(initMd5Pwd);
|
||||
}
|
||||
return initMd5Pwd;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("管理员密码:" + getPassword("123456", "20231123"));
|
||||
System.out.println("Coder密码:" + getPassword("123456", "666666"));
|
||||
System.out.println("YXT密码:" + getPassword("123456", "666666"));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,74 @@
|
||||
package org.leocoder.heritage.satoken.config;
|
||||
|
||||
import cn.dev33.satoken.stp.StpInterface;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.leocoder.heritage.common.exception.coder.YUtil;
|
||||
import org.leocoder.heritage.common.satoken.CoderLoginUtil;
|
||||
import org.leocoder.heritage.satoken.service.menu.SaMenuService;
|
||||
import org.leocoder.heritage.satoken.service.role.SaRoleService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [StpInterfaceImpl自定义权限验证接口扩展]
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
// 保证此类被SpringBoot扫描,完成Sa-Token的自定义权限验证扩展
|
||||
@Component
|
||||
public class CoderSaTokenStpInterfaceImpl implements StpInterface {
|
||||
|
||||
private final SaRoleService saRoleService;
|
||||
|
||||
private final SaMenuService saMenuService;
|
||||
|
||||
/**
|
||||
* @description [返回一个账号所拥有的角色标识集合 (权限与角色可分开校验)]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
public List<String> getRoleList(Object loginId, String loginType) {
|
||||
// 1、数据库查询用户拥有的角色码
|
||||
List<String> roleCodeList = saRoleService.listAuthRoleCode(Long.valueOf(loginId.toString()));
|
||||
// 2、返回角色码集合
|
||||
return roleCodeList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [返回一个账号所拥有的权限码集合]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
public List<String> getPermissionList(Object loginId, String loginType) {
|
||||
// 1、声明权限码集合
|
||||
Set<String> permissionSet = new HashSet<>();
|
||||
// 2、遍历角色列表,查询拥有的角色权限码
|
||||
List<String> roleKeyList = getRoleList(loginId, loginType);
|
||||
if (CollectionUtil.isEmpty(roleKeyList)) {
|
||||
return new ArrayList<>(permissionSet);
|
||||
}
|
||||
// 3、角色判断:超级管理员 roleKeyList.contains(CoderConstants.CODER_ADMIN)
|
||||
if (CoderLoginUtil.getIsCoderAdmin()) {
|
||||
permissionSet.add("*");
|
||||
return new ArrayList<>(permissionSet);
|
||||
}
|
||||
// 4、角色判断:其他角色
|
||||
List<String> menuAuthlist = null;
|
||||
for (String roleKey : roleKeyList) {
|
||||
// 5、根据角色码查询拥有的权限码
|
||||
menuAuthlist = saMenuService.listMenuAuth(roleKey);
|
||||
YUtil.isTrue(CollectionUtil.isEmpty(menuAuthlist), "该用户角色未分配菜单权限,禁止登录");
|
||||
permissionSet.addAll(menuAuthlist);
|
||||
}
|
||||
// 6、返回权限码集合
|
||||
return new ArrayList<>(permissionSet);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
package org.leocoder.heritage.satoken.config;
|
||||
|
||||
import cn.dev33.satoken.interceptor.SaInterceptor;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [SaTokenConfigure-Sa-Token 使用全局拦截器完成注解鉴权功能,为了不为项目带来不必要的性能负担,拦截器默认处于关闭状态
|
||||
* 因此,为了使用注解鉴权,你必须手动将 Sa-Token 的全局拦截器注册到你项目中]
|
||||
*/
|
||||
@Configuration
|
||||
public class SaTokenConfigure implements WebMvcConfigurer {
|
||||
|
||||
// 注册 Sa-Token 拦截器,打开注解式鉴权功能
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
// 注册 Sa-Token 拦截器,打开注解式鉴权功能
|
||||
registry.addInterceptor(new SaInterceptor())
|
||||
.addPathPatterns("/**")
|
||||
.excludePathPatterns(
|
||||
// 排除静态资源
|
||||
"/picture/**",
|
||||
// 排除其他静态资源
|
||||
"/favicon.ico",
|
||||
"/static/**",
|
||||
"/css/**",
|
||||
"/js/**",
|
||||
"/img/**",
|
||||
// 排除API文档
|
||||
"/swagger-ui/**",
|
||||
"/v3/api-docs/**",
|
||||
"/swagger-ui.html",
|
||||
"/webjars/**"
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
package org.leocoder.heritage.satoken.service.loginlog;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import org.leocoder.heritage.domain.pojo.system.SysLoginLog;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [SysLoginLogService]
|
||||
*/
|
||||
public interface SaLoginLogService extends IService<SysLoginLog> {
|
||||
|
||||
/**
|
||||
* @description [保存登录日志]
|
||||
* @author Leocoder
|
||||
*/
|
||||
void addLoginLog(String loginName, String loginStatus, String message);
|
||||
}
|
||||
@ -0,0 +1,75 @@
|
||||
package org.leocoder.heritage.satoken.service.loginlog;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import eu.bitwalker.useragentutils.UserAgent;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.leocoder.heritage.common.enmus.log.ClientType;
|
||||
import org.leocoder.heritage.common.utils.ip.IpAddressUtil;
|
||||
import org.leocoder.heritage.common.utils.ip.IpUtil;
|
||||
import org.leocoder.heritage.common.utils.ip.ServletUtil;
|
||||
import org.leocoder.heritage.domain.pojo.system.SysLoginLog;
|
||||
import org.leocoder.heritage.mybatisplus.mapper.system.SysLoginLogMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [SysLoginLogServiceImpl]
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class SaLoginLogServiceImpl extends ServiceImpl<SysLoginLogMapper, SysLoginLog> implements SaLoginLogService {
|
||||
|
||||
private final IpAddressUtil ipAddressUtil;
|
||||
|
||||
/**
|
||||
* @description [保存登录日志]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
public void addLoginLog(String loginName, String loginStatus, String message) {
|
||||
// 1、new一个登录日志对象,用来装载信息
|
||||
SysLoginLog sysLoginLog = new SysLoginLog();
|
||||
try {
|
||||
sysLoginLog.setLoginName(loginName);
|
||||
sysLoginLog.setLoginStatus(loginStatus);
|
||||
// LocalDateTime.now() 时间类型为 LocalDateTime类型
|
||||
sysLoginLog.setLoginTime(LocalDateTime.now());
|
||||
sysLoginLog.setMessage(message);
|
||||
// 2、登录IP地址
|
||||
sysLoginLog.setLoginIp(IpUtil.getIpAddr(ServletUtil.getRequest()));
|
||||
// 3、登录地理位置
|
||||
sysLoginLog.setLoginAddress(ipAddressUtil.getAddress(sysLoginLog.getLoginIp()));
|
||||
// 4、UserAgent信息
|
||||
UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtil.getRequest().getHeader("User-Agent"));
|
||||
// 5、登录浏览器
|
||||
sysLoginLog.setBrowser(userAgent.getBrowser().getName());
|
||||
// 6、登录操作系统
|
||||
sysLoginLog.setOs(userAgent.getOperatingSystem().getName());
|
||||
// 7、登录设备
|
||||
String deviceName = userAgent.getOperatingSystem().getDeviceType().getName();
|
||||
if (StringUtils.isBlank(deviceName)) {
|
||||
deviceName = "Default";
|
||||
} else if ("Computer".equals(deviceName)) {
|
||||
deviceName = ClientType.PC.name();
|
||||
} else if ("Mobile".equals(deviceName)) {
|
||||
deviceName = ClientType.MOBILE.name();
|
||||
}
|
||||
sysLoginLog.setDeviceName(deviceName);
|
||||
} catch (Exception e) {
|
||||
sysLoginLog.setMessage(e.getMessage());
|
||||
sysLoginLog.setLoginStatus("1");
|
||||
log.error("登录日志异常信息:{}", e.getMessage());
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
// 8、登录日志保存
|
||||
this.save(sysLoginLog);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
package org.leocoder.heritage.satoken.service.menu;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import org.leocoder.heritage.domain.pojo.system.SysMenu;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [SaMenuService]
|
||||
*/
|
||||
public interface SaMenuService extends IService<SysMenu> {
|
||||
|
||||
/**
|
||||
* @description [根据角色编码查询菜单权限-Sa-Token权限]
|
||||
* @author Leocoder
|
||||
*/
|
||||
List<String> listMenuAuth(String roleCode);
|
||||
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
package org.leocoder.heritage.satoken.service.menu;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.leocoder.heritage.domain.pojo.system.SysMenu;
|
||||
import org.leocoder.heritage.mybatisplus.mapper.system.SysMenuMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [SysMenuServiceImpl]
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class SaMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> implements SaMenuService {
|
||||
|
||||
private final SysMenuMapper menuMapper;
|
||||
|
||||
/**
|
||||
* @description [根据角色编码查询菜单权限-Sa-Token权限]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
public List<String> listMenuAuth(String roleCode) {
|
||||
return menuMapper.listMenuAuth(roleCode);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
package org.leocoder.heritage.satoken.service.role;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import org.leocoder.heritage.domain.pojo.system.SysRole;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [SaRoleService]
|
||||
*/
|
||||
public interface SaRoleService extends IService<SysRole> {
|
||||
|
||||
/**
|
||||
* @description [查询用户拥有正常角色-Sa-Token角色权限]
|
||||
* @author Leocoder
|
||||
*/
|
||||
List<String> listAuthRoleCode(Long userId);
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
package org.leocoder.heritage.satoken.service.role;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.leocoder.heritage.domain.pojo.system.SysRole;
|
||||
import org.leocoder.heritage.mybatisplus.mapper.system.SysRoleMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [SysRoleServiceImpl]
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class SaRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole> implements SaRoleService {
|
||||
|
||||
private final SysRoleMapper roleMapper;
|
||||
|
||||
/**
|
||||
* @description [查询用户拥有正常角色-Sa-Token角色权限]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
public List<String> listAuthRoleCode(Long userId) {
|
||||
return roleMapper.listAuthRoleCode(userId);
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user