优化权限逻辑.

This commit is contained in:
lijiahang
2024-08-20 10:20:35 +08:00
parent 2afaf7ad34
commit 059fb30aa4
31 changed files with 1077 additions and 477 deletions

View File

@@ -1,6 +1,8 @@
package com.orion.visor.framework.common.meta;
import com.alibaba.ttl.TransmittableThreadLocal;
import com.orion.lang.id.UUIds;
import org.slf4j.MDC;
/**
* traceId 持有者
@@ -23,16 +25,74 @@ public class TraceIdHolder {
*/
private static final ThreadLocal<String> HOLDER = new TransmittableThreadLocal<>();
/**
* 获取 traceId
*
* @return traceId
*/
public static String get() {
return HOLDER.get();
}
public static void set(String traceId) {
HOLDER.set(traceId);
/**
* 设置 traceId
*/
public static void set() {
set(createTraceId());
}
/**
* 设置 traceId
*
* @param traceId traceId
*/
public static void set(String traceId) {
// 设置应用上下文
HOLDER.set(traceId);
// 设置日志上下文
setMdc(traceId);
}
/**
* 删除 traceId
*/
public static void remove() {
// 移除应用上下文
HOLDER.remove();
// 移除日志上下文
removeMdc();
}
/**
* 从应用上下文 设置到日志上下文
*/
public static void setMdc() {
setMdc(HOLDER.get());
}
/**
* 设置到日志上下文
*
* @param traceId traceId
*/
public static void setMdc(String traceId) {
MDC.put(TRACE_ID_MDC, traceId);
}
/**
* 移除日志上下文
*/
public static void removeMdc() {
MDC.remove(TRACE_ID_MDC);
}
/**
* 创建 traceId
*
* @return traceId
*/
public static String createTraceId() {
return UUIds.random32();
}
}

View File

@@ -98,6 +98,13 @@ public class SecurityUtils {
return loginUser != null ? loginUser.getTimestamp() : null;
}
/**
* 清空用户上下文
*/
public static void clearAuthentication() {
SecurityContextHolder.getContext().setAuthentication(null);
}
/**
* 设置当前用户
*
@@ -107,7 +114,9 @@ public class SecurityUtils {
public static void setLoginUser(LoginUser loginUser, HttpServletRequest request) {
// 创建 authentication
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(loginUser, null, Collections.emptyList());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
if (request != null) {
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
}
// 设置上下文
SecurityContextHolder.getContext().setAuthentication(authentication);
}

View File

@@ -1,8 +1,6 @@
package com.orion.visor.framework.web.core.filter;
import com.orion.lang.id.UUIds;
import com.orion.visor.framework.common.meta.TraceIdHolder;
import org.slf4j.MDC;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
@@ -23,21 +21,17 @@ public class TraceIdFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
// 获 traceId
String traceId = UUIds.random32();
// 设置应用上下文
// 获 traceId
String traceId = TraceIdHolder.createTraceId();
// 设置 traceId 上下文
TraceIdHolder.set(traceId);
// 设置日志上下文
MDC.put(TraceIdHolder.TRACE_ID_MDC, traceId);
// 设置响应头
response.setHeader(TraceIdHolder.TRACE_ID_HEADER, traceId);
// 执行请求
filterChain.doFilter(request, response);
} finally {
// 清理应用上下文
// 清空 traceId 上下文
TraceIdHolder.remove();
// 清理日志上下文
MDC.clear();
}
}

View File

@@ -167,6 +167,8 @@ app:
allow-refresh: true
# 凭证续签最大次数
max-refresh-count: 3
# 登录失败发送站内信阈值
login-failed-send-threshold: 3
# 登录失败锁定次数
login-failed-lock-count: 5
# 登录失败锁定时间 (分)

View File

@@ -7,6 +7,7 @@ import com.orion.lang.utils.Strings;
import com.orion.net.host.SessionHolder;
import com.orion.net.host.SessionLogger;
import com.orion.net.host.SessionStore;
import com.orion.visor.framework.common.constant.AppConst;
import com.orion.visor.framework.common.constant.Const;
import com.orion.visor.framework.common.utils.CryptoUtils;
import com.orion.visor.module.asset.entity.dto.HostTerminalConnectDTO;
@@ -43,6 +44,8 @@ public class SessionStores {
SessionHolder sessionHolder = SessionHolder.create();
sessionHolder.setLogger(SessionLogger.INFO);
SessionStore session = createSessionStore(conn, sessionHolder);
// 设置版本
session.getSession().setClientVersion("SSH-2.0-ORION_VISOR_V" + AppConst.VERSION);
// 连接
session.connect();
log.info("SessionStores-open-success hostId: {}, address: {}, username: {}", hostId, address, username);

View File

@@ -65,13 +65,15 @@ public class TerminalConnectHandler extends AbstractTerminalHandler<TerminalConn
// 移除会话连接信息
channel.getAttributes().remove(sessionId);
Exception ex = null;
ITerminalSession session = null;
try {
// 连接主机
ITerminalSession session = this.connect(sessionId, connect, channel, payload);
session = this.connect(sessionId, connect, channel, payload);
// 添加会话到 manager
hostTerminalManager.addSession(session);
} catch (Exception e) {
ex = e;
Streams.close(session);
// 修改连接状态为失败
Map<String, Object> extra = Maps.newMap(4);
extra.put(ExtraFieldConst.ERROR_MESSAGE, this.getConnectErrorMessage(e));

View File

@@ -0,0 +1,24 @@
package com.orion.visor.module.infra.api;
import com.orion.visor.module.infra.entity.dto.user.SystemUserAuthDTO;
/**
* 认证服务实现
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/8/14 21:37
*/
public interface AuthenticationApi {
/**
* 通过密码认证
*
* @param username username
* @param password password
* @param addFailedCount addFailedCount
* @return result
*/
SystemUserAuthDTO authByPassword(String username, String password, boolean addFailedCount);
}

View File

@@ -0,0 +1,58 @@
package com.orion.visor.module.infra.api;
import java.util.List;
/**
* 权限 对外服务类
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/8/19 15:22
*/
public interface PermissionApi {
/**
* 用户是否为管理员用户
*
* @param id id
* @return isAdmin
*/
boolean isAdminUser(Long id);
/**
* 检查当前用户是否含有此角色
*
* @param userId userId
* @param role role
* @return 是否包含
*/
boolean hasRole(Long userId, String role);
/**
* 检查当前用户是否含有任意角色
*
* @param userId userId
* @param roles roles
* @return 是否包含
*/
boolean hasAnyRole(Long userId, List<String> roles);
/**
* 检查当前用户是否含有此权限
*
* @param userId userId
* @param permission permission
* @return 是否包含
*/
boolean hasPermission(Long userId, String permission);
/**
* 检查当前用户是否含任意权限
*
* @param userId userId
* @param permissions permissions
* @return 是否包含
*/
boolean hasAnyPermission(Long userId, List<String> permissions);
}

View File

@@ -11,6 +11,22 @@ import com.orion.visor.module.infra.entity.dto.user.SystemUserDTO;
*/
public interface SystemUserApi {
/**
* 通过 id 查询用户名
*
* @param id id
* @return username
*/
String getUsernameById(Long id);
/**
* 通过 id 查询花名
*
* @param id id
* @return nickname
*/
String getNicknameById(Long id);
/**
* 通过 id 查询用户
*
@@ -19,12 +35,4 @@ public interface SystemUserApi {
*/
SystemUserDTO getUserById(Long id);
/**
* 用户是否为管理员用户
*
* @param id id
* @return isAdmin
*/
boolean isAdminUser(Long id);
}

View File

@@ -0,0 +1,45 @@
package com.orion.visor.module.infra.entity.dto.user;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 用户状态检查 业务对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/8/14 21:52
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "SystemUserAuthDTO", description = "用户认证 业务对象")
public class SystemUserAuthDTO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "id")
private Long id;
@Schema(description = "用户名")
private String username;
@Schema(description = "花名")
private String nickname;
@Schema(description = "密码是否正确")
private Boolean passRight;
@Schema(description = "认证是否通过")
private Boolean authed;
@Schema(description = "错误信息")
private String errorMessage;
}

View File

