diff --git a/docs/about/change-log.md b/docs/about/change-log.md index fa57b6da..7e5fd687 100644 --- a/docs/about/change-log.md +++ b/docs/about/change-log.md @@ -2,7 +2,7 @@ ## v1.0.1 -`2024-03-` `release` +`2024-03-06` `release` 🐞 修复 用户操作日志条件重置后类型框数据不正常的问题 🩰 修改 主机连接日志 UI diff --git a/docs/docs-serve.cmd b/docs/docs-server.cmd similarity index 100% rename from docs/docs-serve.cmd rename to docs/docs-server.cmd diff --git a/docs/operator/host_audit.md b/docs/operator/host_audit.md index b298118e..329f194c 100644 --- a/docs/operator/host_audit.md +++ b/docs/operator/host_audit.md @@ -6,3 +6,9 @@ * 断开: 断开连接 * 删除: 删除连接记录 * 清理: 根据条件清理数据 + +### SFTP 操作日志 + +查看用户 SFTP 操作日志, 是从用户操作日志中过滤查询。 + +* 删除: 删除操作日志 diff --git a/docs/operator/user.md b/docs/operator/user.md index 5ea97732..d0297acf 100644 --- a/docs/operator/user.md +++ b/docs/operator/user.md @@ -25,3 +25,4 @@ 记录用户在系统内的操作日志。 * 详情: 查看操作的参数以及留痕信息 +* 清理: 根据条件清理数据 diff --git a/orion-ops-framework/orion-ops-spring-boot-starter-redis/src/main/java/com/orion/ops/framework/redis/core/utils/RedisUtils.java b/orion-ops-framework/orion-ops-spring-boot-starter-redis/src/main/java/com/orion/ops/framework/redis/core/utils/RedisUtils.java index 83d05cf4..b6e28d80 100644 --- a/orion-ops-framework/orion-ops-spring-boot-starter-redis/src/main/java/com/orion/ops/framework/redis/core/utils/RedisUtils.java +++ b/orion-ops-framework/orion-ops-spring-boot-starter-redis/src/main/java/com/orion/ops/framework/redis/core/utils/RedisUtils.java @@ -10,6 +10,7 @@ import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ScanOptions; import java.util.*; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** @@ -165,6 +166,18 @@ public class RedisUtils { } } + /** + * 设置过期时间 + * + * @param key key + * @param timeout timeout + * @param unit unit + */ + public static void setExpire(String key, long timeout, TimeUnit unit) { + // 设置过期时间 + redisTemplate.expire(key, timeout, unit); + } + public static void setRedisTemplate(RedisTemplate redisTemplate) { if (RedisUtils.redisTemplate != null) { // unmodified diff --git a/orion-ops-launch/src/main/resources/META-INF/spring-configuration-metadata.json b/orion-ops-launch/src/main/resources/META-INF/spring-configuration-metadata.json new file mode 100644 index 00000000..a4099a50 --- /dev/null +++ b/orion-ops-launch/src/main/resources/META-INF/spring-configuration-metadata.json @@ -0,0 +1,36 @@ +{ + "groups": [ + { + "name": "app.authentication", + "type": "com.orion.ops.module.infra.config.AppAuthenticationConfig", + "sourceType": "com.orion.ops.module.infra.config.AppAuthenticationConfig" + } + ], + "properties": [ + { + "name": "app.authentication.allowMultiDevice", + "type": "java.lang.Boolean", + "description": "是否允许多端登录." + }, + { + "name": "app.authentication.allowRefresh", + "type": "java.lang.Boolean", + "description": "是否允许凭证续签." + }, + { + "name": "app.authentication.maxRefreshCount", + "type": "java.lang.Integer", + "description": "凭证续签最大次数." + }, + { + "name": "app.authentication.loginFailedLockCount", + "type": "java.lang.Integer", + "description": "登录失败锁定次数." + }, + { + "name": "app.authentication.loginFailedLockTime", + "type": "java.lang.Integer", + "description": "登录失败锁定时间 (分)." + } + ] +} \ No newline at end of file diff --git a/orion-ops-launch/src/main/resources/application.yaml b/orion-ops-launch/src/main/resources/application.yaml index 21611fbd..11581757 100644 --- a/orion-ops-launch/src/main/resources/application.yaml +++ b/orion-ops-launch/src/main/resources/application.yaml @@ -118,6 +118,21 @@ logging: level: com.orion.ops.launch.controller.BootstrapController: INFO +# 应用配置 +app: + authentication: + # 是否允许多端登录 + allow-multi-device: true + # 是否允许凭证续签 + allow-refresh: true + # 凭证续签最大次数 + max-refresh-count: 3 + # 登录失败锁定次数 + login-failed-lock-count: 5 + # 登录失败锁定时间 (分) + login-failed-lock-time: 30 + +# orion framework config orion: # 版本 version: @revision@ @@ -151,7 +166,6 @@ orion: asset: group: "asset - 资产模块" path: "asset" - logging: # 全局日志打印 printer: diff --git a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/config/AppAuthenticationConfig.java b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/config/AppAuthenticationConfig.java new file mode 100644 index 00000000..1abc66d4 --- /dev/null +++ b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/config/AppAuthenticationConfig.java @@ -0,0 +1,44 @@ +package com.orion.ops.module.infra.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * 应用认证配置 + * + * @author Jiahang Li + * @version 1.0.0 + * @since 2024/3/5 18:26 + */ +@Data +@Component +@ConfigurationProperties("app.authentication") +public class AppAuthenticationConfig { + + /** + * 是否允许多端登录 + */ + private Boolean allowMultiDevice; + + /** + * 是否允许凭证续签 + */ + private Boolean allowRefresh; + + /** + * 凭证续签最大次数 + */ + private Integer maxRefreshCount; + + /** + * 登录失败锁定次数 + */ + private Integer loginFailedLockCount; + + /** + * 登录失败锁定时间 (分) + */ + private Integer loginFailedLockTime; + +} diff --git a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/define/cache/UserCacheKeyDefine.java b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/define/cache/UserCacheKeyDefine.java index d02ac7da..e095b658 100644 --- a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/define/cache/UserCacheKeyDefine.java +++ b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/define/cache/UserCacheKeyDefine.java @@ -38,7 +38,6 @@ public interface UserCacheKeyDefine { .desc("用户登录失败次数 ${username}") .type(Integer.class) .struct(RedisCacheStruct.STRING) - .timeout(3, TimeUnit.DAYS) .build(); CacheKeyDefine LOGIN_TOKEN = new CacheKeyBuilder() diff --git a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/service/AuthenticationService.java b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/service/AuthenticationService.java index b0589e0b..d23413e1 100644 --- a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/service/AuthenticationService.java +++ b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/service/AuthenticationService.java @@ -16,16 +16,6 @@ import javax.servlet.http.HttpServletRequest; */ public interface AuthenticationService { - // TODO 配置化 - // 允许多端登录 - boolean allowMultiDevice = true; - // 允许凭证续签 - boolean allowRefresh = true; - // 凭证续签最大次数 - int maxRefreshCount = 3; - // 失败锁定次数 - int maxFailedLoginCount = 5; - /** * 登录 * diff --git a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/service/impl/AuthenticationServiceImpl.java b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/service/impl/AuthenticationServiceImpl.java index c7fadf28..ac8a2530 100644 --- a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/service/impl/AuthenticationServiceImpl.java +++ b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/service/impl/AuthenticationServiceImpl.java @@ -16,6 +16,7 @@ import com.orion.ops.framework.common.utils.Valid; import com.orion.ops.framework.redis.core.utils.RedisStrings; import com.orion.ops.framework.redis.core.utils.RedisUtils; import com.orion.ops.framework.security.core.utils.SecurityUtils; +import com.orion.ops.module.infra.config.AppAuthenticationConfig; import com.orion.ops.module.infra.convert.SystemUserConvert; import com.orion.ops.module.infra.dao.SystemUserDAO; import com.orion.ops.module.infra.dao.SystemUserRoleDAO; @@ -39,6 +40,7 @@ import java.util.Date; import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** @@ -51,6 +53,9 @@ import java.util.stream.Collectors; @Service public class AuthenticationServiceImpl implements AuthenticationService { + @Resource + private AppAuthenticationConfig appAuthenticationConfig; + @Resource private SystemUserDAO systemUserDAO; @@ -95,7 +100,7 @@ public class AuthenticationServiceImpl implements AuthenticationService { String userAgent = Servlets.getUserAgent(servletRequest); long current = System.currentTimeMillis(); // 不允许多端登录 - if (!allowMultiDevice) { + if (!appAuthenticationConfig.getAllowMultiDevice()) { // 无效化其他缓存 this.invalidOtherDeviceToken(user.getId(), current, remoteAddr, location, userAgent); } @@ -157,7 +162,7 @@ public class AuthenticationServiceImpl implements AuthenticationService { return JSON.parseObject(loginCache, LoginTokenDTO.class); } // loginToken 不存在 需要查询 refreshToken - if (!allowRefresh) { + if (!appAuthenticationConfig.getAllowRefresh()) { return null; } String refreshKey = UserCacheKeyDefine.LOGIN_REFRESH.format(pair.getKey(), pair.getValue()); @@ -172,7 +177,7 @@ public class AuthenticationServiceImpl implements AuthenticationService { refresh.setRefreshCount(refreshCount); // 设置登录缓存 RedisStrings.setJson(loginKey, UserCacheKeyDefine.LOGIN_TOKEN, refresh); - if (refreshCount < maxRefreshCount) { + if (refreshCount < appAuthenticationConfig.getMaxRefreshCount()) { // 小于续签最大次数 则再次设置 refreshToken RedisStrings.setJson(refreshKey, UserCacheKeyDefine.LOGIN_REFRESH, refresh); } else { @@ -214,7 +219,8 @@ public class AuthenticationServiceImpl implements AuthenticationService { // 检查登录失败次数 String failedCountKey = UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(request.getUsername()); String failedCount = redisTemplate.opsForValue().get(failedCountKey); - if (failedCount != null && Integer.parseInt(failedCount) >= maxFailedLoginCount) { + if (failedCount != null + && Integer.parseInt(failedCount) >= appAuthenticationConfig.getLoginFailedLockCount()) { throw Exceptions.argument(ErrorMessage.MAX_LOGIN_FAILED); } } @@ -235,23 +241,23 @@ public class AuthenticationServiceImpl implements AuthenticationService { // 刷新登录失败缓存 String failedCountKey = UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(request.getUsername()); Long failedLoginCount = redisTemplate.opsForValue().increment(failedCountKey); - RedisUtils.setExpire(failedCountKey, UserCacheKeyDefine.LOGIN_FAILED_COUNT); - // 锁定用户 - if (failedLoginCount >= maxFailedLoginCount) { - // 更新用户表 - SystemUserDO updateUser = new SystemUserDO(); - updateUser.setId(user.getId()); - updateUser.setStatus(UserStatusEnum.LOCKED.getStatus()); - systemUserDAO.updateById(updateUser); - // 修改缓存状态 - String userInfoKey = UserCacheKeyDefine.USER_INFO.format(user.getId()); - String userInfoCache = redisTemplate.opsForValue().get(userInfoKey); - if (userInfoCache != null) { - LoginUser loginUser = JSON.parseObject(userInfoCache, LoginUser.class); - loginUser.setStatus(UserStatusEnum.LOCKED.getStatus()); - RedisStrings.setJson(userInfoKey, UserCacheKeyDefine.USER_INFO, loginUser); - } - } + RedisUtils.setExpire(failedCountKey, appAuthenticationConfig.getLoginFailedLockTime(), TimeUnit.MINUTES); + // // 锁定用户 + // if (failedLoginCount >= appAuthenticationConfig.getLoginFailedLockCount()) { + // // 更新用户表 + // SystemUserDO updateUser = new SystemUserDO(); + // updateUser.setId(user.getId()); + // updateUser.setStatus(UserStatusEnum.LOCKED.getStatus()); + // systemUserDAO.updateById(updateUser); + // // 修改缓存状态 + // String userInfoKey = UserCacheKeyDefine.USER_INFO.format(user.getId()); + // String userInfoCache = redisTemplate.opsForValue().get(userInfoKey); + // if (userInfoCache != null) { + // LoginUser loginUser = JSON.parseObject(userInfoCache, LoginUser.class); + // loginUser.setStatus(UserStatusEnum.LOCKED.getStatus()); + // RedisStrings.setJson(userInfoKey, UserCacheKeyDefine.USER_INFO, loginUser); + // } + // } return false; } @@ -337,7 +343,7 @@ public class AuthenticationServiceImpl implements AuthenticationService { } } // 删除续签信息 - if (allowRefresh) { + if (appAuthenticationConfig.getAllowRefresh()) { RedisUtils.scanKeysDelete(UserCacheKeyDefine.LOGIN_REFRESH.format(id, "*")); } } @@ -365,7 +371,7 @@ public class AuthenticationServiceImpl implements AuthenticationService { .build(); RedisStrings.setJson(loginKey, UserCacheKeyDefine.LOGIN_TOKEN, loginValue); // 生成 refreshToken - if (allowRefresh) { + if (appAuthenticationConfig.getAllowRefresh()) { String refreshKey = UserCacheKeyDefine.LOGIN_REFRESH.format(id, loginTime); RedisStrings.setJson(refreshKey, UserCacheKeyDefine.LOGIN_REFRESH, loginValue); } diff --git a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/service/impl/SystemUserServiceImpl.java b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/service/impl/SystemUserServiceImpl.java index 0c1b698c..9c79ec89 100644 --- a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/service/impl/SystemUserServiceImpl.java +++ b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/service/impl/SystemUserServiceImpl.java @@ -14,6 +14,7 @@ import com.orion.ops.framework.redis.core.utils.RedisStrings; import com.orion.ops.framework.redis.core.utils.RedisUtils; import com.orion.ops.framework.redis.core.utils.barrier.CacheBarriers; import com.orion.ops.framework.security.core.utils.SecurityUtils; +import com.orion.ops.module.infra.config.AppAuthenticationConfig; import com.orion.ops.module.infra.convert.SystemUserConvert; import com.orion.ops.module.infra.dao.OperatorLogDAO; import com.orion.ops.module.infra.dao.SystemRoleDAO; @@ -49,6 +50,9 @@ import java.util.stream.Collectors; @Service public class SystemUserServiceImpl implements SystemUserService { + @Resource + private AppAuthenticationConfig appAuthenticationConfig; + @Resource private SystemUserDAO systemUserDAO; @@ -274,7 +278,7 @@ public class SystemUserServiceImpl implements SystemUserService { // 删除登录缓存 RedisUtils.scanKeysDelete(UserCacheKeyDefine.LOGIN_TOKEN.format(id, "*")); // 删除续签信息 - if (AuthenticationService.allowRefresh) { + if (appAuthenticationConfig.getAllowRefresh()) { RedisUtils.scanKeysDelete(UserCacheKeyDefine.LOGIN_REFRESH.format(id, "*")); } }