first commit
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
package com.starry.admin.common.aspect;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import com.starry.admin.common.domain.LoginUser;
|
||||
import com.starry.admin.modules.system.entity.SysRoleEntity;
|
||||
import com.starry.admin.modules.system.entity.SysUserEntity;
|
||||
import com.starry.admin.utils.SecurityUtils;
|
||||
import com.starry.common.annotation.DataScope;
|
||||
import com.starry.common.context.SecurityContextHolder;
|
||||
import com.starry.common.domain.BaseEntity;
|
||||
import com.starry.common.utils.StringUtils;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 数据过滤处理
|
||||
*
|
||||
* @author vctgo
|
||||
*/
|
||||
@Aspect
|
||||
@Component
|
||||
public class DataScopeAspect {
|
||||
/**
|
||||
* 全部数据权限
|
||||
*/
|
||||
public static final String DATA_SCOPE_ALL = "1";
|
||||
|
||||
/**
|
||||
* 自定数据权限
|
||||
*/
|
||||
public static final String DATA_SCOPE_CUSTOM = "2";
|
||||
|
||||
/**
|
||||
* 部门数据权限
|
||||
*/
|
||||
public static final String DATA_SCOPE_DEPT = "3";
|
||||
|
||||
/**
|
||||
* 部门及以下数据权限
|
||||
*/
|
||||
public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";
|
||||
|
||||
/**
|
||||
* 仅本人数据权限
|
||||
*/
|
||||
public static final String DATA_SCOPE_SELF = "5";
|
||||
|
||||
/**
|
||||
* 数据权限过滤关键字
|
||||
*/
|
||||
public static final String DATA_SCOPE = "dataScope";
|
||||
|
||||
/**
|
||||
* 数据范围过滤
|
||||
*
|
||||
* @param joinPoint 切点
|
||||
* @param user 用户
|
||||
* @param deptAlias 部门别名
|
||||
* @param userAlias 用户别名
|
||||
* @param permission 权限字符
|
||||
*/
|
||||
public static void dataScopeFilter(JoinPoint joinPoint, SysUserEntity user, String deptAlias, String userAlias, String permission) {
|
||||
StringBuilder sqlString = new StringBuilder();
|
||||
List<String> conditions = new ArrayList<>();
|
||||
for (SysRoleEntity role : user.getRoles()) {
|
||||
String dataScope = role.getDataScope();
|
||||
if (!DATA_SCOPE_CUSTOM.equals(dataScope) && conditions.contains(dataScope)) {
|
||||
continue;
|
||||
}
|
||||
if (StringUtils.isNotEmpty(permission) && StringUtils.isNotEmpty(role.getPermissions())
|
||||
&& !StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission))) {
|
||||
continue;
|
||||
}
|
||||
if (DATA_SCOPE_ALL.equals(dataScope)) {
|
||||
sqlString = new StringBuilder();
|
||||
break;
|
||||
} else if (DATA_SCOPE_CUSTOM.equals(dataScope)) {
|
||||
sqlString.append(StringUtils.format(
|
||||
" OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias,
|
||||
role.getRoleId()));
|
||||
} else if (DATA_SCOPE_DEPT.equals(dataScope)) {
|
||||
sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));
|
||||
} else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)) {
|
||||
sqlString.append(StringUtils.format(
|
||||
" OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )",
|
||||
deptAlias, user.getDeptId(), user.getDeptId()));
|
||||
} else if (DATA_SCOPE_SELF.equals(dataScope)) {
|
||||
if (StringUtils.isNotBlank(userAlias)) {
|
||||
sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));
|
||||
} else {
|
||||
// 数据权限为仅本人且没有userAlias别名不查询任何数据
|
||||
sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias));
|
||||
}
|
||||
}
|
||||
conditions.add(dataScope);
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(sqlString.toString())) {
|
||||
Object params = joinPoint.getArgs()[0];
|
||||
if (StringUtils.isNotNull(params) && params instanceof BaseEntity) {
|
||||
BaseEntity baseEntity = (BaseEntity) params;
|
||||
baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Before("@annotation(controllerDataScope)")
|
||||
public void doBefore(JoinPoint point, DataScope controllerDataScope) {
|
||||
clearDataScope(point);
|
||||
handleDataScope(point, controllerDataScope);
|
||||
}
|
||||
|
||||
protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope) {
|
||||
if (controllerDataScope == null) {
|
||||
return;
|
||||
}
|
||||
// 获取当前的用户
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
if (StringUtils.isNotNull(loginUser)) {
|
||||
SysUserEntity currentUser = loginUser.getUser();
|
||||
// 如果是超级管理员,则不过滤数据
|
||||
if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin()) {
|
||||
String permission = StringUtils.defaultIfEmpty(controllerDataScope.permission(), SecurityContextHolder.getPermission());
|
||||
dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),
|
||||
controllerDataScope.userAlias(), permission);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼接权限sql前先清空params.dataScope参数防止注入
|
||||
*/
|
||||
private void clearDataScope(final JoinPoint joinPoint) {
|
||||
Object params = joinPoint.getArgs()[0];
|
||||
if (StringUtils.isNotNull(params) && params instanceof BaseEntity) {
|
||||
BaseEntity baseEntity = (BaseEntity) params;
|
||||
baseEntity.getParams().put(DATA_SCOPE, "");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
package com.starry.admin.common.aspect;
|
||||
|
||||
import cn.hutool.extra.servlet.ServletUtil;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.starry.admin.common.domain.LoginUser;
|
||||
import com.starry.admin.modules.system.entity.SysOperationLogEntity;
|
||||
import com.starry.admin.modules.system.service.ISysOperationLogService;
|
||||
import com.starry.admin.utils.SecurityUtils;
|
||||
import com.starry.common.annotation.Log;
|
||||
import com.starry.common.utils.ServletUtils;
|
||||
import com.starry.common.utils.StringUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.annotation.AfterReturning;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author admin
|
||||
*/
|
||||
@Aspect
|
||||
@Component
|
||||
@Slf4j
|
||||
public class LogAspect {
|
||||
|
||||
@Resource
|
||||
private ISysOperationLogService operLogService;
|
||||
|
||||
/**
|
||||
* 处理完请求后执行
|
||||
*
|
||||
* @param joinPoint 切点
|
||||
*/
|
||||
@AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
|
||||
public void doAfterReturn(JoinPoint joinPoint, Log controllerLog, Object jsonResult) {
|
||||
handleLog(joinPoint, controllerLog, jsonResult);
|
||||
}
|
||||
|
||||
protected void handleLog(final JoinPoint joinPoint, Log controllerLog, Object jsonResult) {
|
||||
try {
|
||||
// 获取当前的用户
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
|
||||
// 日志记录
|
||||
SysOperationLogEntity operLog = new SysOperationLogEntity();
|
||||
operLog.setStatus(0);
|
||||
// 请求的IP地址
|
||||
String iP = ServletUtil.getClientIP(ServletUtils.getRequest());
|
||||
if ("0:0:0:0:0:0:0:1".equals(iP)) {
|
||||
iP = "127.0.0.1";
|
||||
}
|
||||
operLog.setOperIp(iP);
|
||||
operLog.setOperUrl(ServletUtils.getRequest().getRequestURI());
|
||||
if (loginUser != null) {
|
||||
operLog.setOperName(loginUser.getUsername());
|
||||
}
|
||||
if (null != null) {
|
||||
operLog.setStatus(1);
|
||||
operLog.setErrorMsg(StringUtils.substring(((Exception) null).getMessage(), 0, 2000));
|
||||
}
|
||||
// 设置方法名称
|
||||
String className = joinPoint.getTarget().getClass().getName();
|
||||
String methodName = joinPoint.getSignature().getName();
|
||||
operLog.setMethod(className + "." + methodName + "()");
|
||||
operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
|
||||
operLog.setOperTime(new Date());
|
||||
// 处理设置注解上的参数
|
||||
getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
|
||||
// 保存数据库
|
||||
operLogService.save(operLog);
|
||||
|
||||
} catch (Exception exp) {
|
||||
log.error("异常信息:{}", exp.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取注解中对方法的描述信息 用于Controller层注解
|
||||
*
|
||||
* @param log 日志
|
||||
* @param operLog 操作日志
|
||||
*/
|
||||
public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperationLogEntity operLog, Object jsonResult) {
|
||||
// 设置操作业务类型
|
||||
operLog.setBusinessType(log.businessType().ordinal());
|
||||
// 设置标题
|
||||
operLog.setTitle(log.title());
|
||||
// 是否需要保存request,参数和值
|
||||
if (log.isSaveRequestData()) {
|
||||
// 设置参数的信息
|
||||
setRequestValue(joinPoint, operLog);
|
||||
}
|
||||
// 是否需要保存response,参数和值
|
||||
if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult)) {
|
||||
operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求的参数,放到log中
|
||||
*
|
||||
* @param operLog 操作日志
|
||||
*/
|
||||
private void setRequestValue(JoinPoint joinPoint, SysOperationLogEntity operLog) {
|
||||
String requsetMethod = operLog.getRequestMethod();
|
||||
if (HttpMethod.PUT.name().equals(requsetMethod) || HttpMethod.POST.name().equals(requsetMethod)) {
|
||||
String params = argsArrayToString(joinPoint.getArgs());
|
||||
operLog.setOperParam(StringUtils.substring(params, 0, 2000));
|
||||
} else {
|
||||
Map<?, ?> paramsMap = (Map<?, ?>) ServletUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
|
||||
operLog.setOperParam(StringUtils.substring(paramsMap.toString(), 0, 2000));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数拼装
|
||||
*/
|
||||
private String argsArrayToString(Object[] paramsArray) {
|
||||
StringBuilder params = new StringBuilder();
|
||||
if (paramsArray != null) {
|
||||
for (Object object : paramsArray) {
|
||||
// 不为空 并且是不需要过滤的 对象
|
||||
if (StringUtils.isNotNull(object) && !isFilterObject(object)) {
|
||||
Object jsonObj = JSON.toJSON(object);
|
||||
params.append(jsonObj.toString()).append(" ");
|
||||
}
|
||||
}
|
||||
}
|
||||
return params.toString().trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否需要过滤的对象。
|
||||
*
|
||||
* @param object 对象信息。
|
||||
* @return 如果是需要过滤的对象,则返回true;否则返回false。
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public boolean isFilterObject(final Object object) {
|
||||
Class<?> clazz = object.getClass();
|
||||
if (clazz.isArray()) {
|
||||
return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
|
||||
} else if (Collection.class.isAssignableFrom(clazz)) {
|
||||
Collection collection = (Collection) object;
|
||||
for (Object value : collection) {
|
||||
return value instanceof MultipartFile;
|
||||
}
|
||||
} else if (Map.class.isAssignableFrom(clazz)) {
|
||||
Map map = (Map) object;
|
||||
for (Object value : map.entrySet()) {
|
||||
Map.Entry entry = (Map.Entry) value;
|
||||
return entry.getValue() instanceof MultipartFile;
|
||||
}
|
||||
}
|
||||
return object instanceof MultipartFile || object instanceof HttpServletRequest || object instanceof HttpServletResponse || object instanceof BindingResult;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
package com.starry.admin.common.component;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.extra.servlet.ServletUtil;
|
||||
import cn.hutool.http.useragent.UserAgent;
|
||||
import cn.hutool.http.useragent.UserAgentUtil;
|
||||
import com.starry.admin.common.domain.LoginUser;
|
||||
import com.starry.admin.common.security.entity.JwtUser;
|
||||
import com.starry.common.constant.CacheConstants;
|
||||
import com.starry.common.constant.Constants;
|
||||
import com.starry.common.constant.SecurityConstants;
|
||||
import com.starry.common.redis.RedisCache;
|
||||
import com.starry.common.utils.ServletUtils;
|
||||
import com.starry.common.utils.StringUtils;
|
||||
import com.starry.common.utils.ip.AddressUtils;
|
||||
import com.starry.common.utils.ip.IpUtils;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author admin
|
||||
* token 组件
|
||||
* @since 2021/9/6
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class JwtToken {
|
||||
|
||||
private static final String CLAIM_KEY_USERNAME = "sub";
|
||||
private static final String CLAIM_KEY_CREATED = "created";
|
||||
private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L;
|
||||
|
||||
@Value("${jwt.secret}")
|
||||
private String secret;
|
||||
@Value("${jwt.expiration}")
|
||||
private Long expire;
|
||||
@Value("${jwt.tokenHeader}")
|
||||
private String tokenHeader;
|
||||
@Value("${jwt.tokenHead}")
|
||||
private String tokenHead;
|
||||
|
||||
@Resource
|
||||
private RedisCache redisCache;
|
||||
|
||||
|
||||
/**
|
||||
* 从token中获取登录用户名
|
||||
*/
|
||||
public String getUserNameFromToken(String token) {
|
||||
String username;
|
||||
try {
|
||||
Claims claims = getClaimsFromToken(token);
|
||||
username = claims.getSubject();
|
||||
} catch (Exception e) {
|
||||
username = null;
|
||||
}
|
||||
return username;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验token
|
||||
*/
|
||||
public boolean validateToken(String token, UserDetails userDetails) {
|
||||
String username = getUserNameFromToken(token);
|
||||
return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户信息生成token
|
||||
*/
|
||||
public String generateToken(UserDetails userDetails) {
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
|
||||
claims.put(CLAIM_KEY_CREATED, new Date());
|
||||
return generateToken(claims);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建令牌
|
||||
*
|
||||
* @param jwtUser 用户信息
|
||||
* @return 令牌
|
||||
*/
|
||||
public String createToken(JwtUser jwtUser) {
|
||||
String token = IdUtil.fastSimpleUUID();
|
||||
jwtUser.setToken(token);
|
||||
setUserAgent(jwtUser);
|
||||
refresToken(jwtUser);
|
||||
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put(Constants.LOGIN_USER_KEY, token);
|
||||
return createToken(claims);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从数据声明生成令牌
|
||||
*
|
||||
* @param claims 数据声明
|
||||
* @return 令牌
|
||||
*/
|
||||
private String createToken(Map<String, Object> claims) {
|
||||
String token = Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS512, secret).compact();
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断token是否已经失效
|
||||
*/
|
||||
private boolean isTokenExpired(String token) {
|
||||
Date expiredDate = getClaimsFromToken(token).getExpiration();
|
||||
return expiredDate.before(new Date());
|
||||
}
|
||||
|
||||
private String generateToken(Map<String, Object> claims) {
|
||||
return Jwts.builder().setClaims(claims).setExpiration(generateExpirationDate())
|
||||
// 签名算法
|
||||
.signWith(SignatureAlgorithm.HS512, secret).compact();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成token的过期时间
|
||||
*/
|
||||
private Date generateExpirationDate() {
|
||||
return new Date(System.currentTimeMillis() + expire * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从令牌中获取数据声明
|
||||
*
|
||||
* @param token 令牌
|
||||
* @return 数据声明
|
||||
*/
|
||||
private Claims getClaimsFromToken(String token) {
|
||||
Claims claims = null;
|
||||
try {
|
||||
claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
|
||||
} catch (Exception e) {
|
||||
log.info("JWT格式验证失败:{}", token);
|
||||
}
|
||||
return claims;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置用户代理信息
|
||||
*
|
||||
* @param jwtUser 登录信息
|
||||
*/
|
||||
public void setUserAgent(JwtUser jwtUser) {
|
||||
UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
|
||||
String ip = ServletUtil.getClientIP(ServletUtils.getRequest());
|
||||
jwtUser.setIpaddr(ip);
|
||||
jwtUser.setLoginLocation(AddressUtils.getRealAddressByIp(ip));
|
||||
jwtUser.setBrowser(userAgent.getBrowser().getName());
|
||||
jwtUser.setOs(userAgent.getOs().getName());
|
||||
}
|
||||
|
||||
public void refresToken(JwtUser jwtUser) {
|
||||
jwtUser.setLoginTime(System.currentTimeMillis());
|
||||
jwtUser.setExpireTime(jwtUser.getLoginTime() + expire * 1000);
|
||||
String userKey = getTokenKey(jwtUser.getToken());
|
||||
redisCache.setCacheObject(userKey, jwtUser, expire, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private String getTokenKey(String uuid) {
|
||||
return CacheConstants.LOGIN_TOKEN_KEY + uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取登录用户身份信息
|
||||
*
|
||||
* @return 用户信息
|
||||
*/
|
||||
public JwtUser getLoginUser(HttpServletRequest request) {
|
||||
String token = getToken(request);
|
||||
if (StringUtils.isNotEmpty(token)) {
|
||||
try {
|
||||
Claims claims = getClaimsFromToken(token);
|
||||
String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
|
||||
String userKey = getTokenKey(uuid);
|
||||
JwtUser user = redisCache.getCacheObject(userKey);
|
||||
return user;
|
||||
} catch (Exception e) {
|
||||
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求token
|
||||
*
|
||||
* @param request
|
||||
* @return token
|
||||
*/
|
||||
private String getToken(HttpServletRequest request) {
|
||||
// 获取请求头
|
||||
String token = request.getHeader(tokenHeader);
|
||||
if (StringUtils.isNotEmpty(token) && token.startsWith(tokenHead)) {
|
||||
token = token.replace(tokenHead, "");
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证令牌有效期,相差不足20分钟,自动刷新缓存
|
||||
*
|
||||
* @param jwtUser
|
||||
* @return 令牌
|
||||
*/
|
||||
public void verifyToken(JwtUser jwtUser) {
|
||||
long expireTime = jwtUser.getExpireTime();
|
||||
long currentTime = System.currentTimeMillis();
|
||||
if (expireTime - currentTime <= MILLIS_MINUTE_TEN) {
|
||||
refresToken(jwtUser);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除用户身份信息
|
||||
*/
|
||||
public void removeJwtUser(String token) {
|
||||
if (StringUtils.isNotEmpty(token)) {
|
||||
String userKey = getTokenKey(token);
|
||||
redisCache.deleteObject(userKey);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建令牌
|
||||
*/
|
||||
public Map<String, Object> createToken(LoginUser loginUser) {
|
||||
String token = IdUtil.fastSimpleUUID();
|
||||
String userId = loginUser.getUser().getUserId();
|
||||
String userName = loginUser.getUser().getUserCode();
|
||||
String tenantId = loginUser.getUser().getTenantId();
|
||||
Long deptId = loginUser.getUser().getDeptId();
|
||||
loginUser.setToken(token);
|
||||
loginUser.setUserId(userId);
|
||||
loginUser.setUserName(userName);
|
||||
loginUser.setIpaddr(IpUtils.getIpAddr(ServletUtils.getRequest()));
|
||||
// 添加地址信息
|
||||
setUserAgent(loginUser);
|
||||
refreshToken(loginUser);
|
||||
|
||||
// Jwt存储信息
|
||||
Map<String, Object> claimsMap = new HashMap<>(8);
|
||||
claimsMap.put(SecurityConstants.USER_KEY, token);
|
||||
claimsMap.put(SecurityConstants.DETAILS_USER_ID, userId);
|
||||
claimsMap.put(SecurityConstants.DETAILS_USERNAME, userName);
|
||||
// 租户id
|
||||
claimsMap.put(SecurityConstants.DETAILS_TENANT_ID, tenantId);
|
||||
// 部门id
|
||||
claimsMap.put(SecurityConstants.DETAILS_DEPT_ID, deptId);
|
||||
// 接口返回信息
|
||||
Map<String, Object> rspMap = new HashMap<>();
|
||||
rspMap.put("token", this.createToken(claimsMap));
|
||||
rspMap.put("expires_in", expire);
|
||||
rspMap.put("tenant_id", tenantId);
|
||||
rspMap.put("tokenHead", tokenHead);
|
||||
return rspMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置用户代理信息
|
||||
*
|
||||
* @param loginUser 登录信息
|
||||
*/
|
||||
public void setUserAgent(LoginUser loginUser) {
|
||||
UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
|
||||
String ip = ServletUtil.getClientIP(ServletUtils.getRequest());
|
||||
loginUser.setIpaddr(ip);
|
||||
loginUser.setLoginLocation(AddressUtils.getRealAddressByIp(ip));
|
||||
loginUser.setBrowser(userAgent.getBrowser().getName());
|
||||
loginUser.setOs(userAgent.getOs().getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新令牌有效期
|
||||
*
|
||||
* @param loginUser 登录信息
|
||||
*/
|
||||
public void refreshToken(LoginUser loginUser) {
|
||||
loginUser.setLoginTime(System.currentTimeMillis());
|
||||
loginUser.setExpireTime(loginUser.getLoginTime() + expire * 1000);
|
||||
// 根据uuid将loginUser缓存
|
||||
String userKey = getTokenKey(loginUser.getToken());
|
||||
redisCache.setCacheObject(userKey, loginUser, expire, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取登录用户身份信息
|
||||
*
|
||||
* @return 用户信息
|
||||
*/
|
||||
public LoginUser getNewLoginUser(HttpServletRequest request) {
|
||||
String token = getToken(request);
|
||||
if (StringUtils.isNotEmpty(token)) {
|
||||
try {
|
||||
Claims claims = getClaimsFromToken(token);
|
||||
String uuid = (String) claims.get(SecurityConstants.USER_KEY);
|
||||
String userKey = getTokenKey(uuid);
|
||||
LoginUser loginUser = redisCache.getCacheObject(userKey);
|
||||
com.starry.common.context.SecurityContextHolder.set(SecurityConstants.DETAILS_TENANT_ID, loginUser.getUser().getTenantId());
|
||||
com.starry.common.context.SecurityContextHolder.set(SecurityConstants.LOGIN_USER, loginUser);
|
||||
return loginUser;
|
||||
} catch (Exception e) {
|
||||
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证令牌有效期,相差不足20分钟,自动刷新缓存
|
||||
*
|
||||
* @param loginUser
|
||||
* @return 令牌
|
||||
*/
|
||||
public void verifyToken(LoginUser loginUser) {
|
||||
long expireTime = loginUser.getExpireTime();
|
||||
long currentTime = System.currentTimeMillis();
|
||||
if (expireTime - currentTime <= MILLIS_MINUTE_TEN) {
|
||||
refreshToken(loginUser);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package com.starry.admin.common.component;
|
||||
|
||||
|
||||
import com.starry.admin.common.domain.LoginUser;
|
||||
import com.starry.admin.modules.system.entity.SysRoleEntity;
|
||||
import com.starry.admin.utils.SecurityUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 自定义权限实现,ss取自SpringSecurity首字母
|
||||
*
|
||||
* @author admin
|
||||
*/
|
||||
@Service("customSs")
|
||||
public class PermissionService {
|
||||
/**
|
||||
* 所有权限标识
|
||||
*/
|
||||
private static final String ALL_PERMISSION = "*:*:*";
|
||||
|
||||
/**
|
||||
* 管理员角色权限标识
|
||||
*/
|
||||
private static final String SUPER_ADMIN = "admin";
|
||||
|
||||
private static final String ROLE_DELIMETER = ",";
|
||||
|
||||
private static final String PERMISSION_DELIMETER = ",";
|
||||
|
||||
/**
|
||||
* 验证用户是否具备某权限
|
||||
*
|
||||
* @param permission 权限字符串
|
||||
* @return 用户是否具备某权限
|
||||
*/
|
||||
public boolean hasPermi(String permission) {
|
||||
if (StringUtils.isEmpty(permission)) {
|
||||
return false;
|
||||
}
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
if (loginUser == null || CollectionUtils.isEmpty(loginUser.getPermissions())) {
|
||||
return false;
|
||||
}
|
||||
return hasPermissions(loginUser.getPermissions(), permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证用户是否不具备某权限,与 hasPermi逻辑相反
|
||||
*
|
||||
* @param permission 权限字符串
|
||||
* @return 用户是否不具备某权限
|
||||
*/
|
||||
public boolean lacksPermi(String permission) {
|
||||
return hasPermi(permission) != true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证用户是否具有以下任意一个权限
|
||||
*
|
||||
* @param permissions 以 PERMISSION_NAMES_DELIMETER 为分隔符的权限列表
|
||||
* @return 用户是否具有以下任意一个权限
|
||||
*/
|
||||
public boolean hasAnyPermi(String permissions) {
|
||||
if (StringUtils.isEmpty(permissions)) {
|
||||
return false;
|
||||
}
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
if (loginUser == null || CollectionUtils.isEmpty(loginUser.getPermissions())) {
|
||||
return false;
|
||||
}
|
||||
Set<String> authorities = loginUser.getPermissions();
|
||||
for (String permission : permissions.split(PERMISSION_DELIMETER)) {
|
||||
if (permission != null && hasPermissions(authorities, permission)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断用户是否拥有某个角色
|
||||
*
|
||||
* @param role 角色字符串
|
||||
* @return 用户是否具备某角色
|
||||
*/
|
||||
public boolean hasRole(String role) {
|
||||
if (StringUtils.isEmpty(role)) {
|
||||
return false;
|
||||
}
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
if (loginUser == null || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) {
|
||||
return false;
|
||||
}
|
||||
for (SysRoleEntity sysRoleEntity : loginUser.getUser().getRoles()) {
|
||||
String roleKey = sysRoleEntity.getRoleKey();
|
||||
if (SUPER_ADMIN.equals(roleKey) || roleKey.equals(StringUtils.trim(role))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证用户是否不具备某角色,与 isRole逻辑相反。
|
||||
*
|
||||
* @param role 角色名称
|
||||
* @return 用户是否不具备某角色
|
||||
*/
|
||||
public boolean lacksRole(String role) {
|
||||
return hasRole(role) != true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证用户是否具有以下任意一个角色
|
||||
*
|
||||
* @param roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表
|
||||
* @return 用户是否具有以下任意一个角色
|
||||
*/
|
||||
public boolean hasAnyRoles(String roles) {
|
||||
if (StringUtils.isEmpty(roles)) {
|
||||
return false;
|
||||
}
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
if (loginUser == null || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) {
|
||||
return false;
|
||||
}
|
||||
for (String role : roles.split(ROLE_DELIMETER)) {
|
||||
if (hasRole(role)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否包含权限
|
||||
*
|
||||
* @param permissions 权限列表
|
||||
* @param permission 权限字符串
|
||||
* @return 用户是否具备某权限
|
||||
*/
|
||||
private boolean hasPermissions(Set<String> permissions, String permission) {
|
||||
return permissions.contains(ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package com.starry.admin.common.domain;
|
||||
|
||||
import com.starry.admin.modules.system.entity.SysUserEntity;
|
||||
import lombok.Data;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author admin
|
||||
*/
|
||||
@Data
|
||||
public class LoginUser implements UserDetails {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 用户唯一标识
|
||||
*/
|
||||
private String token;
|
||||
/**
|
||||
* 用户名id
|
||||
*/
|
||||
private String userId;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String userName;
|
||||
|
||||
/**
|
||||
* 登录时间
|
||||
*/
|
||||
private Long loginTime;
|
||||
|
||||
/**
|
||||
* 过期时间
|
||||
*/
|
||||
private Long expireTime;
|
||||
|
||||
/**
|
||||
* 登录IP地址
|
||||
*/
|
||||
private String ipaddr;
|
||||
/**
|
||||
* 登录地点
|
||||
*/
|
||||
private String loginLocation;
|
||||
|
||||
/**
|
||||
* 浏览器类型
|
||||
*/
|
||||
private String browser;
|
||||
|
||||
/**
|
||||
* 操作系统
|
||||
*/
|
||||
private String os;
|
||||
|
||||
/**
|
||||
* 权限列表
|
||||
*/
|
||||
private Set<String> permissions;
|
||||
|
||||
/**
|
||||
* 角色列表
|
||||
*/
|
||||
private Set<String> roles;
|
||||
|
||||
/**
|
||||
* 用户信息
|
||||
*/
|
||||
private SysUserEntity user;
|
||||
|
||||
/**
|
||||
* 租户租赁截止日期--dhr
|
||||
*/
|
||||
private Date tenantEndDate;
|
||||
|
||||
/**
|
||||
* 租户状态
|
||||
*/
|
||||
private Integer tenantStatus;
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return user.getPassWord();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return user.getUserCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* 账户是否未过期
|
||||
**/
|
||||
@Override
|
||||
public boolean isAccountNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 账户是否未锁定
|
||||
**/
|
||||
@Override
|
||||
public boolean isAccountNonLocked() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 密码是否未过期
|
||||
**/
|
||||
@Override
|
||||
public boolean isCredentialsNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 账户是否激活
|
||||
**/
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return user.getStatus() == 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.starry.admin.common.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.starry.admin.modules.system.entity.SysDeptEntity;
|
||||
import com.starry.admin.modules.system.entity.SysMenuEntity;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Treeselect树结构实体类
|
||||
* @since 2022/7/4
|
||||
*/
|
||||
@Data
|
||||
public class TreeSelect implements Serializable {
|
||||
|
||||
/**
|
||||
* 节点ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 节点名称
|
||||
*/
|
||||
private String label;
|
||||
|
||||
/**
|
||||
* 子节点
|
||||
*/
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
private List<TreeSelect> children;
|
||||
|
||||
public TreeSelect(SysMenuEntity menu) {
|
||||
this.id = menu.getMenuId();
|
||||
this.label = menu.getMenuName();
|
||||
this.children = menu.getChildren().stream().map(TreeSelect::new).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public TreeSelect(SysDeptEntity dept) {
|
||||
this.id = dept.getDeptId();
|
||||
this.label = dept.getDeptName();
|
||||
this.children = dept.getChildren().stream().map(TreeSelect::new).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.starry.admin.common.exception;
|
||||
|
||||
/**
|
||||
* 自定义异常处理器 * * @author admin
|
||||
*/
|
||||
public class CustomException extends RuntimeException {
|
||||
public CustomException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.starry.admin.common.exception;
|
||||
|
||||
/**
|
||||
* @author 业务异常
|
||||
* @since 2023/3/9
|
||||
*/
|
||||
public class ServiceException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 错误码
|
||||
*/
|
||||
private Integer code;
|
||||
|
||||
/**
|
||||
* 错误提示
|
||||
*/
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 错误明细,内部调试错误
|
||||
*/
|
||||
private String detailMessage;
|
||||
|
||||
/**
|
||||
* 空构造方法,避免反序列化问题
|
||||
*/
|
||||
public ServiceException() {
|
||||
}
|
||||
|
||||
public ServiceException(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public ServiceException(String message, Integer code) {
|
||||
this.message = message;
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getDetailMessage() {
|
||||
return detailMessage;
|
||||
}
|
||||
|
||||
public ServiceException setDetailMessage(String detailMessage) {
|
||||
this.detailMessage = detailMessage;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public ServiceException setMessage(String message) {
|
||||
this.message = message;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Integer getCode() {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.starry.admin.common.exception.handler;
|
||||
|
||||
|
||||
import com.starry.admin.common.exception.CustomException;
|
||||
import com.starry.admin.common.exception.ServiceException;
|
||||
import com.starry.common.result.R;
|
||||
import com.starry.common.utils.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* @author 全局异常处理
|
||||
* @since 2023/3/9
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
|
||||
|
||||
|
||||
/**
|
||||
* 业务异常
|
||||
*/
|
||||
@ExceptionHandler(ServiceException.class)
|
||||
public R handleServiceException(ServiceException e, HttpServletRequest request) {
|
||||
log.error(e.getMessage(), e);
|
||||
Integer code = e.getCode();
|
||||
return StringUtils.isNotNull(code) ? R.error(code, e.getMessage()) : R.error(e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截未知的运行时异常
|
||||
*/
|
||||
@ExceptionHandler(RuntimeException.class)
|
||||
public R handleRuntimeException(RuntimeException e, HttpServletRequest request) {
|
||||
String requestUrl = request.getRequestURI();
|
||||
log.error("请求地址'{}',发生未知异常.", requestUrl, e);
|
||||
return R.error("系统发生未知异常,请联系管理员");
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统异常
|
||||
*/
|
||||
@ExceptionHandler(Exception.class)
|
||||
public R handleException(Exception e, HttpServletRequest request) {
|
||||
String requestUrl = request.getRequestURI();
|
||||
log.error("请求地址'{}',发生系统异常.", requestUrl, e);
|
||||
return R.error("系统出现内部错误,请联系管理员");
|
||||
}
|
||||
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public R methodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) {
|
||||
BindingResult bindingResult = e.getBindingResult();
|
||||
StringBuilder errorMessageBuilder = new StringBuilder();
|
||||
for (FieldError error : bindingResult.getFieldErrors()) {
|
||||
errorMessageBuilder.append(error.getDefaultMessage());
|
||||
}
|
||||
return R.error("请求参数异常," + errorMessageBuilder);
|
||||
}
|
||||
|
||||
@ExceptionHandler(CustomException.class)
|
||||
public R customException(CustomException e, HttpServletRequest request) {
|
||||
return R.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.starry.admin.common.mybatis.config;
|
||||
|
||||
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
|
||||
import com.starry.admin.common.mybatis.handler.MyTenantLineHandler;
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
|
||||
/**
|
||||
* @author admin
|
||||
*/
|
||||
@Configuration
|
||||
@MapperScan("com.starry.**.mapper")
|
||||
@EnableTransactionManagement
|
||||
public class MybatisPlusConfig {
|
||||
|
||||
/**
|
||||
* druid注入
|
||||
*
|
||||
* @return dataSource
|
||||
*/
|
||||
@Bean(name = "dataSource")
|
||||
@ConfigurationProperties(prefix = "spring.datasource.druid")
|
||||
public DataSource dataSource() {
|
||||
return DruidDataSourceBuilder.create().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置事物管理器
|
||||
*
|
||||
* @return DataSourceTransactionManager
|
||||
*/
|
||||
@Bean(name = "transactionManager")
|
||||
public DataSourceTransactionManager transactionManager() {
|
||||
return new DataSourceTransactionManager(dataSource());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||
// 租户插件
|
||||
interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new MyTenantLineHandler()));
|
||||
// 分页插件
|
||||
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
|
||||
// 乐观锁插件
|
||||
interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor());
|
||||
// 阻断插件
|
||||
interceptor.addInnerInterceptor(blockAttackInnerInterceptor());
|
||||
return interceptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 乐观锁
|
||||
*/
|
||||
@Bean
|
||||
public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() {
|
||||
return new OptimisticLockerInnerInterceptor();
|
||||
}
|
||||
|
||||
/**
|
||||
* 防止全表更新与删除
|
||||
*/
|
||||
@Bean
|
||||
public BlockAttackInnerInterceptor blockAttackInnerInterceptor() {
|
||||
return new BlockAttackInnerInterceptor();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.starry.admin.common.mybatis.handler;
|
||||
|
||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||
import com.starry.admin.utils.SecurityUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.ibatis.reflection.MetaObject;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author admin
|
||||
* 字段默认值处理类
|
||||
* @since 2021/9/1
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class MyMetaObjectHandler implements MetaObjectHandler {
|
||||
|
||||
@Override
|
||||
public void insertFill(MetaObject metaObject) {
|
||||
log.info("start insert fill ....");
|
||||
this.setFieldValByName("createdTime", new Date(), metaObject);
|
||||
this.setFieldValByName("deleted", false, metaObject);
|
||||
this.setFieldValByName("version", 1L, metaObject);
|
||||
Object createUser = this.getFieldValByName("createdBy", metaObject);
|
||||
if (createUser == null) {
|
||||
if (SecurityUtils.isLogin()) {
|
||||
this.setFieldValByName("createdBy", SecurityUtils.getUserId(), metaObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFill(MetaObject metaObject) {
|
||||
log.info("start update fill ....");
|
||||
this.setFieldValByName("updatedTime", new Date(), metaObject);
|
||||
Object createUser = this.getFieldValByName("updatedBy", metaObject);
|
||||
if (createUser == null) {
|
||||
if (SecurityUtils.isLogin()) {
|
||||
this.setFieldValByName("updatedBy", SecurityUtils.getUserId(), metaObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.starry.admin.common.mybatis.handler;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
|
||||
import com.starry.admin.utils.SecurityUtils;
|
||||
import com.starry.common.utils.StringUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.sf.jsqlparser.expression.Expression;
|
||||
import net.sf.jsqlparser.expression.LongValue;
|
||||
import net.sf.jsqlparser.expression.NullValue;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* @author admin
|
||||
* 多租户处理器
|
||||
* @since 2023/3/7
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class MyTenantLineHandler implements TenantLineHandler {
|
||||
|
||||
/**
|
||||
* 排除过滤的表
|
||||
*/
|
||||
private static final String[] TABLE_FILTER = {"sys_menu", "sys_tenant_package", "sys_tenant", "sys_dict", "sys_dict_data"};
|
||||
|
||||
/**
|
||||
* 排除过滤的表前缀
|
||||
*/
|
||||
private static final String[] TABLE_PRE = {"qrtz", "gen"};
|
||||
|
||||
@Override
|
||||
public Expression getTenantId() {
|
||||
// 取出当前请求的服务商ID,通过解析器注入到SQL中。
|
||||
Long tenantId = SecurityUtils.getTenantId();
|
||||
if (tenantId == null) {
|
||||
return new NullValue();
|
||||
}
|
||||
return new LongValue(tenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳过不需要加多租户的表
|
||||
*/
|
||||
@Override
|
||||
public boolean ignoreTable(String tableName) {
|
||||
String prefix = StringUtils.substringBefore(tableName, "_");
|
||||
if (Arrays.asList(TABLE_FILTER).contains(tableName) || Arrays.asList(TABLE_PRE).contains(prefix)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package com.starry.admin.common.security.config;
|
||||
|
||||
|
||||
import com.starry.admin.common.security.entity.JwtUser;
|
||||
import com.starry.admin.common.security.filter.JwtAuthenticationTokenFilter;
|
||||
import com.starry.admin.common.security.handler.CustomAccessDeniedHandler;
|
||||
import com.starry.admin.common.security.handler.CustomAuthenticationEntryPoint;
|
||||
import com.starry.admin.common.security.handler.CustomLogoutSuccessHandler;
|
||||
import com.starry.admin.modules.system.entity.SysUserEntity;
|
||||
import com.starry.admin.modules.system.service.SysMenuService;
|
||||
import com.starry.admin.modules.system.service.SysUserService;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author admin
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Resource
|
||||
private CustomAccessDeniedHandler customAccessDeniedHandler;
|
||||
@Resource
|
||||
private CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
|
||||
@Resource
|
||||
private SysUserService sysUserService;
|
||||
@Resource
|
||||
private SysMenuService menuService;
|
||||
@Resource
|
||||
private CustomLogoutSuccessHandler customLogoutSuccessHandler;
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity httpSecurity) throws Exception {
|
||||
httpSecurity.csrf().disable()// 由于使用的是JWT,我们这里不需要csrf
|
||||
.sessionManagement()// 基于token,所以不需要session
|
||||
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||
.and()
|
||||
.authorizeRequests()
|
||||
// 允许对于网站静态资源的无授权访问
|
||||
.antMatchers(HttpMethod.GET,
|
||||
"/",
|
||||
"/*.html",
|
||||
"/favicon.ico",
|
||||
"/**/*.html",
|
||||
"/**/*.css",
|
||||
"/**/*.js",
|
||||
"/swagger-resources/**",
|
||||
"/v2/api-docs/**"
|
||||
).permitAll()
|
||||
// 对登录注册要允许匿名访问
|
||||
.antMatchers("/login", "/captcha/get-captcha").permitAll()
|
||||
// 跨域请求会先进行一次options请求
|
||||
.antMatchers(HttpMethod.OPTIONS).permitAll()
|
||||
.anyRequest()// 除上面外的所有请求全部需要鉴权认证
|
||||
.authenticated();
|
||||
// 禁用缓存
|
||||
httpSecurity.headers().cacheControl();
|
||||
// 添加Logout filter
|
||||
httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(customLogoutSuccessHandler);
|
||||
// 添加JWT filter
|
||||
httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
|
||||
// 添加自定义未授权和未登录结果返回
|
||||
httpSecurity.exceptionHandling()
|
||||
.accessDeniedHandler(customAccessDeniedHandler)
|
||||
.authenticationEntryPoint(customAuthenticationEntryPoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
|
||||
auth.userDetailsService(userDetailsService())
|
||||
.passwordEncoder(passwordEncoder());
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Override
|
||||
public UserDetailsService userDetailsService() {
|
||||
// 获取登录用户信息
|
||||
return username -> {
|
||||
SysUserEntity user = sysUserService.selectUserByUserName(username);
|
||||
if (user != null) {
|
||||
if (user.getStatus() == 1) {
|
||||
throw new UsernameNotFoundException("对不起,您的账号:" + username + " 已停用");
|
||||
}
|
||||
// 获取菜单权限
|
||||
Set<String> permissionList = menuService.selectMenuPermsByUserId(user.getUserId());
|
||||
return new JwtUser(user, permissionList);
|
||||
}
|
||||
throw new UsernameNotFoundException("用户名或密码错误");
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 装载BCrypt密码编码器
|
||||
*/
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
/**
|
||||
* JWT filter
|
||||
*/
|
||||
@Bean
|
||||
public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter() {
|
||||
return new JwtAuthenticationTokenFilter();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
package com.starry.admin.common.security.entity;
|
||||
|
||||
import com.starry.admin.modules.system.entity.SysUserEntity;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author admin
|
||||
*/
|
||||
public class JwtUser implements UserDetails {
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
/**
|
||||
* 登录时间
|
||||
*/
|
||||
private Long loginTime;
|
||||
|
||||
/**
|
||||
* 用户唯一标识
|
||||
*/
|
||||
private String token;
|
||||
|
||||
/**
|
||||
* 过期时间
|
||||
*/
|
||||
private Long expireTime;
|
||||
|
||||
/**
|
||||
* 登录IP地址
|
||||
*/
|
||||
private String ipaddr;
|
||||
|
||||
/**
|
||||
* 登录地点
|
||||
*/
|
||||
private String loginLocation;
|
||||
|
||||
/**
|
||||
* 浏览器类型
|
||||
*/
|
||||
private String browser;
|
||||
|
||||
/**
|
||||
* 操作系统
|
||||
*/
|
||||
private String os;
|
||||
|
||||
/**
|
||||
* 用户信息
|
||||
*/
|
||||
private SysUserEntity user;
|
||||
|
||||
/**
|
||||
* 权限列表
|
||||
*/
|
||||
private Set<String> permissions;
|
||||
|
||||
|
||||
public JwtUser(SysUserEntity user, Set<String> permissions) {
|
||||
this.user = user;
|
||||
this.permissions = permissions;
|
||||
}
|
||||
|
||||
public JwtUser(Long userId, SysUserEntity user, Set<String> permissions) {
|
||||
this.userId = userId;
|
||||
this.user = user;
|
||||
this.permissions = permissions;
|
||||
}
|
||||
|
||||
public Long getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(Long userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return user.getPassWord();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return user.getUserCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* 账户是否未过期
|
||||
**/
|
||||
@Override
|
||||
public boolean isAccountNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 账户是否未锁定
|
||||
**/
|
||||
@Override
|
||||
public boolean isAccountNonLocked() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 密码是否未过期
|
||||
**/
|
||||
@Override
|
||||
public boolean isCredentialsNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 账户是否激活
|
||||
**/
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return user.getStatus() == 0;
|
||||
}
|
||||
|
||||
public Long getLoginTime() {
|
||||
return loginTime;
|
||||
}
|
||||
|
||||
public void setLoginTime(Long loginTime) {
|
||||
this.loginTime = loginTime;
|
||||
}
|
||||
|
||||
public Set<String> getPermissions() {
|
||||
return permissions;
|
||||
}
|
||||
|
||||
public void setPermissions(Set<String> permissions) {
|
||||
this.permissions = permissions;
|
||||
}
|
||||
|
||||
public SysUserEntity getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public void setUser(SysUserEntity user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public String getIpaddr() {
|
||||
return ipaddr;
|
||||
}
|
||||
|
||||
public void setIpaddr(String ipaddr) {
|
||||
this.ipaddr = ipaddr;
|
||||
}
|
||||
|
||||
public String getLoginLocation() {
|
||||
return loginLocation;
|
||||
}
|
||||
|
||||
public void setLoginLocation(String loginLocation) {
|
||||
this.loginLocation = loginLocation;
|
||||
}
|
||||
|
||||
public String getBrowser() {
|
||||
return browser;
|
||||
}
|
||||
|
||||
public void setBrowser(String browser) {
|
||||
this.browser = browser;
|
||||
}
|
||||
|
||||
public String getOs() {
|
||||
return os;
|
||||
}
|
||||
|
||||
public void setOs(String os) {
|
||||
this.os = os;
|
||||
}
|
||||
|
||||
public Long getExpireTime() {
|
||||
return expireTime;
|
||||
}
|
||||
|
||||
public void setExpireTime(Long expireTime) {
|
||||
this.expireTime = expireTime;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.starry.admin.common.security.filter;
|
||||
|
||||
|
||||
import com.starry.admin.common.component.JwtToken;
|
||||
import com.starry.admin.common.domain.LoginUser;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @author admin
|
||||
* JWT登录授权过滤器
|
||||
* @since 2021/9/6
|
||||
*/
|
||||
@Slf4j
|
||||
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
|
||||
|
||||
|
||||
@Value("${jwt.tokenHeader}")
|
||||
private String tokenHeader;
|
||||
@Value("${jwt.tokenHead}")
|
||||
private String tokenHead;
|
||||
@Resource
|
||||
private JwtToken jwtToken;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
|
||||
LoginUser jwtUser = jwtToken.getNewLoginUser(httpServletRequest);
|
||||
if (null != jwtUser && null == SecurityContextHolder.getContext().getAuthentication()) {
|
||||
jwtToken.verifyToken(jwtUser);
|
||||
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(jwtUser, null, jwtUser.getAuthorities());
|
||||
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
}
|
||||
filterChain.doFilter(httpServletRequest, httpServletResponse);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.starry.admin.common.security.handler;
|
||||
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.starry.common.result.R;
|
||||
import com.starry.common.result.ResultCodeEnum;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.web.access.AccessDeniedHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @author admin
|
||||
* 当访问接口没有权限时,自定义的返回结果
|
||||
* @since 2021/9/6
|
||||
*/
|
||||
@Component
|
||||
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
|
||||
|
||||
@Override
|
||||
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
|
||||
httpServletResponse.setCharacterEncoding("UTF-8");
|
||||
httpServletResponse.setContentType("application/json");
|
||||
httpServletResponse.getWriter().println(JSONUtil.parse(R.error(ResultCodeEnum.FORBIDDEN.getCode(), ResultCodeEnum.FORBIDDEN.getMessage())));
|
||||
httpServletResponse.getWriter().flush();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.starry.admin.common.security.handler;
|
||||
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.starry.common.result.R;
|
||||
import com.starry.common.result.ResultCodeEnum;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @author admin
|
||||
* 当未登录或者token失效访问接口时,自定义的返回结果
|
||||
* @since 2021/9/6
|
||||
*/
|
||||
@Component
|
||||
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
||||
@Override
|
||||
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
|
||||
httpServletResponse.setCharacterEncoding("UTF-8");
|
||||
httpServletResponse.setContentType("application/json");
|
||||
httpServletResponse.getWriter().println(JSONUtil.parse(R.error(ResultCodeEnum.UNAUTHORIZED.getCode(), ResultCodeEnum.UNAUTHORIZED.getMessage())));
|
||||
httpServletResponse.getWriter().flush();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.starry.admin.common.security.handler;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.starry.admin.common.component.JwtToken;
|
||||
import com.starry.admin.common.security.entity.JwtUser;
|
||||
import com.starry.admin.manager.AsyncManager;
|
||||
import com.starry.admin.manager.factory.AsyncFactory;
|
||||
import com.starry.common.constant.Constants;
|
||||
import com.starry.common.result.R;
|
||||
import com.starry.common.utils.ServletUtils;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* @author admin
|
||||
* 自定义退出处理类 返回成功
|
||||
* @since 2022/7/8
|
||||
*/
|
||||
@Component
|
||||
public class CustomLogoutSuccessHandler implements LogoutSuccessHandler {
|
||||
|
||||
@Resource
|
||||
private JwtToken jwtToken;
|
||||
|
||||
@Override
|
||||
public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) {
|
||||
JwtUser jwtUser = jwtToken.getLoginUser(httpServletRequest);
|
||||
if (null != jwtUser) {
|
||||
// 删除用户缓存记录
|
||||
jwtToken.removeJwtUser(jwtUser.getToken());
|
||||
// 记录用户退出日志
|
||||
AsyncManager.me().execute(AsyncFactory.recordLoginLog(jwtUser.getUsername(), Constants.LOGOUT, "退出成功"));
|
||||
}
|
||||
ServletUtils.renderString(httpServletResponse, JSON.toJSONString(R.ok("退出成功")));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user