v3.6.0 三级等保重磅更新:1、【新增】双因子方式登录;2、【新增】定期修改密码;3、【新增】最大活跃时间;4、【新增】敏感数据脱敏;5、【新增】登录锁定配置;6、【新增】密码复杂度配置;7、【新增】三级等保可配置

This commit is contained in:
zhuoda
2024-09-02 22:20:29 +08:00
parent ac7c9940bf
commit 92dddd507b
80 changed files with 2770 additions and 501 deletions

View File

@@ -50,9 +50,6 @@ public class AdminInterceptor implements HandlerInterceptor {
@Resource
private SystemEnvironment systemEnvironment;
@Value("${sa-token.active-timeout}")
private long tokenActiveTimeout;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
@@ -158,11 +155,6 @@ public class AdminInterceptor implements HandlerInterceptor {
return;
}
// 小于1 ,也不需要检测
if (tokenActiveTimeout < 1) {
return;
}
StpUtil.checkActiveTimeout();
StpUtil.updateLastActiveToNow();
}

View File

@@ -10,6 +10,8 @@ import net.lab1024.sa.admin.module.system.employee.service.EmployeeService;
import net.lab1024.sa.base.common.domain.PageResult;
import net.lab1024.sa.base.common.domain.ResponseDTO;
import net.lab1024.sa.base.common.util.SmartRequestUtil;
import net.lab1024.sa.base.module.support.apiencrypt.annotation.ApiDecrypt;
import net.lab1024.sa.base.module.support.securityprotect.service.Level3ProtectConfigService;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
@@ -23,7 +25,7 @@ import java.util.List;
* @Date 2021-12-09 22:57:49
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
*/
@RestController
@Tag(name = AdminSwaggerTagConst.System.SYSTEM_EMPLOYEE)
@@ -32,6 +34,9 @@ public class EmployeeController {
@Resource
private EmployeeService employeeService;
@Resource
private Level3ProtectConfigService level3ProtectConfigService;
@PostMapping("/employee/query")
@Operation(summary = "员工管理查询 @author 卓大")
public ResponseDTO<PageResult<EmployeeVO>> query(@Valid @RequestBody EmployeeQueryForm query) {
@@ -89,9 +94,17 @@ public class EmployeeController {
@Operation(summary = "修改密码 @author 卓大")
@PostMapping("/employee/update/password")
@ApiDecrypt
public ResponseDTO<String> updatePassword(@Valid @RequestBody EmployeeUpdatePasswordForm updatePasswordForm) {
updatePasswordForm.setEmployeeId(SmartRequestUtil.getRequestUserId());
return employeeService.updatePassword(updatePasswordForm);
return employeeService.updatePassword(SmartRequestUtil.getRequestUser(), updatePasswordForm);
}
@Operation(summary = "获取密码复杂度 @author 卓大")
@GetMapping("/employee/getPasswordComplexityEnabled")
@ApiDecrypt
public ResponseDTO<Boolean> getPasswordComplexityEnabled() {
return ResponseDTO.ok(level3ProtectConfigService.isPasswordComplexityEnabled());
}
@Operation(summary = "重置员工密码 @author 卓大")

View File

@@ -53,6 +53,11 @@ public class EmployeeEntity {
*/
private String phone;
/**
* 邮箱
*/
private String email;
/**
* 部门id
*/

View File

@@ -51,6 +51,9 @@ public class EmployeeAddForm {
@Pattern(regexp = SmartVerificationUtil.PHONE_REGEXP, message = "手机号格式不正确")
private String phone;
@Schema(description = "邮箱")
private String email;
@Schema(description = "角色列表")
private List<Long> roleIdList;

View File

@@ -24,11 +24,9 @@ public class EmployeeUpdatePasswordForm {
@Schema(description = "原密码")
@NotBlank(message = "原密码不能为空哦")
@Pattern(regexp = SmartVerificationUtil.PWD_REGEXP, message = "原密码请输入6-15位(数字|大小写字母|小数点)")
private String oldPassword;
@Schema(description = "新密码")
@NotBlank(message = "新密码不能为空哦")
@Pattern(regexp = SmartVerificationUtil.PWD_REGEXP, message = "新密码请输入6-15位(数字|大小写字母|小数点)")
private String newPassword;
}

View File

@@ -62,4 +62,7 @@ public class EmployeeVO {
@Schema(description = "职务名称")
private String positionName;
@Schema(description = "邮箱")
private String email;
}

View File

@@ -22,7 +22,7 @@ import java.util.stream.Collectors;
* @Date 2021-12-29 21:52:46
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
*/
@Service
public class EmployeeManager extends ServiceImpl<EmployeeDao, EmployeeEntity> {
@@ -38,7 +38,6 @@ public class EmployeeManager extends ServiceImpl<EmployeeDao, EmployeeEntity> {
/**
* 保存员工
*
*/
@Transactional(rollbackFor = Throwable.class)
public void saveEmployee(EmployeeEntity employee, List<Long> roleIdList) {
@@ -53,17 +52,20 @@ public class EmployeeManager extends ServiceImpl<EmployeeDao, EmployeeEntity> {
/**
* 更新员工
*
*/
@Transactional(rollbackFor = Throwable.class)
public void updateEmployee(EmployeeEntity employee, List<Long> roleIdList) {
// 保存员工 获得id
employeeDao.updateById(employee);
if (CollectionUtils.isNotEmpty(roleIdList)) {
List<RoleEmployeeEntity> roleEmployeeList = roleIdList.stream().map(e -> new RoleEmployeeEntity(e, employee.getEmployeeId())).collect(Collectors.toList());
this.updateEmployeeRole(employee.getEmployeeId(), roleEmployeeList);
// 若为空,则删除所有角色
if (CollectionUtils.isEmpty(roleIdList)) {
roleEmployeeDao.deleteByEmployeeId(employee.getEmployeeId());
return;
}
List<RoleEmployeeEntity> roleEmployeeList = roleIdList.stream().map(e -> new RoleEmployeeEntity(e, employee.getEmployeeId())).collect(Collectors.toList());
this.updateEmployeeRole(employee.getEmployeeId(), roleEmployeeList);
}
/**

View File

@@ -20,15 +20,16 @@ import net.lab1024.sa.admin.module.system.role.domain.vo.RoleEmployeeVO;
import net.lab1024.sa.base.common.code.UserErrorCode;
import net.lab1024.sa.base.common.constant.StringConst;
import net.lab1024.sa.base.common.domain.PageResult;
import net.lab1024.sa.base.common.domain.RequestUser;
import net.lab1024.sa.base.common.domain.ResponseDTO;
import net.lab1024.sa.base.common.enumeration.UserTypeEnum;
import net.lab1024.sa.base.common.util.SmartBeanUtil;
import net.lab1024.sa.base.common.util.SmartPageUtil;
import net.lab1024.sa.base.module.support.securityprotect.service.ProtectPasswordService;
import org.apache.commons.codec.digest.DigestUtils;
import net.lab1024.sa.base.module.support.securityprotect.service.SecurityPasswordService;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.*;
@@ -46,8 +47,6 @@ import java.util.stream.Collectors;
@Service
public class EmployeeService {
private static final String PASSWORD_SALT_FORMAT = "smart_%s_admin_$^&*";
@Resource
private EmployeeDao employeeDao;
@@ -64,7 +63,7 @@ public class EmployeeService {
private DepartmentService departmentService;
@Resource
private ProtectPasswordService protectPasswordService;
private SecurityPasswordService securityPasswordService;
@Resource
@Lazy
@@ -121,16 +120,11 @@ public class EmployeeService {
* 新增员工
*/
public synchronized ResponseDTO<String> addEmployee(EmployeeAddForm employeeAddForm) {
// 校验名是否重复
// 校验登录名是否重复
EmployeeEntity employeeEntity = employeeDao.getByLoginName(employeeAddForm.getLoginName(), null);
if (null != employeeEntity) {
return ResponseDTO.userErrorParam("登录名重复");
}
// 校验姓名是否重复
employeeEntity = employeeDao.getByActualName(employeeAddForm.getActualName(), null);
if (null != employeeEntity) {
return ResponseDTO.userErrorParam("姓名重复");
}
// 校验电话是否存在
employeeEntity = employeeDao.getByPhone(employeeAddForm.getPhone(), null);
if (null != employeeEntity) {
@@ -146,8 +140,8 @@ public class EmployeeService {
EmployeeEntity entity = SmartBeanUtil.copy(employeeAddForm, EmployeeEntity.class);
// 设置密码 默认密码
String password = protectPasswordService.randomPassword();
entity.setLoginPwd(getEncryptPwd(password));
String password = securityPasswordService.randomPassword();
entity.setLoginPwd(SecurityPasswordService.getEncryptPwd(password));
// 保存数据
entity.setDeletedFlag(Boolean.FALSE);
@@ -185,11 +179,6 @@ public class EmployeeService {
return ResponseDTO.userErrorParam("手机号已存在");
}
existEntity = employeeDao.getByActualName(employeeUpdateForm.getActualName(), null);
if (null != existEntity && !Objects.equals(existEntity.getEmployeeId(), employeeId)) {
return ResponseDTO.userErrorParam("姓名重复");
}
// 不更新密码
EmployeeEntity entity = SmartBeanUtil.copy(employeeUpdateForm, EmployeeEntity.class);
entity.setLoginPwd(null);
@@ -301,36 +290,46 @@ public class EmployeeService {
/**
* 更新密码
*/
public ResponseDTO<String> updatePassword(EmployeeUpdatePasswordForm updatePasswordForm) {
@Transactional(rollbackFor = Throwable.class)
public ResponseDTO<String> updatePassword(RequestUser requestUser, EmployeeUpdatePasswordForm updatePasswordForm) {
Long employeeId = updatePasswordForm.getEmployeeId();
EmployeeEntity employeeEntity = employeeDao.selectById(employeeId);
if (employeeEntity == null) {
return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST);
}
// 校验原始密码
String encryptPwd = getEncryptPwd(updatePasswordForm.getOldPassword());
if (!Objects.equals(encryptPwd, employeeEntity.getLoginPwd())) {
String oldPassword = SecurityPasswordService.getEncryptPwd(updatePasswordForm.getOldPassword());
if (!Objects.equals(oldPassword, employeeEntity.getLoginPwd())) {
return ResponseDTO.userErrorParam("原密码有误,请重新输入");
}
// 校验密码复杂度
ResponseDTO<String> validatePassComplexity = securityPasswordService.validatePasswordComplexity(updatePasswordForm.getNewPassword());
if (!validatePassComplexity.getOk()) {
return validatePassComplexity;
}
// 新旧密码相同
String newPassword = updatePasswordForm.getNewPassword();
if (Objects.equals(updatePasswordForm.getOldPassword(), newPassword)) {
String newPassword = SecurityPasswordService.getEncryptPwd(updatePasswordForm.getNewPassword());
if (Objects.equals(oldPassword, newPassword)) {
return ResponseDTO.userErrorParam("新密码与原始密码相同,请重新输入");
}
// 校验密码复杂度
ResponseDTO<String> validatePassComplexity = protectPasswordService.validatePassComplexity(newPassword);
if (!validatePassComplexity.getOk()) {
return validatePassComplexity;
// 根据三级等保规则,校验密码是否重复
ResponseDTO<String> passwordRepeatTimes = securityPasswordService.validatePasswordRepeatTimes(requestUser, updatePasswordForm.getNewPassword());
if (!passwordRepeatTimes.getOk()) {
return ResponseDTO.error(passwordRepeatTimes);
}
// 更新密码
EmployeeEntity updateEntity = new EmployeeEntity();
updateEntity.setEmployeeId(employeeId);
updateEntity.setLoginPwd(getEncryptPwd(newPassword));
updateEntity.setLoginPwd(newPassword);
employeeDao.updateById(updateEntity);
// 保存修改密码密码记录
securityPasswordService.saveUserChangePasswordLog(requestUser, newPassword, oldPassword);
return ResponseDTO.ok();
}
@@ -364,18 +363,11 @@ public class EmployeeService {
* 重置密码
*/
public ResponseDTO<String> resetPassword(Integer employeeId) {
String password = protectPasswordService.randomPassword();
employeeDao.updatePassword(employeeId, getEncryptPwd(password));
String password = securityPasswordService.randomPassword();
employeeDao.updatePassword(employeeId, SecurityPasswordService.getEncryptPwd(password));
return ResponseDTO.ok(password);
}
/**
* 获取 加密后 的密码
*/
public static String getEncryptPwd(String password) {
return DigestUtils.md5Hex(String.format(PASSWORD_SALT_FORMAT, password));
}
/**
* 查询全部员工

View File

@@ -2,8 +2,8 @@ package net.lab1024.sa.admin.module.system.login.controller;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.extra.servlet.ServletUtil;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import net.lab1024.sa.admin.constant.AdminSwaggerTagConst;
import net.lab1024.sa.admin.module.system.login.domain.LoginForm;
import net.lab1024.sa.admin.module.system.login.domain.LoginResultVO;
@@ -14,6 +14,7 @@ import net.lab1024.sa.base.common.constant.RequestHeaderConst;
import net.lab1024.sa.base.common.domain.ResponseDTO;
import net.lab1024.sa.base.common.util.SmartRequestUtil;
import net.lab1024.sa.base.module.support.captcha.domain.CaptchaVO;
import net.lab1024.sa.base.module.support.securityprotect.service.Level3ProtectConfigService;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
@@ -27,7 +28,7 @@ import javax.validation.Valid;
* @Date 2021-12-15 21:05:46
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
*/
@RestController
@Tag(name = AdminSwaggerTagConst.System.SYSTEM_LOGIN)
@@ -36,6 +37,9 @@ public class LoginController {
@Resource
private LoginService loginService;
@Resource
private Level3ProtectConfigService level3ProtectConfigService;
@NoNeedLogin
@PostMapping("/login")
@Operation(summary = "登录 @author 卓大")
@@ -48,8 +52,8 @@ public class LoginController {
@GetMapping("/login/getLoginInfo")
@Operation(summary = "获取登录结果信息 @author 卓大")
public ResponseDTO<LoginResultVO> getLoginInfo() {
LoginResultVO loginResult = loginService.getLoginResult(AdminRequestUtil.getRequestUser());
String tokenValue = StpUtil.getTokenValue();
LoginResultVO loginResult = loginService.getLoginResult(AdminRequestUtil.getRequestUser(), tokenValue);
loginResult.setToken(tokenValue);
return ResponseDTO.ok(loginResult);
}
@@ -67,4 +71,20 @@ public class LoginController {
return loginService.getCaptcha();
}
@NoNeedLogin
@GetMapping("/login/sendEmailCode/{loginName}")
@Operation(summary = "获取邮箱登录验证码 @author 卓大")
public ResponseDTO<String> sendEmailCode(@PathVariable String loginName) {
return loginService.sendEmailCode(loginName);
}
@NoNeedLogin
@GetMapping("/login/getTwoFactorLoginFlag")
@Operation(summary = "获取双因子登录标识 @author 卓大")
public ResponseDTO<Boolean> getTwoFactorLoginFlag() {
// 双因子登录
boolean twoFactorLoginEnabled = level3ProtectConfigService.isTwoFactorLoginEnabled();
return ResponseDTO.ok(twoFactorLoginEnabled);
}
}

View File

@@ -34,4 +34,7 @@ public class LoginForm extends CaptchaForm {
@SchemaEnum(desc = "登录终端", value = LoginDeviceEnum.class)
@CheckEnum(value = LoginDeviceEnum.class, required = true, message = "此终端不允许登录")
private Integer loginDevice;
@Schema(description = "邮箱验证码")
private String emailCode;
}

View File

@@ -26,6 +26,9 @@ public class LoginResultVO extends RequestEmployee {
@Schema(description = "菜单列表")
private List<MenuVO> menuList;
@Schema(description = "是否需要修改密码")
private Boolean needUpdatePwdFlag;
@Schema(description = "上次登录ip")
private String lastLoginIp;

View File

@@ -3,6 +3,8 @@ package net.lab1024.sa.admin.module.system.login.service;
import cn.dev33.satoken.stp.StpInterface;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.extra.servlet.ServletUtil;
import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap;
import lombok.extern.slf4j.Slf4j;
@@ -27,7 +29,10 @@ import net.lab1024.sa.base.common.enumeration.UserTypeEnum;
import net.lab1024.sa.base.common.util.SmartBeanUtil;
import net.lab1024.sa.base.common.util.SmartEnumUtil;
import net.lab1024.sa.base.common.util.SmartIpUtil;
import net.lab1024.sa.base.common.util.SmartStringUtil;
import net.lab1024.sa.base.constant.LoginDeviceEnum;
import net.lab1024.sa.base.constant.RedisKeyConst;
import net.lab1024.sa.base.module.support.apiencrypt.service.ApiEncryptService;
import net.lab1024.sa.base.module.support.captcha.CaptchaService;
import net.lab1024.sa.base.module.support.captcha.domain.CaptchaVO;
import net.lab1024.sa.base.module.support.config.ConfigKeyEnum;
@@ -37,9 +42,13 @@ import net.lab1024.sa.base.module.support.loginlog.LoginLogResultEnum;
import net.lab1024.sa.base.module.support.loginlog.LoginLogService;
import net.lab1024.sa.base.module.support.loginlog.domain.LoginLogEntity;
import net.lab1024.sa.base.module.support.loginlog.domain.LoginLogVO;
import net.lab1024.sa.base.module.support.mail.MailService;
import net.lab1024.sa.base.module.support.mail.constant.MailTemplateCodeEnum;
import net.lab1024.sa.base.module.support.redis.RedisService;
import net.lab1024.sa.base.module.support.securityprotect.domain.LoginFailEntity;
import net.lab1024.sa.base.module.support.securityprotect.service.ProtectLoginService;
import net.lab1024.sa.base.module.support.securityprotect.service.ProtectPasswordService;
import net.lab1024.sa.base.module.support.securityprotect.service.Level3ProtectConfigService;
import net.lab1024.sa.base.module.support.securityprotect.service.SecurityLoginService;
import net.lab1024.sa.base.module.support.securityprotect.service.SecurityPasswordService;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
@@ -107,14 +116,26 @@ public class LoginService implements StpInterface {
private RoleMenuService roleMenuService;
@Resource
private ProtectLoginService protectLoginService;
private SecurityLoginService securityLoginService;
@Resource
private ProtectPasswordService profectPasswordService;
private SecurityPasswordService protectPasswordService;
@Resource
private IFileStorageService fileStorageService;
@Resource
private ApiEncryptService apiEncryptService;
@Resource
private Level3ProtectConfigService level3ProtectConfigService;
@Resource
private MailService mailService;
@Resource
private RedisService redisService;
/**
* 获取验证码
*/
@@ -153,12 +174,18 @@ public class LoginService implements StpInterface {
}
// 解密前端加密的密码
String requestPassword = profectPasswordService.decryptPassword(loginForm.getPassword());
String requestPassword = apiEncryptService.decrypt(loginForm.getPassword());
// 验证密码 是否为万能密码
String superPassword = configService.getConfigValue(ConfigKeyEnum.SUPER_PASSWORD);
boolean superPasswordFlag = superPassword.equals(requestPassword);
// 校验双因子登录
ResponseDTO<String> validateEmailCode = validateEmailCode(loginForm, employeeEntity, superPasswordFlag);
if (!validateEmailCode.getOk()) {
return ResponseDTO.error(validateEmailCode);
}
// 万能密码特殊操作
if (superPasswordFlag) {
@@ -170,23 +197,27 @@ public class LoginService implements StpInterface {
} else {
// 按照等保登录要求,进行登录失败次数校验
ResponseDTO<LoginFailEntity> loginFailEntityResponseDTO = protectLoginService.checkLogin(employeeEntity.getEmployeeId(), UserTypeEnum.ADMIN_EMPLOYEE);
ResponseDTO<LoginFailEntity> loginFailEntityResponseDTO = securityLoginService.checkLogin(employeeEntity.getEmployeeId(), UserTypeEnum.ADMIN_EMPLOYEE);
if (!loginFailEntityResponseDTO.getOk()) {
return ResponseDTO.error(loginFailEntityResponseDTO);
}
// 密码错误
if (!employeeEntity.getLoginPwd().equals(EmployeeService.getEncryptPwd(requestPassword))) {
if (!employeeEntity.getLoginPwd().equals(SecurityPasswordService.getEncryptPwd(requestPassword))) {
// 记录登录失败
saveLoginLog(employeeEntity, ip, userAgent, "密码错误", LoginLogResultEnum.LOGIN_FAIL);
// 记录等级保护次数
String msg = protectLoginService.recordLoginFail(employeeEntity.getEmployeeId(), UserTypeEnum.ADMIN_EMPLOYEE, employeeEntity.getLoginName(), loginFailEntityResponseDTO.getData());
String msg = securityLoginService.recordLoginFail(employeeEntity.getEmployeeId(), UserTypeEnum.ADMIN_EMPLOYEE, employeeEntity.getLoginName(), loginFailEntityResponseDTO.getData());
return msg == null ? ResponseDTO.userErrorParam("登录名或密码错误!") : ResponseDTO.error(UserErrorCode.LOGIN_FAIL_WILL_LOCK, msg);
}
String saTokenLoginId = UserTypeEnum.ADMIN_EMPLOYEE.getValue() + StringConst.COLON + employeeEntity.getEmployeeId();
// 登录
StpUtil.login(saTokenLoginId, String.valueOf(loginDeviceEnum.getDesc()));
// 移除邮箱验证码
deleteEmailCode(employeeEntity.getEmployeeId());
}
// 获取员工信息
@@ -196,16 +227,17 @@ public class LoginService implements StpInterface {
loginEmployeeCache.put(employeeEntity.getEmployeeId(), requestEmployee);
// 移除登录失败
protectLoginService.removeLoginFail(employeeEntity.getEmployeeId(), UserTypeEnum.ADMIN_EMPLOYEE);
securityLoginService.removeLoginFail(employeeEntity.getEmployeeId(), UserTypeEnum.ADMIN_EMPLOYEE);
// 获取登录结果信息
LoginResultVO loginResultVO = getLoginResult(requestEmployee);
String token = StpUtil.getTokenValue();
LoginResultVO loginResultVO = getLoginResult(requestEmployee, token);
//保存登录记录
saveLoginLog(employeeEntity, ip, userAgent, superPasswordFlag ? "万能密码登录" : loginDeviceEnum.getDesc(), LoginLogResultEnum.LOGIN_SUCCESS);
// 设置 token
loginResultVO.setToken(StpUtil.getTokenValue());
loginResultVO.setToken(token);
// 清除权限缓存
permissionCache.remove(employeeEntity.getEmployeeId());
@@ -217,7 +249,7 @@ public class LoginService implements StpInterface {
/**
* 获取登录结果信息
*/
public LoginResultVO getLoginResult(RequestEmployee requestEmployee) {
public LoginResultVO getLoginResult(RequestEmployee requestEmployee, String token) {
// 基础信息
LoginResultVO loginResultVO = SmartBeanUtil.copy(requestEmployee, LoginResultVO.class);
@@ -240,6 +272,16 @@ public class LoginService implements StpInterface {
loginResultVO.setLastLoginUserAgent(loginLogVO.getUserAgent());
}
// 是否需要强制修改密码
boolean needChangePasswordFlag = protectPasswordService.checkNeedChangePassword(requestEmployee.getUserType().getValue(), requestEmployee.getUserId());
loginResultVO.setNeedUpdatePwdFlag(needChangePasswordFlag);
// 万能密码登录,则不需要设置强制修改密码
String loginIdByToken = (String) StpUtil.getLoginIdByToken(token);
if (loginIdByToken != null && loginIdByToken.startsWith(SUPER_PASSWORD_LOGIN_ID_PREFIX)) {
loginResultVO.setNeedUpdatePwdFlag(false);
}
return loginResultVO;
}
@@ -259,9 +301,9 @@ public class LoginService implements StpInterface {
// 头像信息
String avatar = employeeEntity.getAvatar();
if(StringUtils.isNotBlank(avatar)){
if (StringUtils.isNotBlank(avatar)) {
ResponseDTO<String> getFileUrl = fileStorageService.getFileUrl(avatar);
if(BooleanUtils.isTrue(getFileUrl.getOk())){
if (BooleanUtils.isTrue(getFileUrl.getOk())) {
requestEmployee.setAvatar(getFileUrl.getData());
}
}
@@ -357,9 +399,8 @@ public class LoginService implements StpInterface {
/**
* 清除员工登录缓存
* @param employeeId
*/
public void clearLoginEmployeeCache(Long employeeId){
public void clearLoginEmployeeCache(Long employeeId) {
// 清空登录信息缓存
loginEmployeeCache.remove(employeeId);
}
@@ -451,4 +492,94 @@ public class LoginService implements StpInterface {
return userPermission;
}
/**
* 发送 邮箱 验证码
*/
public ResponseDTO<String> sendEmailCode(String loginName) {
// 开启双因子登录
if (!level3ProtectConfigService.isTwoFactorLoginEnabled()) {
return ResponseDTO.userErrorParam("无需使用邮箱验证码");
}
// 验证登录名
EmployeeEntity employeeEntity = employeeService.getByLoginName(loginName);
if (null == employeeEntity) {
return ResponseDTO.userErrorParam("登录名不存在!");
}
// 验证账号状态
if (employeeEntity.getDisabledFlag()) {
return ResponseDTO.userErrorParam("您的账号已被禁用,请联系工作人员!");
}
String mail = employeeEntity.getEmail();
if (SmartStringUtil.isBlank(mail)) {
return ResponseDTO.userErrorParam("您暂未配置邮箱地址,请联系管理员配置邮箱");
}
// 校验验证码发送时间60秒内不能重复发生
String redisVerificationCodeKey = redisService.generateRedisKey(RedisKeyConst.Support.LOGIN_VERIFICATION_CODE, UserTypeEnum.ADMIN_EMPLOYEE.getValue() + RedisKeyConst.SEPARATOR + employeeEntity.getEmployeeId());
String emailCode = redisService.get(redisVerificationCodeKey);
long sendCodeTimeMills = -1;
if (!SmartStringUtil.isEmpty(emailCode)) {
sendCodeTimeMills = NumberUtil.parseLong(emailCode.split(StringConst.UNDERLINE)[1]);
}
if (System.currentTimeMillis() - sendCodeTimeMills < 60 * 1000) {
return ResponseDTO.userErrorParam("邮箱验证码已发送,一分钟内请勿重复发送");
}
//生成验证码
long currentTimeMillis = System.currentTimeMillis();
String verificationCode = RandomUtil.randomNumbers(4);
redisService.set(redisVerificationCodeKey, verificationCode + StringConst.UNDERLINE + currentTimeMillis, 300);
// 发送邮件验证码
HashMap<String, Object> mailParams = new HashMap<>();
mailParams.put("code", verificationCode);
return mailService.sendMail(MailTemplateCodeEnum.LOGIN_VERIFICATION_CODE, mailParams, Collections.singletonList(employeeEntity.getEmail()));
}
/**
* 校验邮箱验证码
*/
private ResponseDTO<String> validateEmailCode(LoginForm loginForm, EmployeeEntity employeeEntity, boolean superPasswordFlag) {
// 万能密码则不校验
if (superPasswordFlag) {
return ResponseDTO.ok();
}
// 未开启双因子登录
if (!level3ProtectConfigService.isTwoFactorLoginEnabled()) {
return ResponseDTO.ok();
}
if (SmartStringUtil.isEmpty(loginForm.getEmailCode())) {
return ResponseDTO.userErrorParam("请输入邮箱验证码");
}
// 校验验证码
String redisVerificationCodeKey = redisService.generateRedisKey(RedisKeyConst.Support.LOGIN_VERIFICATION_CODE, UserTypeEnum.ADMIN_EMPLOYEE.getValue() + RedisKeyConst.SEPARATOR + employeeEntity.getEmployeeId());
String emailCode = redisService.get(redisVerificationCodeKey);
if (SmartStringUtil.isEmpty(emailCode)) {
return ResponseDTO.userErrorParam("邮箱验证码已失效,请重新发送");
}
if (!emailCode.split(StringConst.UNDERLINE)[0].equals(loginForm.getEmailCode().trim())) {
return ResponseDTO.userErrorParam("邮箱验证码错误,请重新填写");
}
return ResponseDTO.ok();
}
/**
* 移除邮箱验证码
*/
private void deleteEmailCode(Long employeeId) {
String redisVerificationCodeKey = redisService.generateRedisKey(RedisKeyConst.Support.LOGIN_VERIFICATION_CODE, UserTypeEnum.ADMIN_EMPLOYEE.getValue() + RedisKeyConst.SEPARATOR + employeeId);
redisService.delete(redisVerificationCodeKey);
}
}

View File

@@ -0,0 +1,88 @@
package net.lab1024.sa.admin.module.system.support;
import cn.hutool.core.util.RandomUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.Data;
import net.lab1024.sa.base.common.controller.SupportBaseController;
import net.lab1024.sa.base.common.domain.ResponseDTO;
import net.lab1024.sa.base.constant.SwaggerTagConst;
import net.lab1024.sa.base.module.support.datamasking.DataMasking;
import net.lab1024.sa.base.module.support.datamasking.DataMaskingTypeEnum;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
/**
* 数据脱敏demo
*
* @Author 1024创新实验室-主任:卓大
* @Date 2024/08/01 22:07:27
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>Since 2012
*/
@RestController
@Tag(name = SwaggerTagConst.Support.DATA_MASKING)
public class AdminDataMaskingDemoController extends SupportBaseController {
@Operation(summary = "数据脱敏demo @author 1024创新实验室-主任-卓大")
@GetMapping("/dataMasking/demo/query")
public ResponseDTO<List<DataVO>> query() {
List<DataVO> list = new ArrayList<>();
for (int i = 0; i < RandomUtil.randomInt(10,16); i++) {
DataVO data = new DataVO();
data.setUserId(RandomUtil.randomLong(1328479238, 83274298347982L));
data.setPhone("1" + RandomUtil.randomNumbers(10));
data.setIdCard("410" + RandomUtil.randomNumbers(3) + RandomUtil.randomInt(1980, 2010) + RandomUtil.randomInt(10, 12) + RandomUtil.randomInt(10, 30) + RandomUtil.randomNumbers(4));
data.setAddress(RandomUtil.randomBoolean() ? "河南省洛阳市洛龙区一零二四大街1024号" : "河南省郑州市高新区六边形大街六边形大楼");
data.setPassword(RandomUtil.randomString(10));
data.setEmail(RandomUtil.randomString(RandomUtil.randomInt(6, 10)) + "@" + RandomUtil.randomString(2) + ".com");
data.setCarLicense("" + RandomStringUtils.randomAlphabetic(1).toUpperCase()+" " + RandomStringUtils.randomAlphanumeric(5).toUpperCase());
data.setBankCard("6225" + RandomStringUtils.randomNumeric(14));
data.setOther(RandomStringUtils.randomAlphanumeric(1, 12));
list.add(data);
}
return ResponseDTO.ok(list);
}
@Data
public static class DataVO {
@DataMasking(DataMaskingTypeEnum.USER_ID)
private Long userId;
@DataMasking(DataMaskingTypeEnum.PHONE)
private String phone;
@DataMasking(DataMaskingTypeEnum.ID_CARD)
private String idCard;
@DataMasking(DataMaskingTypeEnum.ADDRESS)
private String address;
@DataMasking(DataMaskingTypeEnum.PASSWORD)
private String password;
@DataMasking(DataMaskingTypeEnum.EMAIL)
private String email;
@DataMasking(DataMaskingTypeEnum.CAR_LICENSE)
private String carLicense;
@DataMasking(DataMaskingTypeEnum.BANK_CARD)
private String bankCard;
@DataMasking
private String other;
}
}

View File

@@ -1,15 +1,20 @@
package net.lab1024.sa.admin.module.system.support;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import net.lab1024.sa.base.common.controller.SupportBaseController;
import net.lab1024.sa.base.common.domain.PageResult;
import net.lab1024.sa.base.common.domain.ResponseDTO;
import net.lab1024.sa.base.common.domain.ValidateList;
import net.lab1024.sa.base.constant.SwaggerTagConst;
import net.lab1024.sa.base.module.support.config.ConfigKeyEnum;
import net.lab1024.sa.base.module.support.config.ConfigService;
import net.lab1024.sa.base.module.support.securityprotect.domain.Level3ProtectConfigForm;
import net.lab1024.sa.base.module.support.securityprotect.domain.LoginFailQueryForm;
import net.lab1024.sa.base.module.support.securityprotect.domain.LoginFailVO;
import net.lab1024.sa.base.module.support.securityprotect.service.ProtectLoginService;
import net.lab1024.sa.base.module.support.securityprotect.service.Level3ProtectConfigService;
import net.lab1024.sa.base.module.support.securityprotect.service.SecurityLoginService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@@ -18,14 +23,13 @@ import javax.annotation.Resource;
import javax.validation.Valid;
/**
*
* 网络安全
*
* @Author 1024创新实验室-主任:卓大
* @Date 2023/10/17 19:07:27
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>Since 2012
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>Since 2012
*/
@RestController
@@ -33,20 +37,37 @@ import javax.validation.Valid;
public class AdminProtectController extends SupportBaseController {
@Resource
private ProtectLoginService protectLoginService;
private SecurityLoginService securityLoginService;
@Resource
private Level3ProtectConfigService level3ProtectConfigService;
@Resource
private ConfigService configService;
@Operation(summary = "分页查询 @author 1024创新实验室-主任-卓大")
@PostMapping("/protect/loginFail/queryPage")
public ResponseDTO<PageResult<LoginFailVO>> queryPage(@RequestBody @Valid LoginFailQueryForm queryForm) {
return ResponseDTO.ok(protectLoginService.queryPage(queryForm));
return ResponseDTO.ok(securityLoginService.queryPage(queryForm));
}
@Operation(summary = "批量删除 @author 1024创新实验室-主任-卓大")
@PostMapping("/protect/loginFail/batchDelete")
public ResponseDTO<String> batchDelete(@RequestBody ValidateList<Long> idList) {
return protectLoginService.batchDelete(idList);
return securityLoginService.batchDelete(idList);
}
@Operation(summary = "更新三级等保配置 @author 1024创新实验室-主任-卓大")
@PostMapping("/protect/level3protect/updateConfig")
public ResponseDTO<String> updateConfig(@RequestBody @Valid Level3ProtectConfigForm configForm) {
return level3ProtectConfigService.updateLevel3Config(configForm);
}
@Operation(summary = "查询 三级等保配置 @author 1024创新实验室-主任-卓大")
@GetMapping("/protect/level3protect/getConfig")
public ResponseDTO<String> getConfig() {
return ResponseDTO.ok(configService.getConfigValue(ConfigKeyEnum.LEVEL3_PROTECT_CONFIG));
}
}

View File

@@ -19,27 +19,4 @@ server:
# 环境
spring:
profiles:
active: '@profiles.active@'
####################################### 安全等级保护 相关配置 ##################################################
# #
# 建议开启 "三级等保" 所要求的配置,具体如下: #
# 1连续登录失败 5 次锁定账户 30 分钟, #
# 2登录超时时长建议为 30分钟超过此时间没有访问系统会重新要求登录 #
# 3密码复杂度至少三种字符最小 8 位 #
# #
#############################################################################################################
classified-protect:
# 连续登录失败次数则锁定,-1表示不受限制可以一直尝试登录
login-max-fail-times: 5
# 连续登录失败锁定时间(单位:秒),-1表示不锁定建议锁定30分钟
login-fail-locked-seconds: 1800
# 密码复杂度开启(默认复杂度为:至少三种字符,最小 8 位), true 开启false 不开启,建议开启
password-complexity-enabled: true
sa-token:
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
active-timeout: 1800
# token 有效期(单位:秒) 1天86400秒-1 代表永久有效
timeout: 86400
active: '@profiles.active@'

View File

@@ -19,27 +19,4 @@ server:
# 环境
spring:
profiles:
active: '@profiles.active@'
####################################### 安全等级保护 相关配置 ##################################################
# #
# 建议开启 "三级等保" 所要求的配置,具体如下: #
# 1连续登录失败 5 次锁定账户 30 分钟, #
# 2登录超时时长建议为 30分钟超过此时间没有访问系统会重新要求登录 #
# 3密码复杂度至少三种字符最小 8 位 #
# #
#############################################################################################################
classified-protect:
# 连续登录失败次数则锁定,-1表示不受限制可以一直尝试登录
login-max-fail-times: 5
# 连续登录失败锁定时间(单位:秒),-1表示不锁定建议锁定30分钟
login-fail-locked-seconds: 1800
# 密码复杂度开启(默认复杂度为:至少三种字符,最小 8 位), true 开启false 不开启,建议开启
password-complexity-enabled: true
sa-token:
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
active-timeout: 1800
# token 有效期(单位:秒) 1天86400秒-1 代表永久有效
timeout: 86400
active: '@profiles.active@'

View File

@@ -19,27 +19,4 @@ server:
# 环境
spring:
profiles:
active: '@profiles.active@'
####################################### 安全等级保护 相关配置 ##################################################
# #
# 建议开启 "三级等保" 所要求的配置,具体如下: #
# 1连续登录失败 5 次锁定账户 30 分钟, #
# 2登录超时时长建议为 30分钟超过此时间没有访问系统会重新要求登录 #
# 3密码复杂度至少三种字符最小 8 位 #
# #
#############################################################################################################
classified-protect:
# 连续登录失败次数则锁定,-1表示不受限制可以一直尝试登录
login-max-fail-times: 5
# 连续登录失败锁定时间(单位:秒),-1表示不锁定建议锁定30分钟
login-fail-locked-seconds: 1800
# 密码复杂度开启(默认复杂度为:至少三种字符,最小 8 位), true 开启false 不开启,建议开启
password-complexity-enabled: true
sa-token:
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
active-timeout: 1800
# token 有效期(单位:秒) 1天86400秒-1 代表永久有效
timeout: 86400
active: '@profiles.active@'

View File

@@ -8,7 +8,7 @@
# 项目配置: 名称、日志目录
project:
name: sa-admin
log-directory: /home/project/smartadmin/sit/log
log-directory: /home/project/smartadmin/test/log
# 项目端口和url根路径
server:
@@ -19,27 +19,4 @@ server:
# 环境
spring:
profiles:
active: '@profiles.active@'
####################################### 安全等级保护 相关配置 ##################################################
# #
# 建议开启 "三级等保" 所要求的配置,具体如下: #
# 1连续登录失败 5 次锁定账户 30 分钟, #
# 2登录超时时长建议为 30分钟超过此时间没有访问系统会重新要求登录 #
# 3密码复杂度至少三种字符最小 8 位 #
# #
#############################################################################################################
classified-protect:
# 连续登录失败次数则锁定,-1表示不受限制可以一直尝试登录
login-max-fail-times: 5
# 连续登录失败锁定时间(单位:秒),-1表示不锁定建议锁定30分钟
login-fail-locked-seconds: 1800
# 密码复杂度开启(默认复杂度为:至少三种字符,最小 8 位), true 开启false 不开启,建议开启
password-complexity-enabled: true
sa-token:
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
active-timeout: 1800
# token 有效期(单位:秒) 1天86400秒-1 代表永久有效
timeout: 86400
active: '@profiles.active@'