@@ -0,0 +1,51 @@
package com.orion.visor.module.infra.api.impl;
import com.orion.visor.framework.common.constant.ErrorMessage;
import com.orion.visor.framework.common.utils.Valid;
import com.orion.visor.module.infra.api.AuthenticationApi;
import com.orion.visor.module.infra.entity.domain.SystemUserDO;
import com.orion.visor.module.infra.entity.dto.user.SystemUserAuthDTO;
import com.orion.visor.module.infra.service.AuthenticationService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* 认证服务实现
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/8/14 21:37
*/
@Slf4j
@Service
public class AuthenticationApiImpl implements AuthenticationApi {
@Resource
private AuthenticationService authenticationService;
@Override
public SystemUserAuthDTO authByPassword(String username, String password, boolean addFailedCount) {
SystemUserAuthDTO result = new SystemUserAuthDTO();
try {
// 登录预检
SystemUserDO user = authenticationService.preCheckLogin(username, password);
result.setId(user.getId());
result.setUsername(user.getUsername());
result.setNickname(user.getNickname());
// 检查用户密码
boolean passRight = authenticationService.checkUserPassword(user, password, addFailedCount);
result.setPassRight(passRight);
Valid.isTrue(passRight, ErrorMessage.USERNAME_PASSWORD_ERROR);
// 检查用户状态
authenticationService.checkUserStatus(user);
result.setAuthed(true);
} catch (Exception e) {
result.setAuthed(false);
result.setErrorMessage(e.getMessage());
}
return result;
}
}

View File

@@ -0,0 +1,48 @@
package com.orion.visor.module.infra.api.impl;
import com.orion.visor.module.infra.api.PermissionApi;
import com.orion.visor.module.infra.service.PermissionService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
/**
* 权限 对外服务类实现
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/8/19 15:25
*/
@Service
public class PermissionApiImpl implements PermissionApi {
@Resource
private PermissionService permissionService;
@Override
public boolean isAdminUser(Long id) {
return permissionService.isAdminUser(id);
}
@Override
public boolean hasRole(Long userId, String role) {
return permissionService.hasRole(userId, role);
}
@Override
public boolean hasAnyRole(Long userId, List<String> roles) {
return permissionService.hasAnyRole(userId, roles);
}
@Override
public boolean hasPermission(Long userId, String permission) {
return permissionService.hasPermission(userId, permission);
}
@Override
public boolean hasAnyPermission(Long userId, List<String> permissions) {
return permissionService.hasAnyPermission(userId, permissions);
}
}

View File

