From 95bae3f9b341cb134dc7ea7543599094793b17db Mon Sep 17 00:00:00 2001 From: ailanyin Date: Mon, 12 Jun 2023 22:49:55 +0800 Subject: [PATCH] first commit --- .../auth/service/AuthConfigApiService.java | 22 +++++++++ .../auth/config/SpringSecurityConfig.java | 26 ---------- .../auth/constants/SecurityConstant.java | 18 +------ .../service/impl/AuthConfigServiceImpl.java | 18 +++++++ .../common/base/constants/ConfigConstant.java | 15 ++++++ .../common/base/constants/TenantConstant.java | 10 ++++ .../common/base/context/BaseContext.java | 7 +++ .../service/impl/SysLoginServiceImpl.java | 48 ++++++++++++++++--- .../filters/DynamicDataSourceFilter.java | 9 +--- .../tenant/service/SysTenantService.java | 13 +++++ .../service/impl/SysTenantServiceImpl.java | 44 ++++++++++++++++- 11 files changed, 172 insertions(+), 58 deletions(-) diff --git a/qiaoba-apis/qiaoba-api-auth/src/main/java/com/qiaoba/api/auth/service/AuthConfigApiService.java b/qiaoba-apis/qiaoba-api-auth/src/main/java/com/qiaoba/api/auth/service/AuthConfigApiService.java index 5134ccf..54b72bb 100644 --- a/qiaoba-apis/qiaoba-api-auth/src/main/java/com/qiaoba/api/auth/service/AuthConfigApiService.java +++ b/qiaoba-apis/qiaoba-api-auth/src/main/java/com/qiaoba/api/auth/service/AuthConfigApiService.java @@ -30,8 +30,30 @@ public interface AuthConfigApiService { */ Boolean getRegisterConfig(); + /** + * 是否开启黑名单 + * + * @return 是/否 + */ + Boolean getBlacklistConfig(); + + /** + * 黑名单过期时间(拉黑时间), 单位:秒 + * + * @return 过期时间 + */ + Long getBlacklistExpireTime(); + + /** + * 获取最大允许错误次数 + * + * @return 最大允许错误次数 + */ + Integer getAllowMaxErrorCount(); + /** * 验证验证码 + * * @param code code * @param uuid uuid */ diff --git a/qiaoba-auth/src/main/java/com/qiaoba/auth/config/SpringSecurityConfig.java b/qiaoba-auth/src/main/java/com/qiaoba/auth/config/SpringSecurityConfig.java index 5715697..c297602 100644 --- a/qiaoba-auth/src/main/java/com/qiaoba/auth/config/SpringSecurityConfig.java +++ b/qiaoba-auth/src/main/java/com/qiaoba/auth/config/SpringSecurityConfig.java @@ -1,16 +1,11 @@ package com.qiaoba.auth.config; -import cn.hutool.core.util.RandomUtil; -import cn.hutool.crypto.SecureUtil; import com.qiaoba.auth.constants.SecurityConstant; import com.qiaoba.auth.filters.AuthenticationCoreFilter; import com.qiaoba.auth.handler.AccessDeniedHandler; import com.qiaoba.auth.handler.LogoutHandler; import com.qiaoba.auth.properties.AuthConfigProperties; -import com.qiaoba.auth.utils.TokenUtil; import com.qiaoba.common.base.constants.BaseConstant; -import com.qiaoba.common.base.constants.ConfigConstant; -import com.qiaoba.common.redis.service.RedisService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; @@ -25,8 +20,6 @@ import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.logout.LogoutFilter; -import javax.annotation.PostConstruct; - /** * SpringSecurity安全配置 * @@ -44,27 +37,8 @@ public class SpringSecurityConfig { private final AuthConfigProperties authConfigProperties; private final AccessDeniedHandler accessDeniedHandler; private final AuthenticationCoreFilter authenticationCoreFilter; - private final RedisService redisService; private final LogoutHandler logoutHandler; - /** - * 创建Token秘钥和Token有效期 - */ - @PostConstruct - public void init() { - - if (redisService.hasKey(ConfigConstant.TOKEN_EXPIRE_TIME_KEY)) { - TokenUtil.expireTime = Integer.parseInt(redisService.get(ConfigConstant.TOKEN_EXPIRE_TIME_KEY).toString()); - } - - if (redisService.hasKey(SecurityConstant.REDIS_SECRET_KEY)) { - TokenUtil.secret = SecureUtil.md5(SecureUtil.md5(redisService.get(SecurityConstant.REDIS_SECRET_KEY).toString())); - } else { - String random = RandomUtil.randomString(8); - TokenUtil.secret = SecureUtil.md5(SecureUtil.md5(random)); - redisService.set(SecurityConstant.REDIS_SECRET_KEY, random); - } - } @Bean SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { diff --git a/qiaoba-auth/src/main/java/com/qiaoba/auth/constants/SecurityConstant.java b/qiaoba-auth/src/main/java/com/qiaoba/auth/constants/SecurityConstant.java index e38f2a5..2b90011 100644 --- a/qiaoba-auth/src/main/java/com/qiaoba/auth/constants/SecurityConstant.java +++ b/qiaoba-auth/src/main/java/com/qiaoba/auth/constants/SecurityConstant.java @@ -1,11 +1,5 @@ package com.qiaoba.auth.constants; -import com.qiaoba.common.base.constants.BaseConstant; -import com.qiaoba.common.base.constants.ConfigConstant; - -import java.util.Arrays; -import java.util.List; - /** * 安全常量 * @@ -15,17 +9,10 @@ import java.util.List; */ public class SecurityConstant { - /** - * 被挤下线 - */ - public static final int HTTP_SQUEEZED_OFFLINE = 4011; - - - public static final int MAX_ERROR_COUNT = 5; public static final String LOGOUT_URI = "/logout"; - public static final String HAS_BEEN_PULLED_BLACK = "您的IP已经被系统拉黑"; + public static final String HAS_BEEN_PULLED_BLACK = "你的IP已经被系统拉黑, 请联系管理员处理"; public static final String ACCESS_DENIED = "暂无权限访问, 请重新登录"; - public static final String BLACKLIST_KEY = "login:blacklist"; + public static final String BLACKLIST_KEY = "login:blacklist:"; public static final String LOGIN_ERROR_COUNT = "login:errorCount:"; public static final String BLACKLIST_ON = "true"; @@ -37,7 +24,6 @@ public class SecurityConstant { public static final String LOGGED_USER_REDIS_KEY = "logged_user:"; - /** * 密码错误 */ diff --git a/qiaoba-auth/src/main/java/com/qiaoba/auth/service/impl/AuthConfigServiceImpl.java b/qiaoba-auth/src/main/java/com/qiaoba/auth/service/impl/AuthConfigServiceImpl.java index c4d7624..f0f680d 100644 --- a/qiaoba-auth/src/main/java/com/qiaoba/auth/service/impl/AuthConfigServiceImpl.java +++ b/qiaoba-auth/src/main/java/com/qiaoba/auth/service/impl/AuthConfigServiceImpl.java @@ -9,6 +9,8 @@ import com.qiaoba.common.redis.service.RedisService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import java.util.Objects; + /** * 安全配置 服务层实现 * @@ -37,6 +39,22 @@ public class AuthConfigServiceImpl implements AuthConfigApiService { return ConfigConstant.COMMON_ON_VALUE.equals(redisService.get(ConfigConstant.REGISTER_ON_OFF_KEY)); } + @Override + public Boolean getBlacklistConfig() { + return ConfigConstant.COMMON_ON_VALUE.equals(redisService.get(ConfigConstant.BLACKLIST_ON_OFF_KEY)); + } + + @Override + public Long getBlacklistExpireTime() { + return redisService.getObject(ConfigConstant.BLACKLIST_EXPIRE_TIME_KEY, Long.class); + } + + @Override + public Integer getAllowMaxErrorCount() { + Integer count = redisService.getObject(ConfigConstant.LOGIN_ERROR_MAX_COUNT_KEY, Integer.class); + return Objects.isNull(count) ? ConfigConstant.DEFAULT_LOGIN_ERROR_MAX_COUNT : count; + } + @Override public void validateCaptcha(String code, String uuid) { if (getCaptchaConfig()) { diff --git a/qiaoba-commons/qiaoba-common-base/src/main/java/com/qiaoba/common/base/constants/ConfigConstant.java b/qiaoba-commons/qiaoba-common-base/src/main/java/com/qiaoba/common/base/constants/ConfigConstant.java index ccb8007..adf464e 100644 --- a/qiaoba-commons/qiaoba-common-base/src/main/java/com/qiaoba/common/base/constants/ConfigConstant.java +++ b/qiaoba-commons/qiaoba-common-base/src/main/java/com/qiaoba/common/base/constants/ConfigConstant.java @@ -39,6 +39,16 @@ public class ConfigConstant { */ public static final String BLACKLIST_ON_OFF_KEY = SYS_CONFIG_KEY_PREFIX + "sys.account.blacklistOnOff"; + /** + * 参数配置-黑名单过期时间(拉黑时间), 单位:分钟 + */ + public static final String BLACKLIST_EXPIRE_TIME_KEY = SYS_CONFIG_KEY_PREFIX + "sys.account.blacklistExpireTime"; + + /** + * 参数配置-登陆最大错误次数 + */ + public static final String LOGIN_ERROR_MAX_COUNT_KEY = SYS_CONFIG_KEY_PREFIX + "sys.account.loginErrorMaxCount"; + /** * 开 */ @@ -48,4 +58,9 @@ public class ConfigConstant { * 关 */ public static final String COMMON_OFF_VALUE = "false"; + + /** + * 默认最大允许错误次数 + */ + public static final Integer DEFAULT_LOGIN_ERROR_MAX_COUNT = 5; } diff --git a/qiaoba-commons/qiaoba-common-base/src/main/java/com/qiaoba/common/base/constants/TenantConstant.java b/qiaoba-commons/qiaoba-common-base/src/main/java/com/qiaoba/common/base/constants/TenantConstant.java index 92da78e..6fd574e 100644 --- a/qiaoba-commons/qiaoba-common-base/src/main/java/com/qiaoba/common/base/constants/TenantConstant.java +++ b/qiaoba-commons/qiaoba-common-base/src/main/java/com/qiaoba/common/base/constants/TenantConstant.java @@ -14,6 +14,11 @@ public class TenantConstant { */ public static final String TENANT_KEY_PREFIX = "tenant_"; + /** + * 租户信息 Redis Key + */ + public static final String TENANT_INFO_KEY_PREFIX = "tenant_info:"; + /** * header 租户 key */ @@ -23,4 +28,9 @@ public class TenantConstant { * 系统默认租户ID */ public static final String DEFAULT_TENANT_ID = "1"; + + /** + * 登陆入口-获取正常的租户-Uri + */ + public static final String LOGIN_TENANT_LIST_URI = "/tenant/normal-list"; } diff --git a/qiaoba-commons/qiaoba-common-base/src/main/java/com/qiaoba/common/base/context/BaseContext.java b/qiaoba-commons/qiaoba-common-base/src/main/java/com/qiaoba/common/base/context/BaseContext.java index 72a7d1d..1ee9952 100644 --- a/qiaoba-commons/qiaoba-common-base/src/main/java/com/qiaoba/common/base/context/BaseContext.java +++ b/qiaoba-commons/qiaoba-common-base/src/main/java/com/qiaoba/common/base/context/BaseContext.java @@ -57,6 +57,13 @@ public class BaseContext { TENANT_ID_HOLDER.set(tenantId); } + /** + * 清除上下文中租户ID + */ + public static void clearTenantId() { + TENANT_ID_HOLDER.remove(); + } + /** * 获取上下文中的数据源 */ diff --git a/qiaoba-modules/qiaoba-module-system/src/main/java/com/qiaoba/module/system/service/impl/SysLoginServiceImpl.java b/qiaoba-modules/qiaoba-module-system/src/main/java/com/qiaoba/module/system/service/impl/SysLoginServiceImpl.java index dc43acc..6a94cc7 100644 --- a/qiaoba-modules/qiaoba-module-system/src/main/java/com/qiaoba/module/system/service/impl/SysLoginServiceImpl.java +++ b/qiaoba-modules/qiaoba-module-system/src/main/java/com/qiaoba/module/system/service/impl/SysLoginServiceImpl.java @@ -72,6 +72,9 @@ public class SysLoginServiceImpl implements SysLoginService { @Override public String login(LoginDto dto) { + Boolean blacklistSwitch = authConfigApiService.getBlacklistConfig(); + // 校验黑名单 + validateBlacklist(blacklistSwitch); // 校验验证码 authConfigApiService.validateCaptcha(dto.getCode(), dto.getUuid()); // username查询用户信息 @@ -79,7 +82,7 @@ public class SysLoginServiceImpl implements SysLoginService { // 检查账号信息 validateUser(dto.getUsername(), sysUser); // 检验密码 - validatePassword(dto.getUsername(), sysUser.getPassword(), dto.getPassword()); + validatePassword(blacklistSwitch, dto.getUsername(), sysUser.getPassword(), dto.getPassword()); // 缓存在线用户 String deviceSn = cacheOnlineUser(dto.getUsername(), sysUser.getNickname()); // 缓存userDetails @@ -88,15 +91,46 @@ public class SysLoginServiceImpl implements SysLoginService { return TokenUtil.generateToken(sysUser.getUsername(), deviceSn); } - private void validatePassword(String username, String password, String inputPassword) { - boolean result = SecurityUtil.matchesPassword(inputPassword, password); - if (!result) { - ThreadUtil.execAsync(() -> beforePasswordError(username)); - throw new ServiceException(SecurityConstant.PASSWORD_ERROR); + private void validateBlacklist(Boolean blacklistSwitch) { + String ip = IpUtil.getIp(request); + if (blacklistSwitch && redisService.hasKey(SecurityConstant.BLACKLIST_KEY + ip)) { + throw new ServiceException(SecurityConstant.HAS_BEEN_PULLED_BLACK); } } - private void beforePasswordError(String username) { + private void validatePassword(Boolean blacklistSwitch, String username, String password, String inputPassword) { + boolean result = SecurityUtil.matchesPassword(inputPassword, password); + if (result && blacklistSwitch) { + // 密码正确, 删除错误次数 + String ip = IpUtil.getIp(request); + redisService.del(SecurityConstant.LOGIN_ERROR_COUNT + ip); + } else { + // 密码错误 + String msg = beforePasswordError(blacklistSwitch, username); + throw new ServiceException(msg); + } + } + + private String beforePasswordError(Boolean blacklistSwitch, String username) { + String ip = IpUtil.getIp(request); + // 未开启->直接结束 + if (!blacklistSwitch) { + return "密码错误"; + } + // 开启->继续 + // 错误次数是否到达允许最大错误次数 + Integer maxAllowCount = authConfigApiService.getAllowMaxErrorCount(); + Integer ipErrorCount = redisService.getObject(SecurityConstant.LOGIN_ERROR_COUNT + ip, Integer.class); + if (ipErrorCount >= maxAllowCount) { + // 是-> 进入黑名单库 && 返回"IP已被拉黑" + redisService.set(SecurityConstant.BLACKLIST_KEY + ip, username, authConfigApiService.getBlacklistExpireTime()); + return SecurityConstant.HAS_BEEN_PULLED_BLACK; + } else { + // 否-> 错误次数+1 && 返回"你还剩xx次错误机会" + ipErrorCount++; + redisService.set(SecurityConstant.LOGIN_ERROR_COUNT + ip, ipErrorCount); + return StrUtil.format("密码错误, 还有[{}]次错误机会", ipErrorCount); + } } diff --git a/qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/filters/DynamicDataSourceFilter.java b/qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/filters/DynamicDataSourceFilter.java index 2ea931b..4f8c008 100644 --- a/qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/filters/DynamicDataSourceFilter.java +++ b/qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/filters/DynamicDataSourceFilter.java @@ -2,14 +2,12 @@ package com.qiaoba.module.tenant.filters; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.StrUtil; -import com.qiaoba.auth.properties.AuthConfigProperties; import com.qiaoba.common.base.code.TenantErrorCode; import com.qiaoba.common.base.constants.TenantConstant; import com.qiaoba.common.base.context.BaseContext; import com.qiaoba.common.database.config.DynamicDataSourceConfig; import com.qiaoba.common.database.constants.DynamicDatasourceConstant; import com.qiaoba.common.web.utils.ResponseUtil; -import com.qiaoba.common.web.utils.UriUtil; import com.qiaoba.module.tenant.entity.SysTenant; import com.qiaoba.module.tenant.enums.TenantStatusEnum; import com.qiaoba.module.tenant.service.SysTenantService; @@ -43,17 +41,14 @@ public class DynamicDataSourceFilter extends OncePerRequestFilter { private SysTenantService sysTenantService; @Resource private DynamicDataSourceConfig dynamicDataSourceConfig; - @Resource - private AuthConfigProperties authConfigProperties; - private final String LOGIN_TENANT_LIST_URI = "/tenant/normal-list"; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { log.debug(StrUtil.format("Start run DynamicDataSourceFilter, Uri: {}", request.getRequestURI())); String tenantId = request.getHeader(TenantConstant.HEADER_KEY_TENANT); // 主系统 or 登录入口获取租户列表 - if (TenantConstant.DEFAULT_TENANT_ID.equals(tenantId) || LOGIN_TENANT_LIST_URI.equals(request.getRequestURI())) { + if (TenantConstant.DEFAULT_TENANT_ID.equals(tenantId) || TenantConstant.LOGIN_TENANT_LIST_URI.equals(request.getRequestURI())) { dynamicDataSourceConfig.setDefaultSetting(); filterChain.doFilter(request, response); after(); @@ -61,7 +56,7 @@ public class DynamicDataSourceFilter extends OncePerRequestFilter { } - SysTenant sysTenant = sysTenantService.selectById(tenantId); + SysTenant sysTenant = sysTenantService.selectFromCache(tenantId); // 检查租户是否允许访问 if (checkTenantIsNotAllow(response, sysTenant)) { return; diff --git a/qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/service/SysTenantService.java b/qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/service/SysTenantService.java index 3a3ca42..a1be243 100644 --- a/qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/service/SysTenantService.java +++ b/qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/service/SysTenantService.java @@ -50,6 +50,14 @@ public interface SysTenantService { */ SysTenant selectById(String tenantId); + /** + * 在缓存中查询详情 + * + * @param tenantId tenantId + * @return info + */ + SysTenant selectFromCache(String tenantId); + /** * 获取设置信息 * @@ -73,4 +81,9 @@ public interface SysTenantService { * @param status status */ void updateStatus(String tenantId, String status); + + /** + * 更新缓存 + */ + void resetCache(); } diff --git a/qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/service/impl/SysTenantServiceImpl.java b/qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/service/impl/SysTenantServiceImpl.java index a1d4136..07dd661 100644 --- a/qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/service/impl/SysTenantServiceImpl.java +++ b/qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/service/impl/SysTenantServiceImpl.java @@ -4,6 +4,8 @@ import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.qiaoba.auth.utils.SecurityUtil; import com.qiaoba.common.base.constants.BaseConstant; +import com.qiaoba.common.base.constants.TenantConstant; +import com.qiaoba.common.base.context.BaseContext; import com.qiaoba.common.base.enums.BaseEnum; import com.qiaoba.common.base.enums.DataBaseEnum; import com.qiaoba.common.base.exceptions.ServiceException; @@ -12,6 +14,7 @@ import com.qiaoba.common.database.entity.PageQuery; import com.qiaoba.common.database.entity.TableDataInfo; import com.qiaoba.common.database.properties.PoolInfo; import com.qiaoba.common.database.utils.JdbcUtil; +import com.qiaoba.common.redis.service.RedisService; import com.qiaoba.module.tenant.entity.SysTenant; import com.qiaoba.module.tenant.entity.SysTenantDatasource; import com.qiaoba.module.tenant.entity.param.SysTenantParam; @@ -24,6 +27,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import javax.annotation.PostConstruct; import java.sql.Connection; import java.util.Date; import java.util.List; @@ -54,20 +58,36 @@ public class SysTenantServiceImpl implements SysTenantService { private final SysTenantMapper sysTenantMapper; private final SysTenantDatasourceService sysTenantDatasourceService; private final DynamicDataSourceConfig dynamicDataSourceConfig; + private final RedisService redisService; + + @PostConstruct + public void init() { + BaseContext.setTenantId(TenantConstant.DEFAULT_TENANT_ID); + resetCache(); + BaseContext.clearTenantId(); + } @Override public int insert(SysTenant sysTenant) { sysTenant.setCreateTime(new Date()); sysTenant.setCreateUser(SecurityUtil.getLoginUsername()); sysTenant.setStatus(BaseEnum.NORMAL.getCode()); - return sysTenantMapper.insert(sysTenant); + int result = sysTenantMapper.insert(sysTenant); + if (result > BaseConstant.HANDLE_ERROR) { + toCache(sysTenant); + } + return result; } @Override public int update(SysTenant sysTenant) { sysTenant.setUpdateTime(new Date()); sysTenant.setUpdateUser(SecurityUtil.getLoginUsername()); - return sysTenantMapper.updateById(sysTenant); + int result = sysTenantMapper.updateById(sysTenant); + if (result > BaseConstant.HANDLE_ERROR) { + toCache(sysTenant); + } + return result; } @Override @@ -80,6 +100,14 @@ public class SysTenantServiceImpl implements SysTenantService { return sysTenantMapper.selectById(tenantId); } + @Override + public SysTenant selectFromCache(String tenantId) { + BaseContext.setTenantId(TenantConstant.DEFAULT_TENANT_ID); + SysTenant sysTenant = redisService.getObject(TenantConstant.TENANT_INFO_KEY_PREFIX + tenantId, SysTenant.class); + BaseContext.clearTenantId(); + return sysTenant; + } + @Override public TenantSettingVo getSetting(String tenantId) { SysTenant sysTenant = selectById(tenantId); @@ -118,6 +146,15 @@ public class SysTenantServiceImpl implements SysTenantService { @Override public void updateStatus(String tenantId, String status) { sysTenantMapper.updateById(new SysTenant(tenantId, status)); + toCache(selectById(tenantId)); + } + + @Override + public void resetCache() { + List sysTenants = sysTenantMapper.selectList(param2Wrapper(new SysTenantParam())); + for (SysTenant sysTenant : sysTenants) { + toCache(sysTenant); + } } private QueryWrapper param2Wrapper(SysTenantParam param) { @@ -184,4 +221,7 @@ public class SysTenantServiceImpl implements SysTenantService { return JdbcUtil.getConnection(DataBaseEnum.getDriver(master.getType()), DataBaseEnum.getUrl(master.getType(), master.getIp(), master.getPort(), master.getName()), master.getUsername(), master.getPassword()); } + private void toCache(SysTenant sysTenant) { + redisService.set(TenantConstant.TENANT_INFO_KEY_PREFIX + sysTenant.getTenantId(), sysTenant); + } }