first commit

This commit is contained in:
2023-06-12 22:49:55 +08:00
parent c931657a8a
commit 95bae3f9b3
11 changed files with 172 additions and 58 deletions

View File

@ -30,8 +30,30 @@ public interface AuthConfigApiService {
*/
Boolean getRegisterConfig();
/**
* 是否开启黑名单
*
* @return 是/否
*/
Boolean getBlacklistConfig();
/**
* 黑名单过期时间(拉黑时间), 单位:秒
*
* @return 过期时间
*/
Long getBlacklistExpireTime();
/**
* 获取最大允许错误次数
*
* @return 最大允许错误次数
*/
Integer getAllowMaxErrorCount();
/**
* 验证验证码
*
* @param code code
* @param uuid uuid
*/

View File

@ -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 {

View File

@ -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:";
/**
* 密码错误
*/

View File

@ -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()) {

View File

@ -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;
}

View File

@ -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";
}

View File

@ -57,6 +57,13 @@ public class BaseContext {
TENANT_ID_HOLDER.set(tenantId);
}
/**
* 清除上下文中租户ID
*/
public static void clearTenantId() {
TENANT_ID_HOLDER.remove();
}
/**
* 获取上下文中的数据源
*/

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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();
}

View File

@ -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<SysTenant> sysTenants = sysTenantMapper.selectList(param2Wrapper(new SysTenantParam()));
for (SysTenant sysTenant : sysTenants) {
toCache(sysTenant);
}
}
private QueryWrapper<SysTenant> 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);
}
}