@@ -5,7 +5,6 @@ import com.orion.visor.module.infra.convert.SystemUserProviderConvert;
import com.orion.visor.module.infra.dao.SystemUserDAO;
import com.orion.visor.module.infra.entity.domain.SystemUserDO;
import com.orion.visor.module.infra.entity.dto.user.SystemUserDTO;
import com.orion.visor.module.infra.service.SystemUserService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@@ -23,8 +22,25 @@ public class SystemUserApiImpl implements SystemUserApi {
@Resource
private SystemUserDAO systemUserDAO;
@Resource
private SystemUserService systemUserService;
@Override
public String getUsernameById(Long id) {
return systemUserDAO.of()
.createWrapper()
.select(SystemUserDO::getUsername)
.eq(SystemUserDO::getId, id)
.then()
.getOne(SystemUserDO::getUsername);
}
@Override
public String getNicknameById(Long id) {
return systemUserDAO.of()
.createWrapper()
.select(SystemUserDO::getNickname)
.eq(SystemUserDO::getId, id)
.then()
.getOne(SystemUserDO::getNickname);
}
@Override
public SystemUserDTO getUserById(Long id) {
@@ -35,9 +51,4 @@ public class SystemUserApiImpl implements SystemUserApi {
return SystemUserProviderConvert.MAPPER.to(user);
}
@Override
public boolean isAdminUser(Long id) {
return systemUserService.isAdminUser(id);
}
}

View File

@@ -5,7 +5,7 @@ import com.orion.visor.framework.log.core.enums.IgnoreLogMode;
import com.orion.visor.framework.web.core.annotation.RestWrapper;
import com.orion.visor.module.infra.entity.vo.SystemMenuVO;
import com.orion.visor.module.infra.entity.vo.UserPermissionVO;
import com.orion.visor.module.infra.service.PermissionService;
import com.orion.visor.module.infra.service.UserPermissionService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
@@ -26,22 +26,23 @@ import java.util.List;
* @version 1.0.0
* @since 2023/7/14 11:20
*/
@Tag(name = "infra - 权限服务")
@Tag(name = "infra - 用户权限服务")
@Slf4j
@Validated
@RestWrapper
@RestController
@RequestMapping("/infra/permission")
public class PermissionController {
@RequestMapping("/infra/user-permission")
@SuppressWarnings({"ELValidationInJSP", "SpringElInspection"})
public class UserPermissionController {
@Resource
private PermissionService permissionService;
private UserPermissionService userPermissionService;
@PutMapping("/refresh-cache")
@Operation(summary = "刷新角色权限缓存")
@PreAuthorize("@ss.hasPermission('infra:system-menu:management:refresh-cache')")
public Boolean refreshCache() {
permissionService.initPermissionCache();
userPermissionService.initPermissionCache();
return true;
}
@@ -49,14 +50,14 @@ public class PermissionController {
@GetMapping("/menu")
@Operation(summary = "获取用户菜单")
public List<SystemMenuVO> getUserMenuList() {
return permissionService.getUserMenuList();
return userPermissionService.getUserMenuList();
}
@IgnoreLog(IgnoreLogMode.RET)
@GetMapping("/user")
@Operation(summary = "获取用户权限聚合信息")
public UserPermissionVO getUserPermission() {
return permissionService.getUserPermission();
return userPermissionService.getUserPermission();
}
}

View File

@@ -34,11 +34,22 @@ public interface SystemRoleDAO extends IMapper<SystemRoleDO> {
/**
* 通过 userId 和 roleCode 查询 roleId (检查用户是否包含某个角色)
*
* @param userId userId
* @param code code
* @param userId userId
* @param codeList codeList
* @return roleId
*/
Long getRoleIdByUserIdAndRoleCode(@Param("userId") Long userId, @Param("code") String code);
List<Long> getRoleIdByUserIdAndRoleCode(@Param("userId") Long userId,
@Param("codeList") List<String> codeList);
/**
* 通过 roleId 和 permission 查询 permission (检查角色是否包含某个权限)
*
* @param roleIdList roleIdList
* @param permissionList permissionList
* @return permission
*/
List<String> getPermissionByRoleIdAndPermission(@Param("roleIdList") List<Long> roleIdList,
@Param("permissionList") List<String> permissionList);
/**
* 查询用户角色

View File

@@ -31,6 +31,11 @@ public class AppAuthenticationConfig {
*/
private Integer maxRefreshCount;
/**
* 登录失败发送站内信阈值
*/
private Integer loginFailedSendThreshold;
/**
* 登录失败锁定次数
*/

View File

@@ -0,0 +1,53 @@
package com.orion.visor.module.infra.define.message;
import com.orion.visor.module.infra.define.SystemMessageDefine;
import com.orion.visor.module.infra.enums.MessageClassifyEnum;
import lombok.Getter;
/**
* 用户 系统消息定义
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/5/14 17:23
*/
@Getter
public enum SystemUserMessageDefine implements SystemMessageDefine {
/**
* 登录失败
*/
LOGIN_FAILED(MessageClassifyEnum.NOTICE,
"登录失败",
"您的账号在 <sb>${time}</sb> 登录系统时身份认证失败, 您的密码可能已经泄漏。如非本人操作请尽快修改密码。(<sb>${address} - ${location}</sb>)"),
;
SystemUserMessageDefine(MessageClassifyEnum classify, String title, String content) {
this.classify = classify;
this.type = this.name();
this.title = title;
this.content = content;
}
/**
* 消息分类
*/
private final MessageClassifyEnum classify;
/**
* 消息类型
*/
private final String type;
/**
* 标题
*/
private final String title;
/**
* 内容
*/
private final String content;
}

View File

@@ -8,7 +8,7 @@ import com.orion.visor.module.infra.entity.dto.LoginTokenDTO;
import com.orion.visor.module.infra.enums.LoginTokenStatusEnum;
import com.orion.visor.module.infra.enums.UserStatusEnum;
import com.orion.visor.module.infra.service.AuthenticationService;
import com.orion.visor.module.infra.service.PermissionService;
import com.orion.visor.module.infra.service.UserPermissionService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@@ -27,30 +27,30 @@ public class SecurityFrameworkServiceImpl implements SecurityFrameworkService {
private AuthenticationService authenticationService;
@Resource
private PermissionService permissionService;
private UserPermissionService userPermissionService;
@Override
public boolean hasPermission(String permission) {
// 检查是否有权限
return permissionService.hasPermission(permission);
return userPermissionService.hasPermission(permission);
}
@Override
public boolean hasAnyPermission(String... permissions) {
// 检查是否有权限
return permissionService.hasAnyPermission(permissions);
return userPermissionService.hasAnyPermission(permissions);
}
@Override
public boolean hasRole(String role) {
// 检查是否有角色
return permissionService.hasRole(role);
return userPermissionService.hasRole(role);
}
@Override
public boolean hasAnyRole(String... roles) {
// 检查是否有角色
return permissionService.hasAnyRole(roles);
return userPermissionService.hasAnyRole(roles);
}
@Override

View File

@@ -1,6 +1,7 @@
package com.orion.visor.module.infra.service;
import com.orion.visor.framework.common.security.LoginUser;
import com.orion.visor.module.infra.entity.domain.SystemUserDO;
import com.orion.visor.module.infra.entity.dto.LoginTokenDTO;
import com.orion.visor.module.infra.entity.request.user.UserLoginRequest;
import com.orion.visor.module.infra.entity.vo.UserLoginVO;
@@ -48,4 +49,30 @@ public interface AuthenticationService {
*/
LoginTokenDTO getLoginTokenInfo(String loginToken);
/**
* 登录预检查
*
* @param username username
* @param password password
* @return user
*/
SystemUserDO preCheckLogin(String username, String password);
/**
* 检查用户密码
*
* @param user user
* @param password password
* @param addFailedCount addFailedCount
* @return passRight
*/
boolean checkUserPassword(SystemUserDO user, String password, boolean addFailedCount);
/**
* 检查用户状态
*
* @param user user
*/
void checkUserStatus(SystemUserDO user);
}

View File

@@ -1,92 +1,58 @@
package com.orion.visor.module.infra.service;
import com.orion.visor.module.infra.entity.domain.SystemRoleDO;
import com.orion.visor.module.infra.entity.dto.SystemMenuCacheDTO;
import com.orion.visor.module.infra.entity.vo.SystemMenuVO;
import com.orion.visor.module.infra.entity.vo.UserPermissionVO;
import java.util.List;
import java.util.Map;
/**
* 权限服务
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/7/16 1:03
* @since 2024/8/19 15:29
*/
public interface PermissionService {
/**
* 获取 角色缓存
* 检测用户是否是为管理员
*
* @return cache
* @param userId userId
* @return 是否为管理员
*/
Map<Long, SystemRoleDO> getRoleCache();
boolean isAdminUser(Long userId);
/**
* 获取 菜单缓存 以作角色权限直接引用
* 检查当前用户是否含有此角色
*
* @return cache
*/
List<SystemMenuCacheDTO> getMenuCache();
/**
* 获取 角色菜单关联
*
* @return cache
*/
Map<Long, List<SystemMenuCacheDTO>> getRoleMenuCache();
/**
* 初始化权限缓存
*/
void initPermissionCache();
/**
* 检查当前用户是否含有此角色 (有效性判断)
*
* @param role role
* @param userId userId
* @param role role
* @return 是否包含
*/
boolean hasRole(String role);
boolean hasRole(Long userId, String role);
/**
* 检查当前用户是否含有任意角色 (有效性判断)
* 检查当前用户是否含有任意角色
*
* @param roles roles
* @param userId userId
* @param roles roles
* @return 是否包含
*/
boolean hasAnyRole(String... roles);
boolean hasAnyRole(Long userId, List<String> roles);
/**
* 检查当前用户是否含有此权限 (有效性判断)
* 检查当前用户是否含有此权限
*
* @param userId userId
* @param permission permission
* @return 是否包含
*/
boolean hasPermission(String permission);
boolean hasPermission(Long userId, String permission);
/**
* 检查当前用户是否含任意权限 (有效性判断)
* 检查当前用户是否含任意权限
*
* @param userId userId
* @param permissions permissions
* @return 是否包含
*/
boolean hasAnyPermission(String... permissions);
/**
* 获取用户菜单
*
* @return 菜单
*/
List<SystemMenuVO> getUserMenuList();
/**
* 获取用户权限
*
* @return 权限信息
*/
UserPermissionVO getUserPermission();
boolean hasAnyPermission(Long userId, List<String> permissions);
}

View File

@@ -92,12 +92,4 @@ public interface SystemUserService {
*/
void resetPassword(UserResetPasswordRequest request);
/**
* 检测用户是否是为管理员
*
* @param userId userId
* @return 是否为管理员
*/
boolean isAdminUser(Long userId);
}

View File

@@ -0,0 +1,92 @@
package com.orion.visor.module.infra.service;
import com.orion.visor.module.infra.entity.domain.SystemRoleDO;
import com.orion.visor.module.infra.entity.dto.SystemMenuCacheDTO;
import com.orion.visor.module.infra.entity.vo.SystemMenuVO;
import com.orion.visor.module.infra.entity.vo.UserPermissionVO;
import java.util.List;
import java.util.Map;
/**
* 用户权限服务
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/7/16 1:03
*/
public interface UserPermissionService {
/**
* 获取 角色缓存
*
* @return cache
*/
Map<Long, SystemRoleDO> getRoleCache();
/**
* 获取 菜单缓存 以作角色权限直接引用
*
* @return cache
*/
List<SystemMenuCacheDTO> getMenuCache();
/**
* 获取 角色菜单关联
*
* @return cache
*/
Map<Long, List<SystemMenuCacheDTO>> getRoleMenuCache();
/**
* 初始化权限缓存
*/
void initPermissionCache();
/**
* 检查当前用户是否含有此角色 (有效性判断)
*
* @param role role
* @return 是否包含
*/
boolean hasRole(String role);
/**
* 检查当前用户是否含有任意角色 (有效性判断)
*
* @param roles roles
* @return 是否包含
*/
boolean hasAnyRole(String... roles);
/**
* 检查当前用户是否含有此权限 (有效性判断)
*
* @param permission permission
* @return 是否包含
*/
boolean hasPermission(String permission);
/**
* 检查当前用户是否含任意权限 (有效性判断)
*
* @param permissions permissions
* @return 是否包含
*/
boolean hasAnyPermission(String... permissions);
/**
* 获取用户菜单
*
* @return 菜单
*/
List<SystemMenuVO> getUserMenuList();
/**
* 获取用户权限
*
* @return 权限信息
*/
UserPermissionVO getUserPermission();
}

View File

@@ -1,14 +1,17 @@
package com.orion.visor.module.infra.service.impl;
import com.alibaba.fastjson.JSON;
import com.orion.lang.annotation.Keep;
import com.orion.lang.define.wrapper.Pair;
import com.orion.lang.utils.Exceptions;
import com.orion.lang.utils.Strings;
import com.orion.lang.utils.collect.Lists;
import com.orion.lang.utils.crypto.Signatures;
import com.orion.lang.utils.time.Dates;
import com.orion.visor.framework.biz.operator.log.core.utils.OperatorLogs;
import com.orion.visor.framework.common.annotation.Keep;
import com.orion.visor.framework.common.constant.Const;
import com.orion.visor.framework.common.constant.ErrorMessage;
import com.orion.visor.framework.common.constant.ExtraFieldConst;
import com.orion.visor.framework.common.security.LoginUser;
import com.orion.visor.framework.common.security.UserRole;
import com.orion.visor.framework.common.utils.CryptoUtils;
@@ -17,30 +20,30 @@ import com.orion.visor.framework.common.utils.Valid;
import com.orion.visor.framework.redis.core.utils.RedisStrings;
import com.orion.visor.framework.redis.core.utils.RedisUtils;
import com.orion.visor.framework.security.core.utils.SecurityUtils;
import com.orion.visor.module.infra.api.SystemMessageApi;
import com.orion.visor.module.infra.convert.SystemUserConvert;
import com.orion.visor.module.infra.dao.SystemUserDAO;
import com.orion.visor.module.infra.dao.SystemUserRoleDAO;
import com.orion.visor.module.infra.define.cache.UserCacheKeyDefine;
import com.orion.visor.module.infra.define.config.AppAuthenticationConfig;
import com.orion.visor.module.infra.define.message.SystemUserMessageDefine;
import com.orion.visor.module.infra.entity.domain.SystemUserDO;
import com.orion.visor.module.infra.entity.dto.LoginTokenDTO;
import com.orion.visor.module.infra.entity.dto.LoginTokenIdentityDTO;
import com.orion.visor.module.infra.entity.dto.message.SystemMessageDTO;
import com.orion.visor.module.infra.entity.request.user.UserLoginRequest;
import com.orion.visor.module.infra.entity.vo.UserLoginVO;
import com.orion.visor.module.infra.enums.LoginTokenStatusEnum;
import com.orion.visor.module.infra.enums.UserStatusEnum;
import com.orion.visor.module.infra.service.AuthenticationService;
import com.orion.visor.module.infra.service.PermissionService;
import com.orion.visor.module.infra.service.UserPermissionService;
import com.orion.web.servlet.web.Servlets;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@@ -64,7 +67,10 @@ public class AuthenticationServiceImpl implements AuthenticationService {
private SystemUserRoleDAO systemUserRoleDAO;
@Resource
private PermissionService permissionService;
private UserPermissionService userPermissionService;
@Resource
private SystemMessageApi systemMessageApi;
@Keep
@Resource
@@ -72,39 +78,35 @@ public class AuthenticationServiceImpl implements AuthenticationService {
@Override
public UserLoginVO login(UserLoginRequest request, HttpServletRequest servletRequest) {
// 设置日志上下文的用户 否则登录失败不会记录日志
OperatorLogs.setUser(SystemUserConvert.MAPPER.toLoginUser(request));
// 登录前检查
this.preCheckLogin(request);
// 获取登录用户
SystemUserDO user = systemUserDAO.of()
.createWrapper()
.eq(SystemUserDO::getUsername, request.getUsername())
.then()
.getOne();
Valid.notNull(user, ErrorMessage.USERNAME_PASSWORD_ERROR);
// 重新设置日志上下文
OperatorLogs.setUser(SystemUserConvert.MAPPER.toLoginUser(user));
// 检查密码
boolean passwordCorrect = this.checkPassword(request, user);
Valid.isTrue(passwordCorrect, ErrorMessage.USERNAME_PASSWORD_ERROR);
// 检查用户状态
UserStatusEnum.checkUserStatus(user.getStatus());
// 设置上次登录时间
this.setLastLoginTime(user.getId());
// 删除用户缓存
this.deleteUserCache(user);
// 重设用户缓存
this.setUserCache(user);
// 获取登录信息
String remoteAddr = IpUtils.getRemoteAddr(servletRequest);
String location = IpUtils.getLocation(remoteAddr);
String userAgent = Servlets.getUserAgent(servletRequest);
// 设置日志上下文的用户 否则登录失败不会记录日志
OperatorLogs.setUser(SystemUserConvert.MAPPER.toLoginUser(request));
// 登录前检查
SystemUserDO user = this.preCheckLogin(request.getUsername(), request.getPassword());
// 重新设置日志上下文
OperatorLogs.setUser(SystemUserConvert.MAPPER.toLoginUser(user));
// 用户密码校验
boolean passRight = this.checkUserPassword(user, request.getPassword(), true);
// 发送站内信
this.sendLoginFailedErrorMessage(passRight, user, remoteAddr, location);
Valid.isTrue(passRight, ErrorMessage.USERNAME_PASSWORD_ERROR);
// 用户状态校验
this.checkUserStatus(user);
Long id = user.getId();
// 设置上次登录时间
this.setLastLoginTime(id);
// 删除用户缓存
this.deleteUserCache(user);
// 重设用户缓存
this.setUserCache(user);
long current = System.currentTimeMillis();
// 不允许多端登录
if (!appAuthenticationConfig.getAllowMultiDevice()) {
// 无效化其他缓存
this.invalidOtherDeviceToken(user.getId(), current, remoteAddr, location, userAgent);
this.invalidOtherDeviceToken(id, current, remoteAddr, location, userAgent);
}
// 生成 loginToken
String token = this.generatorLoginToken(user, current, remoteAddr, location, userAgent);
@@ -189,62 +191,83 @@ public class AuthenticationServiceImpl implements AuthenticationService {
return refresh;
}
/**
* 获取 token pair
*
* @param loginToken loginToken
* @return pair
*/
private Pair<Long, Long> getLoginTokenPair(String loginToken) {
if (loginToken == null) {
return null;
}
try {
String value = CryptoUtils.decryptBase62(loginToken);
String[] pair = value.split(":");
return Pair.of(Long.valueOf(pair[0]), Long.valueOf(pair[1]));
} catch (Exception e) {
return null;
}
}
/**
* 登录预检查
*
* @param request request
*/
private void preCheckLogin(UserLoginRequest request) {
@Override
public SystemUserDO preCheckLogin(String username, String password) {
// 检查密码长度是否正确 MD5 长度为 32
if (request.getPassword().length() != Const.MD5_LEN) {
if (password.length() != Const.MD5_LEN) {
throw Exceptions.argument(ErrorMessage.USERNAME_PASSWORD_ERROR);
}
// 检查登录失败次数
String failedCountKey = UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(request.getUsername());
String failedCountKey = UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(username);
String failedCount = redisTemplate.opsForValue().get(failedCountKey);
if (failedCount != null
&& Integer.parseInt(failedCount) >= appAuthenticationConfig.getLoginFailedLockCount()) {
throw Exceptions.argument(ErrorMessage.MAX_LOGIN_FAILED);
}
// 获取登录用户
SystemUserDO user = systemUserDAO.of()
.createWrapper()
.eq(SystemUserDO::getUsername, username)
.then()
.getOne();
Valid.notNull(user, ErrorMessage.USERNAME_PASSWORD_ERROR);
return user;
}
@Override
public boolean checkUserPassword(SystemUserDO user, String password, boolean addFailedCount) {
// 检查密码
boolean passRight = user.getPassword().equals(Signatures.md5(password));
if (!passRight && addFailedCount) {
// 刷新登录失败缓存
String failedCountKey = UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(user.getUsername());
redisTemplate.opsForValue().increment(failedCountKey);
RedisUtils.setExpire(failedCountKey, appAuthenticationConfig.getLoginFailedLockTime(), TimeUnit.MINUTES);
}
return passRight;
}
@Override
public void checkUserStatus(SystemUserDO user) {
// 检查用户状态
UserStatusEnum.checkUserStatus(user.getStatus());
}
/**
* 检查密码
* 发送登录失败错误消息
*
* @param request request
* @param user user
* @return 是否正确
* @param passRight passRight
* @param user user
* @param remoteAddr remoteAddr
* @param location location
*/
@SuppressWarnings("ALL")
private boolean checkPassword(UserLoginRequest request, SystemUserDO user) {
// 密码正确
if (user.getPassword().equals(Signatures.md5(request.getPassword()))) {
return true;
private void sendLoginFailedErrorMessage(boolean passRight, SystemUserDO user,
String remoteAddr, String location) {
if (passRight) {
return;
}
// 刷新登录失败缓存
String failedCountKey = UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(request.getUsername());
redisTemplate.opsForValue().increment(failedCountKey);
RedisUtils.setExpire(failedCountKey, appAuthenticationConfig.getLoginFailedLockTime(), TimeUnit.MINUTES);
return false;
String failedCountKey = UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(user.getUsername());
String failedCountStr = redisTemplate.opsForValue().get(failedCountKey);
if (failedCountStr == null || !Strings.isInteger(failedCountStr)) {
return;
}
// 直接用相等 因为只触发一次
if (!appAuthenticationConfig.getLoginFailedSendThreshold().equals(Integer.valueOf(failedCountStr))) {
return;
}
// 发送站内信
Map<String, Object> params = new HashMap<>();
params.put(ExtraFieldConst.ADDRESS, remoteAddr);
params.put(ExtraFieldConst.LOCATION, location);
params.put(ExtraFieldConst.TIME, Dates.current());
SystemMessageDTO message = SystemMessageDTO.builder()
.receiverId(user.getId())
.receiverUsername(user.getUsername())
.relKey(user.getUsername())
.params(params)
.build();
// 发送
systemMessageApi.create(SystemUserMessageDefine.LOGIN_FAILED, message);
}
/**
@@ -273,6 +296,25 @@ public class AuthenticationServiceImpl implements AuthenticationService {
redisTemplate.delete(Lists.of(userInfoKey, loginFailedCountKey));
}
/**
* 获取 token pair
*
* @param loginToken loginToken
* @return pair
*/
private Pair<Long, Long> getLoginTokenPair(String loginToken) {
if (loginToken == null) {
return null;
}
try {
String value = CryptoUtils.decryptBase62(loginToken);
String[] pair = value.split(":");
return Pair.of(Long.valueOf(pair[0]), Long.valueOf(pair[1]));
} catch (Exception e) {
return null;
}
}
/**
* 设置用户缓存
*
@@ -283,7 +325,7 @@ public class AuthenticationServiceImpl implements AuthenticationService {
Long id = user.getId();
// 查询用户角色
List<Long> roleIds = systemUserRoleDAO.selectRoleIdByUserId(id);
List<UserRole> roleList = permissionService.getRoleCache()
List<UserRole> roleList = userPermissionService.getRoleCache()
.values()
.stream()
.filter(s -> roleIds.contains(s.getId()))

View File

@@ -1,305 +1,67 @@
package com.orion.visor.module.infra.service.impl;
import com.orion.lang.utils.Arrays1;
import com.orion.lang.utils.collect.Lists;
import com.orion.lang.utils.collect.Maps;
import com.orion.visor.framework.common.constant.Const;
import com.orion.visor.framework.common.security.LoginUser;
import com.orion.visor.framework.common.security.UserRole;
import com.orion.visor.framework.security.core.utils.SecurityUtils;
import com.orion.visor.module.infra.convert.SystemMenuConvert;
import com.orion.visor.module.infra.convert.SystemUserConvert;
import com.orion.visor.module.infra.dao.SystemMenuDAO;
import com.orion.visor.module.infra.dao.SystemRoleDAO;
import com.orion.visor.module.infra.dao.SystemRoleMenuDAO;
import com.orion.visor.module.infra.define.RoleDefine;
import com.orion.visor.module.infra.entity.domain.SystemMenuDO;
import com.orion.visor.module.infra.entity.domain.SystemRoleDO;
import com.orion.visor.module.infra.entity.domain.SystemRoleMenuDO;
import com.orion.visor.module.infra.entity.dto.SystemMenuCacheDTO;
import com.orion.visor.module.infra.entity.vo.SystemMenuVO;
import com.orion.visor.module.infra.entity.vo.UserCollectInfoVO;
import com.orion.visor.module.infra.entity.vo.UserPermissionVO;
import com.orion.visor.module.infra.enums.MenuStatusEnum;
import com.orion.visor.module.infra.enums.MenuTypeEnum;
import com.orion.visor.module.infra.enums.PreferenceTypeEnum;
import com.orion.visor.module.infra.enums.RoleStatusEnum;
import com.orion.visor.module.infra.service.PermissionService;
import com.orion.visor.module.infra.service.PreferenceService;
import com.orion.visor.module.infra.service.SystemMenuService;
import com.orion.visor.module.infra.service.TipsService;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.Future;
import java.util.function.Function;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 权限服务
* 权限 服务实现类
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/7/16 1:05
* @since 2024/8/19 15:29
*/
@Slf4j
@Service
public class PermissionServiceImpl implements PermissionService {
@Getter
private final Map<Long, SystemRoleDO> roleCache = new HashMap<>();
@Getter
private final List<SystemMenuCacheDTO> menuCache = new ArrayList<>();
@Getter
private final Map<Long, List<SystemMenuCacheDTO>> roleMenuCache = new HashMap<>();
@Resource
private SystemRoleDAO systemRoleDAO;
@Resource
private SystemMenuDAO systemMenuDAO;
@Resource
private SystemRoleMenuDAO systemRoleMenuDAO;
@Resource
private SystemMenuService systemMenuService;
@Resource
private PreferenceService preferenceService;
@Resource
private TipsService tipsService;
@PostConstruct
@Override
public void initPermissionCache() {
long start = System.currentTimeMillis();
log.info("initPermissionCache-start");
roleCache.clear();
menuCache.clear();
roleMenuCache.clear();
// 加载所有角色
List<SystemRoleDO> roles = systemRoleDAO.selectList(null);
for (SystemRoleDO role : roles) {
roleCache.put(role.getId(), role);
}
// 加载所有菜单信息
List<SystemMenuDO> menuList = systemMenuDAO.selectList(null);
List<SystemMenuCacheDTO> menus = SystemMenuConvert.MAPPER.toCache(menuList);
Map<Long, SystemMenuCacheDTO> menuMapping = menus.stream()
.collect(Collectors.toMap(SystemMenuCacheDTO::getId, Function.identity()));
menuCache.addAll(menus);
// 查询所有角色菜单
systemRoleMenuDAO.selectList(null)
.stream()
.collect(Collectors.groupingBy(SystemRoleMenuDO::getRoleId,
Collectors.mapping(SystemRoleMenuDO::getMenuId, Collectors.toList())))
.forEach((roleId, menuIdList) -> {
// 获取菜单引用
List<SystemMenuCacheDTO> roleMenus = menuIdList.stream()
.map(menuMapping::get)
.filter(Objects::nonNull)
.collect(Collectors.toList());
// 获取角色引用
roleMenuCache.put(roleId, roleMenus);
});
log.info("initPermissionCache-end used: {}ms", System.currentTimeMillis() - start);
public boolean isAdminUser(Long userId) {
return this.hasAnyRole(userId, Lists.of(RoleDefine.ADMIN_CODE));
}
@Override
public boolean hasRole(String role) {
// 获取用户角色
Map<Long, String> roles = this.getUserEnabledRoles();
public boolean hasRole(Long userId, String role) {
return this.hasAnyRole(userId, Lists.of(role));
}
@Override
public boolean hasAnyRole(Long userId, List<String> roles) {
return !systemRoleDAO.getRoleIdByUserIdAndRoleCode(userId, roles).isEmpty();
}
@Override
public boolean hasPermission(Long userId, String permission) {
return this.hasAnyPermission(userId, Lists.singleton(permission));
}
@Override
public boolean hasAnyPermission(Long userId, List<String> permissions) {
// 查询用户角色
List<SystemRoleDO> roles = systemRoleDAO.selectRoleByUserId(userId);
roles.removeIf(s -> !RoleStatusEnum.ENABLED.getStatus().equals(s.getStatus()));
if (roles.isEmpty()) {
return false;
}
// 检查是否为超级管理员或包含此角色
return RoleDefine.containsAdmin(roles.values()) || roles.containsValue(role);
}
@Override
public boolean hasAnyRole(String... roles) {
if (Arrays1.isEmpty(roles)) {
// 判断是否为 admin
boolean isAdmin = roles.stream().anyMatch(s -> s.getCode().equals(RoleDefine.ADMIN_CODE));
if (isAdmin) {
return true;
}
// 获取用户角色
Map<Long, String> enableRoles = this.getUserEnabledRoles();
if (enableRoles.isEmpty()) {
return false;
}
// 检查是否为超级管理员 || 有此角色
return RoleDefine.containsAdmin(enableRoles.values())
|| Arrays.stream(roles).anyMatch(enableRoles::containsValue);
}
@Override
public boolean hasPermission(String permission) {
// 获取用户角色
Map<Long, String> roles = this.getUserEnabledRoles();
if (roles.isEmpty()) {
return false;
}
// 检查是否为超级管理员
if (RoleDefine.containsAdmin(roles.values())) {
return true;
}
// 检查普通角色是否有此权限
return roles.keySet()
.stream()
.anyMatch(s -> this.checkRoleHasPermission(s, permission));
}
@Override
public boolean hasAnyPermission(String... permissions) {
if (Arrays1.isEmpty(permissions)) {
return true;
}
// 获取用户角色
Map<Long, String> roles = this.getUserEnabledRoles();
if (roles.isEmpty()) {
return false;
}
// 检查是否为超级管理员
if (RoleDefine.containsAdmin(roles.values())) {
return true;
}
// 检查用户角色是否包含权限
return Arrays.stream(permissions)
.anyMatch(perm -> roles.keySet()
.stream()
.anyMatch(s -> this.checkRoleHasPermission(s, perm)));
}
@Override
public List<SystemMenuVO> getUserMenuList() {
// 获取用户角色
Map<Long, String> roles = this.getUserEnabledRoles();
if (roles.isEmpty()) {
return Lists.empty();
}
// 查询角色菜单
Stream<SystemMenuCacheDTO> mergeStream;
if (RoleDefine.containsAdmin(roles.values())) {
// 管理员拥有全部菜单
mergeStream = menuCache.stream();
} else {
// 当前用户所适配的角色菜单
mergeStream = roles.keySet()
.stream()
.map(roleMenuCache::get)
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.distinct();
}
// 状态过滤
List<SystemMenuVO> menus = mergeStream
.filter(s -> MenuStatusEnum.ENABLED.getStatus().equals(s.getStatus()))
.filter(s -> !MenuTypeEnum.FUNCTION.getType().equals(s.getType()))
.map(SystemMenuConvert.MAPPER::to)
List<Long> roleIdList = roles.stream()
.map(SystemRoleDO::getId)
.collect(Collectors.toList());
// 构建菜单树
return systemMenuService.buildSystemMenuTree(menus);
}
@SneakyThrows
@Override
public UserPermissionVO getUserPermission() {
// 获取用户信息
UserCollectInfoVO user = SystemUserConvert.MAPPER.toCollectInfo(SecurityUtils.getLoginUser());
Long id = user.getId();
// 获取用户系统偏好
Future<Map<String, Object>> systemPreference = preferenceService.getPreferenceAsync(id, PreferenceTypeEnum.SYSTEM);
// 获取用户角色
Map<Long, String> roles = this.getUserEnabledRoles();
// 获取用户权限
List<String> permissions;
if (roles.isEmpty()) {
permissions = Lists.empty();
} else {
if (RoleDefine.containsAdmin(roles.values())) {
// 管理员拥有全部权限
permissions = Lists.of(Const.ASTERISK);
} else {
// 当前用户所适配的角色的权限
permissions = roles.keySet()
.stream()
.map(roleMenuCache::get)
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.filter(s -> MenuStatusEnum.ENABLED.getStatus().equals(s.getStatus()))
.map(SystemMenuCacheDTO::getPermission)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
}
}
// 设置已提示的 key
user.setTippedKeys(tipsService.getTippedKeys());
// 获取异步结果
user.setSystemPreference(systemPreference.get());
// 组装数据
return UserPermissionVO.builder()
.user(user)
.roles(roles.values())
.permissions(permissions)
.build();
}
/**
* 检查角色是否有权限
*
* @param roleId roleId
* @param permission permission
* @return 是否有权限
*/
private boolean checkRoleHasPermission(Long roleId, String permission) {
// 获取角色权限列表
List<SystemMenuCacheDTO> menus = roleMenuCache.get(roleId);
if (Lists.isEmpty(menus)) {
return false;
}
// 检查是否有此权限
return menus.stream()
.filter(s -> MenuStatusEnum.ENABLED.getStatus().equals(s.getStatus()))
.map(SystemMenuCacheDTO::getPermission)
.filter(Objects::nonNull)
.anyMatch(permission::equals);
}
/**
* 获取用户启用的角色
*
* @return roles
*/
private Map<Long, String> getUserEnabledRoles() {
// 获取当前用户角色
List<UserRole> userRoles = Optional.ofNullable(SecurityUtils.getLoginUser())
.map(LoginUser::getRoles)
.orElse(Lists.empty());
if (Lists.isEmpty(userRoles)) {
return Maps.empty();
}
// 获取角色编码
Map<Long, String> roles = userRoles.stream()
.map(UserRole::getId)
.map(roleCache::get)
.filter(Objects::nonNull)
// 过滤未启用的角色
.filter(r -> RoleStatusEnum.ENABLED.getStatus().equals(r.getStatus()))
.collect(Collectors.toMap(SystemRoleDO::getId, SystemRoleDO::getCode));
if (Maps.isEmpty(roles)) {
return Maps.empty();
}
return roles;
return !systemRoleDAO.getPermissionByRoleIdAndPermission(roleIdList, permissions).isEmpty();
}
}

View File

@@ -21,7 +21,7 @@ import com.orion.visor.module.infra.entity.vo.SystemMenuVO;
import com.orion.visor.module.infra.enums.MenuStatusEnum;
import com.orion.visor.module.infra.enums.MenuTypeEnum;
import com.orion.visor.module.infra.enums.MenuVisibleEnum;
import com.orion.visor.module.infra.service.PermissionService;
import com.orion.visor.module.infra.service.UserPermissionService;
import com.orion.visor.module.infra.service.SystemMenuService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
@@ -51,7 +51,7 @@ public class SystemMenuServiceImpl implements SystemMenuService {
private SystemRoleMenuDAO systemRoleMenuDAO;
@Resource
private PermissionService permissionService;
private UserPermissionService userPermissionService;
@Override
public Long createSystemMenu(SystemMenuCreateRequest request) {
@@ -68,7 +68,7 @@ public class SystemMenuServiceImpl implements SystemMenuService {
int effect = systemMenuDAO.insert(record);
log.info("SystemMenuService-createSystemMenu effect: {}, record: {}", effect, JSON.toJSONString(record));
// 保存至缓存
List<SystemMenuCacheDTO> menuCache = permissionService.getMenuCache();
List<SystemMenuCacheDTO> menuCache = userPermissionService.getMenuCache();
menuCache.add(SystemMenuConvert.MAPPER.toCache(record));
return record.getId();
}
@@ -89,7 +89,7 @@ public class SystemMenuServiceImpl implements SystemMenuService {
// 重新查询转换为缓存
SystemMenuCacheDTO cache = SystemMenuConvert.MAPPER.toCache(systemMenuDAO.selectById(id));
// 获取原始缓存
permissionService.getMenuCache()
userPermissionService.getMenuCache()
.stream()
.filter(s -> s.getId().equals(id))
.findFirst()
@@ -115,7 +115,7 @@ public class SystemMenuServiceImpl implements SystemMenuService {
Integer type = request.getType();
Integer status = request.getStatus();
// 从缓存中查询
List<SystemMenuVO> menus = permissionService.getMenuCache()
List<SystemMenuVO> menus = userPermissionService.getMenuCache()
.stream()
.filter(s -> Strings.isBlank(name) || s.getName().contains(name))
.filter(s -> type == null || s.getType().equals(type))
@@ -197,7 +197,7 @@ public class SystemMenuServiceImpl implements SystemMenuService {
// 添加日志参数
OperatorLogs.add(OperatorLogs.NAME, record.getName());
// 从缓存中查询
List<SystemMenuCacheDTO> cache = permissionService.getMenuCache();
List<SystemMenuCacheDTO> cache = userPermissionService.getMenuCache();
// 获取要更新的id
List<Long> updateIdList = this.getChildrenIdList(id, cache, record.getType());
// 修改状态
@@ -229,7 +229,7 @@ public class SystemMenuServiceImpl implements SystemMenuService {
// 添加日志参数
OperatorLogs.add(OperatorLogs.NAME, record.getName());
// 从缓存中查询
List<SystemMenuCacheDTO> cache = permissionService.getMenuCache();
List<SystemMenuCacheDTO> cache = userPermissionService.getMenuCache();
// 获取要删除的id
List<Long> deletedIdList = this.getChildrenIdList(id, cache, record.getType());
// 删除菜单
@@ -239,7 +239,7 @@ public class SystemMenuServiceImpl implements SystemMenuService {
// 删除菜单缓存
cache.removeIf(s -> deletedIdList.contains(s.getId()));
// 删除引用缓存
permissionService.getRoleMenuCache()
userPermissionService.getRoleMenuCache()
.values()
.forEach(roleMenus -> roleMenus.removeIf(s -> deletedIdList.contains(s.getId())));
log.info("SystemMenuService-deleteSystemMenu deletedIdList: {}, effect: {}", deletedIdList, effect);

View File

@@ -16,7 +16,7 @@ import com.orion.visor.module.infra.entity.domain.SystemRoleDO;
import com.orion.visor.module.infra.entity.domain.SystemRoleMenuDO;
import com.orion.visor.module.infra.entity.dto.SystemMenuCacheDTO;
import com.orion.visor.module.infra.entity.request.menu.SystemRoleGrantMenuRequest;
import com.orion.visor.module.infra.service.PermissionService;
import com.orion.visor.module.infra.service.UserPermissionService;
import com.orion.visor.module.infra.service.SystemRoleMenuService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -49,7 +49,7 @@ public class SystemRoleMenuServiceImpl implements SystemRoleMenuService {
private SystemRoleMenuDAO systemRoleMenuDAO;
@Resource
private PermissionService permissionService;
private UserPermissionService userPermissionService;
@Override
@Transactional(rollbackFor = Exception.class)
@@ -104,7 +104,7 @@ public class SystemRoleMenuServiceImpl implements SystemRoleMenuService {
effect += insertMenuIdList.size();
}
// 更新缓存
Map<Long, List<SystemMenuCacheDTO>> cache = permissionService.getRoleMenuCache();
Map<Long, List<SystemMenuCacheDTO>> cache = userPermissionService.getRoleMenuCache();
List<SystemMenuCacheDTO> roleCache = cache.computeIfAbsent(roleId, s -> new ArrayList<>());
roleCache.clear();
roleCache.addAll(SystemMenuConvert.MAPPER.toCache(menuList));

View File

@@ -20,7 +20,7 @@ import com.orion.visor.module.infra.entity.request.role.SystemRoleUpdateRequest;
import com.orion.visor.module.infra.entity.vo.SystemRoleVO;
import com.orion.visor.module.infra.enums.RoleStatusEnum;
import com.orion.visor.module.infra.service.DataPermissionService;
import com.orion.visor.module.infra.service.PermissionService;
import com.orion.visor.module.infra.service.UserPermissionService;
import com.orion.visor.module.infra.service.SystemRoleService;
import com.orion.visor.module.infra.service.SystemUserRoleService;
import lombok.extern.slf4j.Slf4j;
@@ -51,7 +51,7 @@ public class SystemRoleServiceImpl implements SystemRoleService {
private SystemRoleMenuDAO systemRoleMenuDAO;
@Resource
private PermissionService permissionService;
private UserPermissionService userPermissionService;
@Resource
private SystemUserRoleService systemUserRoleService;
@@ -72,7 +72,7 @@ public class SystemRoleServiceImpl implements SystemRoleService {
int effect = systemRoleDAO.insert(record);
log.info("SystemRoleService-createSystemRole effect: {}, domain: {}", effect, JSON.toJSONString(record));
// 设置到缓存
permissionService.getRoleCache().put(record.getId(), record);
userPermissionService.getRoleCache().put(record.getId(), record);
return record.getId();
}
@@ -92,7 +92,7 @@ public class SystemRoleServiceImpl implements SystemRoleService {
int effect = systemRoleDAO.updateById(updateRecord);
log.info("SystemRoleService-updateSystemRoleById effect: {}, updateRecord: {}", effect, JSON.toJSONString(updateRecord));
// 设置到缓存
SystemRoleDO roleCache = permissionService.getRoleCache().get(id);
SystemRoleDO roleCache = userPermissionService.getRoleCache().get(id);
roleCache.setName(updateRecord.getName());
return effect;
}
@@ -117,7 +117,7 @@ public class SystemRoleServiceImpl implements SystemRoleService {
int effect = systemRoleDAO.updateById(updateRecord);
log.info("SystemRoleService-updateRoleStatus effect: {}, updateRecord: {}", effect, JSON.toJSONString(updateRecord));
// 修改本地缓存状态
SystemRoleDO roleCache = permissionService.getRoleCache().get(id);
SystemRoleDO roleCache = userPermissionService.getRoleCache().get(id);
roleCache.setStatus(status);
// 删除数据权限缓存
dataPermissionService.clearRoleCache(id);
@@ -180,9 +180,9 @@ public class SystemRoleServiceImpl implements SystemRoleService {
// 删除角色菜单关联
effect += systemRoleMenuDAO.deleteByRoleId(id);
// 删除角色缓存
permissionService.getRoleCache().remove(id);
userPermissionService.getRoleCache().remove(id);
// 删除菜单缓存
permissionService.getRoleMenuCache().remove(id);
userPermissionService.getRoleMenuCache().remove(id);
// 删除用户缓存中的角色
systemUserRoleService.deleteUserCacheRoleAsync(id, userIdList);
// 删除数据权限缓存

View File

@@ -25,7 +25,6 @@ import com.orion.visor.module.infra.dao.OperatorLogDAO;
import com.orion.visor.module.infra.dao.SystemRoleDAO;
import com.orion.visor.module.infra.dao.SystemUserDAO;
import com.orion.visor.module.infra.dao.SystemUserRoleDAO;
import com.orion.visor.module.infra.define.RoleDefine;
import com.orion.visor.module.infra.define.cache.TipsCacheKeyDefine;
import com.orion.visor.module.infra.define.cache.UserCacheKeyDefine;
import com.orion.visor.module.infra.define.config.AppAuthenticationConfig;
@@ -302,11 +301,6 @@ public class SystemUserServiceImpl implements SystemUserService {
}
}
@Override
public boolean isAdminUser(Long userId) {
return systemRoleDAO.getRoleIdByUserIdAndRoleCode(userId, RoleDefine.ADMIN_CODE) != null;
}
/**
* 检查用户名否存在
*

View File

@@ -0,0 +1,305 @@
package com.orion.visor.module.infra.service.impl;
import com.orion.lang.utils.Arrays1;
import com.orion.lang.utils.collect.Lists;
import com.orion.lang.utils.collect.Maps;
import com.orion.visor.framework.common.constant.Const;
import com.orion.visor.framework.common.security.LoginUser;
import com.orion.visor.framework.common.security.UserRole;
import com.orion.visor.framework.security.core.utils.SecurityUtils;
import com.orion.visor.module.infra.convert.SystemMenuConvert;
import com.orion.visor.module.infra.convert.SystemUserConvert;
import com.orion.visor.module.infra.dao.SystemMenuDAO;
import com.orion.visor.module.infra.dao.SystemRoleDAO;
import com.orion.visor.module.infra.dao.SystemRoleMenuDAO;
import com.orion.visor.module.infra.define.RoleDefine;
import com.orion.visor.module.infra.entity.domain.SystemMenuDO;
import com.orion.visor.module.infra.entity.domain.SystemRoleDO;
import com.orion.visor.module.infra.entity.domain.SystemRoleMenuDO;
import com.orion.visor.module.infra.entity.dto.SystemMenuCacheDTO;
import com.orion.visor.module.infra.entity.vo.SystemMenuVO;
import com.orion.visor.module.infra.entity.vo.UserCollectInfoVO;
import com.orion.visor.module.infra.entity.vo.UserPermissionVO;
import com.orion.visor.module.infra.enums.MenuStatusEnum;
import com.orion.visor.module.infra.enums.MenuTypeEnum;
import com.orion.visor.module.infra.enums.PreferenceTypeEnum;
import com.orion.visor.module.infra.enums.RoleStatusEnum;
import com.orion.visor.module.infra.service.PreferenceService;
import com.orion.visor.module.infra.service.SystemMenuService;
import com.orion.visor.module.infra.service.TipsService;
import com.orion.visor.module.infra.service.UserPermissionService;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.Future;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 用户权限服务
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/7/16 1:05
*/
@Slf4j
@Service
public class UserPermissionServiceImpl implements UserPermissionService {
@Getter
private final Map<Long, SystemRoleDO> roleCache = new HashMap<>();
@Getter
private final List<SystemMenuCacheDTO> menuCache = new ArrayList<>();
@Getter
private final Map<Long, List<SystemMenuCacheDTO>> roleMenuCache = new HashMap<>();
@Resource
private SystemRoleDAO systemRoleDAO;
@Resource
private SystemMenuDAO systemMenuDAO;
@Resource
private SystemRoleMenuDAO systemRoleMenuDAO;
@Resource
private SystemMenuService systemMenuService;
@Resource
private PreferenceService preferenceService;
@Resource
private TipsService tipsService;
@PostConstruct
@Override
public void initPermissionCache() {
long start = System.currentTimeMillis();
log.info("initPermissionCache-start");
roleCache.clear();
menuCache.clear();
roleMenuCache.clear();
// 加载所有角色
List<SystemRoleDO> roles = systemRoleDAO.selectList(null);
for (SystemRoleDO role : roles) {
roleCache.put(role.getId(), role);
}
// 加载所有菜单信息
List<SystemMenuDO> menuList = systemMenuDAO.selectList(null);
List<SystemMenuCacheDTO> menus = SystemMenuConvert.MAPPER.toCache(menuList);
Map<Long, SystemMenuCacheDTO> menuMapping = menus.stream()
.collect(Collectors.toMap(SystemMenuCacheDTO::getId, Function.identity()));
menuCache.addAll(menus);
// 查询所有角色菜单
systemRoleMenuDAO.selectList(null)
.stream()
.collect(Collectors.groupingBy(SystemRoleMenuDO::getRoleId,
Collectors.mapping(SystemRoleMenuDO::getMenuId, Collectors.toList())))
.forEach((roleId, menuIdList) -> {
// 获取菜单引用
List<SystemMenuCacheDTO> roleMenus = menuIdList.stream()
.map(menuMapping::get)
.filter(Objects::nonNull)
.collect(Collectors.toList());
// 获取角色引用
roleMenuCache.put(roleId, roleMenus);
});
log.info("initPermissionCache-end used: {}ms", System.currentTimeMillis() - start);
}
@Override
public boolean hasRole(String role) {
// 获取用户角色
Map<Long, String> roles = this.getUserEnabledRoles();
if (roles.isEmpty()) {
return false;
}
// 检查是否为超级管理员或包含此角色
return RoleDefine.containsAdmin(roles.values()) || roles.containsValue(role);
}
@Override
public boolean hasAnyRole(String... roles) {
if (Arrays1.isEmpty(roles)) {
return true;
}
// 获取用户角色
Map<Long, String> enableRoles = this.getUserEnabledRoles();
if (enableRoles.isEmpty()) {
return false;
}
// 检查是否为超级管理员 || 有此角色
return RoleDefine.containsAdmin(enableRoles.values())
|| Arrays.stream(roles).anyMatch(enableRoles::containsValue);
}
@Override
public boolean hasPermission(String permission) {
// 获取用户角色
Map<Long, String> roles = this.getUserEnabledRoles();
if (roles.isEmpty()) {
return false;
}
// 检查是否为超级管理员
if (RoleDefine.containsAdmin(roles.values())) {
return true;
}
// 检查普通角色是否有此权限
return roles.keySet()
.stream()
.anyMatch(s -> this.checkRoleHasPermission(s, permission));
}
@Override
public boolean hasAnyPermission(String... permissions) {
if (Arrays1.isEmpty(permissions)) {
return true;
}
// 获取用户角色
Map<Long, String> roles = this.getUserEnabledRoles();
if (roles.isEmpty()) {
return false;
}
// 检查是否为超级管理员
if (RoleDefine.containsAdmin(roles.values())) {
return true;
}
// 检查用户角色是否包含权限
return Arrays.stream(permissions)
.anyMatch(perm -> roles.keySet()
.stream()
.anyMatch(s -> this.checkRoleHasPermission(s, perm)));
}
@Override
public List<SystemMenuVO> getUserMenuList() {
// 获取用户角色
Map<Long, String> roles = this.getUserEnabledRoles();
if (roles.isEmpty()) {
return Lists.empty();
}
// 查询角色菜单
Stream<SystemMenuCacheDTO> mergeStream;
if (RoleDefine.containsAdmin(roles.values())) {
// 管理员拥有全部菜单
mergeStream = menuCache.stream();
} else {
// 当前用户所适配的角色菜单
mergeStream = roles.keySet()
.stream()
.map(roleMenuCache::get)
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.distinct();
}
// 状态过滤
List<SystemMenuVO> menus = mergeStream
.filter(s -> MenuStatusEnum.ENABLED.getStatus().equals(s.getStatus()))
.filter(s -> !MenuTypeEnum.FUNCTION.getType().equals(s.getType()))
.map(SystemMenuConvert.MAPPER::to)
.collect(Collectors.toList());
// 构建菜单树
return systemMenuService.buildSystemMenuTree(menus);
}
@SneakyThrows
@Override
public UserPermissionVO getUserPermission() {
// 获取用户信息
UserCollectInfoVO user = SystemUserConvert.MAPPER.toCollectInfo(SecurityUtils.getLoginUser());
Long id = user.getId();
// 获取用户系统偏好
Future<Map<String, Object>> systemPreference = preferenceService.getPreferenceAsync(id, PreferenceTypeEnum.SYSTEM);
// 获取用户角色
Map<Long, String> roles = this.getUserEnabledRoles();
// 获取用户权限
List<String> permissions;
if (roles.isEmpty()) {
permissions = Lists.empty();
} else {
if (RoleDefine.containsAdmin(roles.values())) {
// 管理员拥有全部权限
permissions = Lists.of(Const.ASTERISK);
} else {
// 当前用户所适配的角色的权限
permissions = roles.keySet()
.stream()
.map(roleMenuCache::get)
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.filter(s -> MenuStatusEnum.ENABLED.getStatus().equals(s.getStatus()))
.map(SystemMenuCacheDTO::getPermission)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
}
}
// 设置已提示的 key
user.setTippedKeys(tipsService.getTippedKeys());
// 获取异步结果
user.setSystemPreference(systemPreference.get());
// 组装数据
return UserPermissionVO.builder()
.user(user)
.roles(roles.values())
.permissions(permissions)
.build();
}
/**
* 检查角色是否有权限
*
* @param roleId roleId
* @param permission permission
* @return 是否有权限
*/
private boolean checkRoleHasPermission(Long roleId, String permission) {
// 获取角色权限列表
List<SystemMenuCacheDTO> menus = roleMenuCache.get(roleId);
if (Lists.isEmpty(menus)) {
return false;
}
// 检查是否有此权限
return menus.stream()
.filter(s -> MenuStatusEnum.ENABLED.getStatus().equals(s.getStatus()))
.map(SystemMenuCacheDTO::getPermission)
.filter(Objects::nonNull)
.anyMatch(permission::equals);
}
/**
* 获取用户启用的角色
*
* @return roles
*/
private Map<Long, String> getUserEnabledRoles() {
// 获取当前用户角色
List<UserRole> userRoles = Optional.ofNullable(SecurityUtils.getLoginUser())
.map(LoginUser::getRoles)
.orElse(Lists.empty());
if (Lists.isEmpty(userRoles)) {
return Maps.empty();
}
// 获取角色编码
Map<Long, String> roles = userRoles.stream()
.map(UserRole::getId)
.map(roleCache::get)
.filter(Objects::nonNull)
// 过滤未启用的角色
.filter(r -> RoleStatusEnum.ENABLED.getStatus().equals(r.getStatus()))
.collect(Collectors.toMap(SystemRoleDO::getId, SystemRoleDO::getCode));
if (Maps.isEmpty(roles)) {
return Maps.empty();
}
return roles;
}
}

View File

@@ -22,6 +22,11 @@
"type": "java.lang.Integer",
"description": "凭证续签最大次数."
},
{
"name": "app.authentication.loginFailedSendThreshold",
"type": "java.lang.Integer",
"description": "登录失败发送站内信阈值."
},
{
"name": "app.authentication.loginFailedLockCount",
"type": "java.lang.Integer",

View File

@@ -24,12 +24,42 @@
SELECT role_id
FROM system_user_role
WHERE user_id = #{userId}
AND deleted = 0
AND role_id IN (SELECT id FROM system_role WHERE CODE = #{code} AND deleted = 0) LIMIT 1
AND deleted = 0
AND role_id IN
(SELECT id
FROM system_role
WHERE deleted = 0
AND status = 1
AND code IN
<foreach collection="codeList" item="item" open="(" close=")" separator=",">
#{item}
</foreach>
)
</select>
<select id="getPermissionByRoleIdAndPermission" resultType="java.lang.String">
SELECT m.permission
FROM system_menu m
LEFT JOIN system_role_menu rm ON rm.menu_id = m.id
WHERE rm.deleted = 0
AND m.deleted = 0
AND m.type = 3
AND m.status = 1
<if test="permissionList != null and permissionList.size() > 0">
AND m.permission IN
<foreach collection="permissionList" item="item" open="(" close=")" separator=",">
#{item}
</foreach>
</if>
AND rm.role_id IN
<foreach collection="roleIdList" item="item" open="(" close=")" separator=",">
#{item}
</foreach>
</select>
<select id="selectRoleByUserId" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
SELECT
<include refid="Base_Column_List"/>
FROM system_role
WHERE deleted = 0
AND id IN (SELECT role_id FROM system_user_role WHERE user_id = #{userId} AND deleted = 0)