diff --git a/smart-admin-api/pom.xml b/smart-admin-api/pom.xml index b08d256d..5f2a4b42 100644 --- a/smart-admin-api/pom.xml +++ b/smart-admin-api/pom.xml @@ -19,9 +19,10 @@ UTF-8 UTF-8 1.8 - 2.7.5 + 2.7.18 2.0.8 3.5.2 + 8.0.33 3.9.1 1.7.0 4.3.0 @@ -42,7 +43,7 @@ 5.2.4 1.4 1.11.842 - 2.17.2 + 2.23.1 5.7.22 2.3 0.9.1 @@ -52,8 +53,12 @@ 2.7.0 1.59 2.13.4 + 2.16.1 1.2.0 3.25.0 + 2.2 + 2.3.33 + 1.18.1 @@ -81,6 +86,12 @@ + + com.mysql + mysql-connector-j + ${mysql-connector-j.version} + + com.baomidou mybatis-plus-boot-starter @@ -201,12 +212,6 @@ ${commons-text.version} - - org.apache.logging.log4j - log4j-spring-boot - ${log4j-spring-boot.version} - - cn.hutool hutool-all @@ -309,6 +314,12 @@ ${jackson-datatype-jsr310.version} + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + ${jackson-dataformat-yaml.version} + + net.1024lab smartdb @@ -341,6 +352,24 @@ ${redisson.version} + + org.yaml + snakeyaml + ${snakeyaml.version} + + + + org.jsoup + jsoup + ${jsoup.version} + + + + org.freemarker + freemarker + ${freemarker.version} + + diff --git a/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/interceptor/AdminInterceptor.java b/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/interceptor/AdminInterceptor.java index 1de8e721..9b01d544 100644 --- a/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/interceptor/AdminInterceptor.java +++ b/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/interceptor/AdminInterceptor.java @@ -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(); } diff --git a/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/controller/EmployeeController.java b/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/controller/EmployeeController.java index a79b4355..b6d1c6d5 100644 --- a/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/controller/EmployeeController.java +++ b/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/controller/EmployeeController.java @@ -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 1024创新实验室 + * @Copyright 1024创新实验室 */ @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> query(@Valid @RequestBody EmployeeQueryForm query) { @@ -89,9 +94,17 @@ public class EmployeeController { @Operation(summary = "修改密码 @author 卓大") @PostMapping("/employee/update/password") + @ApiDecrypt public ResponseDTO 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 getPasswordComplexityEnabled() { + return ResponseDTO.ok(level3ProtectConfigService.isPasswordComplexityEnabled()); } @Operation(summary = "重置员工密码 @author 卓大") diff --git a/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/entity/EmployeeEntity.java b/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/entity/EmployeeEntity.java index 692f831a..edc53e61 100644 --- a/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/entity/EmployeeEntity.java +++ b/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/entity/EmployeeEntity.java @@ -53,6 +53,11 @@ public class EmployeeEntity { */ private String phone; + /** + * 邮箱 + */ + private String email; + /** * 部门id */ diff --git a/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/form/EmployeeAddForm.java b/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/form/EmployeeAddForm.java index bd7b4e63..2621eb7f 100644 --- a/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/form/EmployeeAddForm.java +++ b/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/form/EmployeeAddForm.java @@ -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 roleIdList; diff --git a/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/form/EmployeeUpdatePasswordForm.java b/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/form/EmployeeUpdatePasswordForm.java index 736248f0..9a44afb2 100644 --- a/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/form/EmployeeUpdatePasswordForm.java +++ b/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/form/EmployeeUpdatePasswordForm.java @@ -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; } diff --git a/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/vo/EmployeeVO.java b/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/vo/EmployeeVO.java index 5ab64c53..a4b620a8 100644 --- a/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/vo/EmployeeVO.java +++ b/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/vo/EmployeeVO.java @@ -62,4 +62,7 @@ public class EmployeeVO { @Schema(description = "职务名称") private String positionName; + @Schema(description = "邮箱") + private String email; + } diff --git a/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/manager/EmployeeManager.java b/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/manager/EmployeeManager.java index 2aca93bb..4882e93b 100644 --- a/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/manager/EmployeeManager.java +++ b/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/manager/EmployeeManager.java @@ -22,7 +22,7 @@ import java.util.stream.Collectors; * @Date 2021-12-29 21:52:46 * @Wechat zhuoda1024 * @Email lab1024@163.com - * @Copyright 1024创新实验室 + * @Copyright 1024创新实验室 */ @Service public class EmployeeManager extends ServiceImpl { @@ -38,7 +38,6 @@ public class EmployeeManager extends ServiceImpl { /** * 保存员工 - * */ @Transactional(rollbackFor = Throwable.class) public void saveEmployee(EmployeeEntity employee, List roleIdList) { @@ -53,17 +52,20 @@ public class EmployeeManager extends ServiceImpl { /** * 更新员工 - * */ @Transactional(rollbackFor = Throwable.class) public void updateEmployee(EmployeeEntity employee, List roleIdList) { // 保存员工 获得id employeeDao.updateById(employee); - if (CollectionUtils.isNotEmpty(roleIdList)) { - List 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 roleEmployeeList = roleIdList.stream().map(e -> new RoleEmployeeEntity(e, employee.getEmployeeId())).collect(Collectors.toList()); + this.updateEmployeeRole(employee.getEmployeeId(), roleEmployeeList); } /** diff --git a/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/service/EmployeeService.java b/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/service/EmployeeService.java index eb037d42..61cd6f79 100644 --- a/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/service/EmployeeService.java +++ b/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/service/EmployeeService.java @@ -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 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 updatePassword(EmployeeUpdatePasswordForm updatePasswordForm) { + @Transactional(rollbackFor = Throwable.class) + public ResponseDTO 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 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 validatePassComplexity = protectPasswordService.validatePassComplexity(newPassword); - if (!validatePassComplexity.getOk()) { - return validatePassComplexity; + // 根据三级等保规则,校验密码是否重复 + ResponseDTO 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 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)); - } - /** * 查询全部员工 diff --git a/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/login/controller/LoginController.java b/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/login/controller/LoginController.java index 9bf884d5..d2ab72a0 100644 --- a/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/login/controller/LoginController.java +++ b/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/login/controller/LoginController.java @@ -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 1024创新实验室 + * @Copyright 1024创新实验室 */ @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 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 sendEmailCode(@PathVariable String loginName) { + return loginService.sendEmailCode(loginName); + } + + + @NoNeedLogin + @GetMapping("/login/getTwoFactorLoginFlag") + @Operation(summary = "获取双因子登录标识 @author 卓大") + public ResponseDTO getTwoFactorLoginFlag() { + // 双因子登录 + boolean twoFactorLoginEnabled = level3ProtectConfigService.isTwoFactorLoginEnabled(); + return ResponseDTO.ok(twoFactorLoginEnabled); + } } diff --git a/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/login/domain/LoginForm.java b/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/login/domain/LoginForm.java index 207120c4..fd707a1e 100644 --- a/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/login/domain/LoginForm.java +++ b/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/login/domain/LoginForm.java @@ -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; } diff --git a/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/login/domain/LoginResultVO.java b/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/login/domain/LoginResultVO.java index 4bcd0cbc..7ab52c6d 100644 --- a/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/login/domain/LoginResultVO.java +++ b/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/login/domain/LoginResultVO.java @@ -26,6 +26,9 @@ public class LoginResultVO extends RequestEmployee { @Schema(description = "菜单列表") private List menuList; + @Schema(description = "是否需要修改密码") + private Boolean needUpdatePwdFlag; + @Schema(description = "上次登录ip") private String lastLoginIp; diff --git a/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/login/service/LoginService.java b/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/login/service/LoginService.java index e32e177f..2f1bb2ad 100644 --- a/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/login/service/LoginService.java +++ b/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/login/service/LoginService.java @@ -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 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 loginFailEntityResponseDTO = protectLoginService.checkLogin(employeeEntity.getEmployeeId(), UserTypeEnum.ADMIN_EMPLOYEE); + ResponseDTO 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 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 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 mailParams = new HashMap<>(); + mailParams.put("code", verificationCode); + return mailService.sendMail(MailTemplateCodeEnum.LOGIN_VERIFICATION_CODE, mailParams, Collections.singletonList(employeeEntity.getEmail())); + } + + + /** + * 校验邮箱验证码 + */ + private ResponseDTO 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); + } } diff --git a/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminDataMaskingDemoController.java b/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminDataMaskingDemoController.java new file mode 100644 index 00000000..7e6156b1 --- /dev/null +++ b/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminDataMaskingDemoController.java @@ -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 1024创新实验室,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> query() { + + List 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; + + } + +} diff --git a/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminProtectController.java b/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminProtectController.java index faa875a3..9e737f41 100644 --- a/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminProtectController.java +++ b/smart-admin-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminProtectController.java @@ -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 1024创新实验室,Since 2012 + * @Copyright 1024创新实验室,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> 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 batchDelete(@RequestBody ValidateList idList) { - return protectLoginService.batchDelete(idList); + return securityLoginService.batchDelete(idList); } + @Operation(summary = "更新三级等保配置 @author 1024创新实验室-主任-卓大") + @PostMapping("/protect/level3protect/updateConfig") + public ResponseDTO updateConfig(@RequestBody @Valid Level3ProtectConfigForm configForm) { + return level3ProtectConfigService.updateLevel3Config(configForm); + } + @Operation(summary = "查询 三级等保配置 @author 1024创新实验室-主任-卓大") + @GetMapping("/protect/level3protect/getConfig") + public ResponseDTO getConfig() { + return ResponseDTO.ok(configService.getConfigValue(ConfigKeyEnum.LEVEL3_PROTECT_CONFIG)); + } } diff --git a/smart-admin-api/sa-admin/src/main/resources/dev/application.yaml b/smart-admin-api/sa-admin/src/main/resources/dev/application.yaml index ba540719..7cd85cfa 100644 --- a/smart-admin-api/sa-admin/src/main/resources/dev/application.yaml +++ b/smart-admin-api/sa-admin/src/main/resources/dev/application.yaml @@ -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 \ No newline at end of file + active: '@profiles.active@' \ No newline at end of file diff --git a/smart-admin-api/sa-admin/src/main/resources/pre/application.yaml b/smart-admin-api/sa-admin/src/main/resources/pre/application.yaml index cbac6806..a56dcc58 100644 --- a/smart-admin-api/sa-admin/src/main/resources/pre/application.yaml +++ b/smart-admin-api/sa-admin/src/main/resources/pre/application.yaml @@ -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 \ No newline at end of file + active: '@profiles.active@' \ No newline at end of file diff --git a/smart-admin-api/sa-admin/src/main/resources/prod/application.yaml b/smart-admin-api/sa-admin/src/main/resources/prod/application.yaml index cbac6806..a56dcc58 100644 --- a/smart-admin-api/sa-admin/src/main/resources/prod/application.yaml +++ b/smart-admin-api/sa-admin/src/main/resources/prod/application.yaml @@ -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 \ No newline at end of file + active: '@profiles.active@' \ No newline at end of file diff --git a/smart-admin-api/sa-admin/src/main/resources/test/application.yaml b/smart-admin-api/sa-admin/src/main/resources/test/application.yaml index e6af9476..6ca92cf3 100644 --- a/smart-admin-api/sa-admin/src/main/resources/test/application.yaml +++ b/smart-admin-api/sa-admin/src/main/resources/test/application.yaml @@ -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 \ No newline at end of file + active: '@profiles.active@' \ No newline at end of file diff --git a/smart-admin-api/sa-base/pom.xml b/smart-admin-api/sa-base/pom.xml index 870fb9d8..0f76c8fb 100644 --- a/smart-admin-api/sa-base/pom.xml +++ b/smart-admin-api/sa-base/pom.xml @@ -88,8 +88,8 @@ - mysql - mysql-connector-java + com.mysql + mysql-connector-j @@ -263,6 +263,11 @@ jackson-datatype-jsr310 + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + + net.1024lab smartdb @@ -278,6 +283,27 @@ redisson-spring-data-27 + + org.yaml + snakeyaml + + + + org.springframework.boot + spring-boot-starter-mail + + + + org.jsoup + jsoup + + + + org.freemarker + freemarker + + + diff --git a/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/DataMaskingSerializer.java b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/DataMaskingSerializer.java new file mode 100644 index 00000000..ffe7c84e --- /dev/null +++ b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/DataMaskingSerializer.java @@ -0,0 +1,59 @@ +package net.lab1024.sa.base.common.json.serializer; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.ContextualSerializer; +import net.lab1024.sa.base.module.support.datamasking.DataMasking; +import net.lab1024.sa.base.module.support.datamasking.DataMaskingTypeEnum; +import net.lab1024.sa.base.module.support.datamasking.SmartDataMaskingUtil; +import org.apache.commons.lang3.ObjectUtils; + +import java.io.IOException; + +/** + * 脱敏序列化 + * + * @author 罗伊 + * @description: + * @date 2024/7/21 4:39 下午 + */ +public class DataMaskingSerializer extends JsonSerializer implements ContextualSerializer { + + private DataMaskingTypeEnum typeEnum; + + @Override + public void serialize(Object value, JsonGenerator jsonGenerator, SerializerProvider serializers) throws IOException { + + if (ObjectUtils.isEmpty(value)) { + jsonGenerator.writeObject(value); + return; + } + + if (typeEnum == null) { + jsonGenerator.writeObject(SmartDataMaskingUtil.dataMasking(String.valueOf(value))); + return; + } + + jsonGenerator.writeObject(SmartDataMaskingUtil.dataMasking(value, typeEnum)); + } + + @Override + public JsonSerializer createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException { + // 判断beanProperty是不是空 + if (null == property) { + return prov.findNullValueSerializer(property); + } + + DataMasking annotation = property.getAnnotation(DataMasking.class); + if (null == annotation) { + return prov.findValueSerializer(property.getType(), property); + } + + typeEnum = annotation.value(); + return this; + } + +} \ No newline at end of file diff --git a/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/config/SwaggerConfig.java b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/config/SwaggerConfig.java index c1f343d1..e980dba1 100644 --- a/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/config/SwaggerConfig.java +++ b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/config/SwaggerConfig.java @@ -28,7 +28,7 @@ import java.util.Optional; * springdoc-openapi 配置 * nginx配置前缀时如果需要访问【/swagger-ui/index.html】需添加额外nginx配置 * location /v3/api-docs/ { - * proxy_pass http://127.0.0.1:11024/v3/api-docs/; + * proxy_pass http://127.0.0.1:1024/v3/api-docs/; * } * @Author 1024创新实验室-主任: 卓大 * @Date 2020-03-25 22:54:46 @@ -43,7 +43,7 @@ public class SwaggerConfig { /** * 用于解决/swagger-ui/index.html页面ServersUrl 测试环境部署错误问题 */ - @Value("${springdoc.swagger-ui.server-base-url:''}") + @Value("${springdoc.swagger-ui.server-base-url}") private String serverBaseUrl; public static final String[] SWAGGER_WHITELIST = { diff --git a/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/constant/RedisKeyConst.java b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/constant/RedisKeyConst.java index f4e70413..29733c93 100644 --- a/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/constant/RedisKeyConst.java +++ b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/constant/RedisKeyConst.java @@ -23,5 +23,7 @@ public class RedisKeyConst { public static final String CAPTCHA = "captcha:"; + public static final String LOGIN_VERIFICATION_CODE = "login:verification-code:"; + } } diff --git a/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/constant/SwaggerTagConst.java b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/constant/SwaggerTagConst.java index 8a9642d7..fa30161e 100644 --- a/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/constant/SwaggerTagConst.java +++ b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/constant/SwaggerTagConst.java @@ -49,6 +49,8 @@ public class SwaggerTagConst { public static final String PROTECT = "业务支撑-网络安全"; + public static final String DATA_MASKING = "业务支撑-数据脱敏"; + public static final String JOB = "业务支撑-定时任务"; public static final String MESSAGE = "业务支撑-消息"; diff --git a/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/captcha/CaptchaService.java b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/captcha/CaptchaService.java index b1850925..829135b7 100644 --- a/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/captcha/CaptchaService.java +++ b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/captcha/CaptchaService.java @@ -42,8 +42,10 @@ public class CaptchaService { @Resource private DefaultKaptcha defaultKaptcha; - @Autowired + + @Resource private SystemEnvironment systemEnvironment; + @Resource private RedisService redisService; diff --git a/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/ConfigKeyEnum.java b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/ConfigKeyEnum.java index 35f287af..da4514d7 100644 --- a/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/ConfigKeyEnum.java +++ b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/ConfigKeyEnum.java @@ -22,6 +22,7 @@ public enum ConfigKeyEnum implements BaseEnum { */ SUPER_PASSWORD("super_password", "万能密码"), + LEVEL3_PROTECT_CONFIG("level3_protect_config", "三级等保配置"), ; private final String value; diff --git a/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/ConfigService.java b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/ConfigService.java index 6872da3d..3522dea5 100644 --- a/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/ConfigService.java +++ b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/ConfigService.java @@ -109,7 +109,8 @@ public class ConfigService { * */ public String getConfigValue(ConfigKeyEnum configKey) { - return this.getConfig(configKey).getConfigValue(); + ConfigVO config = this.getConfig(configKey); + return config == null ? null : config.getConfigValue(); } /** @@ -125,12 +126,12 @@ public class ConfigService { * 添加系统配置 * */ - public ResponseDTO add(ConfigAddForm configAddDTO) { - ConfigEntity entity = configDao.selectByKey(configAddDTO.getConfigKey()); + public ResponseDTO add(ConfigAddForm configAddForm) { + ConfigEntity entity = configDao.selectByKey(configAddForm.getConfigKey()); if (null != entity) { return ResponseDTO.error(UserErrorCode.ALREADY_EXIST); } - entity = SmartBeanUtil.copy(configAddDTO, ConfigEntity.class); + entity = SmartBeanUtil.copy(configAddForm, ConfigEntity.class); configDao.insert(entity); // 刷新缓存 diff --git a/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datamasking/DataMasking.java b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datamasking/DataMasking.java new file mode 100644 index 00000000..c3847d71 --- /dev/null +++ b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datamasking/DataMasking.java @@ -0,0 +1,27 @@ +package net.lab1024.sa.base.module.support.datamasking; + +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import net.lab1024.sa.base.common.json.serializer.DataMaskingSerializer; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 脱敏注解 + * + * @author 罗伊 + * @description: + * @date 2024/7/21 4:39 下午 + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@JacksonAnnotationsInside +@JsonSerialize(using = DataMaskingSerializer.class, nullsUsing = DataMaskingSerializer.class) +public @interface DataMasking { + + DataMaskingTypeEnum value() default DataMaskingTypeEnum.COMMON; + +} diff --git a/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datamasking/DataMaskingTypeEnum.java b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datamasking/DataMaskingTypeEnum.java new file mode 100644 index 00000000..7df897ad --- /dev/null +++ b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datamasking/DataMaskingTypeEnum.java @@ -0,0 +1,40 @@ +package net.lab1024.sa.base.module.support.datamasking; + +import cn.hutool.core.util.DesensitizedUtil; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 脱敏数据类型 + * + * @Author 1024创新实验室-创始人兼主任:卓大 + * @Date 2024/8/1 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 ,Since 2012 + */ + +@AllArgsConstructor +@Getter +public enum DataMaskingTypeEnum { + + COMMON(null, "通用"), + PHONE(DesensitizedUtil.DesensitizedType.MOBILE_PHONE, "手机号"), + CHINESE_NAME(DesensitizedUtil.DesensitizedType.CHINESE_NAME, "中文名"), + ID_CARD(DesensitizedUtil.DesensitizedType.ID_CARD, "身份证号"), + FIXED_PHONE(DesensitizedUtil.DesensitizedType.FIXED_PHONE, "座机号"), + ADDRESS(DesensitizedUtil.DesensitizedType.ADDRESS, "地址"), + EMAIL(DesensitizedUtil.DesensitizedType.EMAIL, "电子邮件"), + PASSWORD(DesensitizedUtil.DesensitizedType.PASSWORD, "密码"), + CAR_LICENSE(DesensitizedUtil.DesensitizedType.CAR_LICENSE, "中国大陆车牌"), + BANK_CARD(DesensitizedUtil.DesensitizedType.BANK_CARD, "银行卡"), + USER_ID(DesensitizedUtil.DesensitizedType.USER_ID, "用户id"); + + + + private DesensitizedUtil.DesensitizedType type; + + private String desc; + + +} diff --git a/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datamasking/SmartDataMaskingUtil.java b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datamasking/SmartDataMaskingUtil.java new file mode 100644 index 00000000..4443da40 --- /dev/null +++ b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datamasking/SmartDataMaskingUtil.java @@ -0,0 +1,216 @@ +package net.lab1024.sa.base.module.support.datamasking; + +import cn.hutool.core.util.DesensitizedUtil; +import cn.hutool.core.util.StrUtil; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; + +import java.beans.IntrospectionException; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 脱敏工具类 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2024-07-23 21:38:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class SmartDataMaskingUtil { + + /** + * 类 加注解字段缓存 + */ + private static final ConcurrentHashMap, List> fieldMap = new ConcurrentHashMap<>(); + + public static String dataMasking(String value) { + if (StringUtils.isBlank(value)) { + return value; + } + + if (value.length() < 4) { + return StrUtil.hide(value, 0, value.length()); + } + + int valueLength = value.length(); + int startHideIndex = getHideStartIndex(valueLength); + int endHideIndex = getHideEndIndex(valueLength); + return StrUtil.hide(value, startHideIndex, endHideIndex); + } + + public static Object dataMasking(Object value, DataMaskingTypeEnum dataType) { + + if (value == null) { + return null; + } + + if (dataType == null) { + return dataMasking(String.valueOf(value)); + } + + switch (dataType) { + case PHONE: + return DesensitizedUtil.mobilePhone(String.valueOf(value)); + case CHINESE_NAME: + return DesensitizedUtil.chineseName(String.valueOf(value)); + case ID_CARD: + return DesensitizedUtil.idCardNum(String.valueOf(value), 6, 2); + case FIXED_PHONE: + return DesensitizedUtil.fixedPhone(String.valueOf(value)); + case ADDRESS: + return StrUtil.hide(String.valueOf(value), 6, String.valueOf(value).length() - 1); + case EMAIL: + return DesensitizedUtil.email(String.valueOf(value)); + case PASSWORD: + return DesensitizedUtil.password(String.valueOf(value)); + case CAR_LICENSE: + return DesensitizedUtil.carLicense(String.valueOf(value)); + case BANK_CARD: + return DesensitizedUtil.bankCard(String.valueOf(value)); + case USER_ID: + return DesensitizedUtil.userId(); + default: + return dataMasking(String.valueOf(value)); + } + } + + /** + * 批量脱敏 + */ + public static void dataMasking(Collection objectList) throws IntrospectionException, InvocationTargetException, IllegalAccessException { + if (CollectionUtils.isEmpty(objectList)) { + return; + } + + for (T object : objectList) { + dataMasking(object); + } + } + + + /** + * 单个脱敏 + */ + public static void dataMasking(T object) throws IntrospectionException, InvocationTargetException, IllegalAccessException { + Class tClass = object.getClass(); + List fieldList = getField(object); + for (Field field : fieldList) { + field.setAccessible(true); + String fieldValue = ""; + try { + PropertyDescriptor pd = new PropertyDescriptor(field.getName(), tClass); + Method getMethod = pd.getReadMethod(); + Object value = getMethod.invoke(object); + if (value != null) { + fieldValue = value.toString(); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + if (StringUtils.isBlank(fieldValue)) { + continue; + } + int valueLength = fieldValue.length(); + int startHideIndex = getHideStartIndex(valueLength); + int endHideIndex = getHideEndIndex(valueLength); + try { + field.set(object, StrUtil.hide(fieldValue, startHideIndex, endHideIndex)); + } catch (Exception e1) { + throw new RuntimeException(e1); + } + } + } + + private static int getHideStartIndex(int totalLength) { + if (totalLength <= 4) { + return 1; + } else if (totalLength <= 6) { + return 1; + } else if (totalLength <= 10) { + return 2; + } else if (totalLength <= 18) { + return 3; + } else if (totalLength <= 27) { + return 5; + } else if (totalLength <= 34) { + return 7; + } else if (totalLength <= 41) { + return 9; + } else { + return 15; + } + } + + private static int getHideEndIndex(int totalLength) { + if (totalLength <= 4) { + return totalLength - 1; + } else if (totalLength <= 6) { + return totalLength - 2; + } else if (totalLength <= 10) { + return totalLength - 2; + } else if (totalLength <= 18) { + return totalLength - 4; + } else if (totalLength <= 27) { + return totalLength - 6; + } else if (totalLength <= 34) { + return totalLength - 8; + } else if (totalLength <= 41) { + return totalLength - 10; + } else { + return totalLength - 16; + } + } + + + public static List getField(Object object) throws IntrospectionException { + // 从缓存中查询 + Class tClass = object.getClass(); + List fieldList = fieldMap.get(tClass); + if (null != fieldList) { + return fieldList; + } + + // 这一段递归代码 是为了 从父类中获取属性 + Class tempClass = tClass; + fieldList = new ArrayList<>(); + while (tempClass != null) { + Field[] declaredFields = tempClass.getDeclaredFields(); + for (Field field : declaredFields) { + boolean stringField = false; + try { + PropertyDescriptor pd = new PropertyDescriptor(field.getName(), tClass); + Method getMethod = pd.getReadMethod(); + Type returnType = getMethod.getGenericReturnType(); + stringField = "java.lang.String".equals(returnType.getTypeName()); + } catch (Exception e) { + throw new RuntimeException(e); + } + if (field.isAnnotationPresent(DataMasking.class) && stringField) { + field.setAccessible(true); + fieldList.add(field); + } + } + tempClass = tempClass.getSuperclass(); + } + fieldMap.put(tClass, fieldList); + return fieldList; + } + + public static void main(String[] args) { + System.out.println(dataMasking("a", null)); + System.out.println(dataMasking("ab", null)); + System.out.println(dataMasking("abc", null)); + System.out.println(dataMasking("abcd", null)); + System.out.println(dataMasking("abcde", null)); + } + +} \ No newline at end of file diff --git a/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/service/FileService.java b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/service/FileService.java index 48fb6641..9b30e482 100644 --- a/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/service/FileService.java +++ b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/service/FileService.java @@ -19,9 +19,9 @@ import net.lab1024.sa.base.module.support.file.domain.vo.FileDownloadVO; import net.lab1024.sa.base.module.support.file.domain.vo.FileUploadVO; import net.lab1024.sa.base.module.support.file.domain.vo.FileVO; import net.lab1024.sa.base.module.support.redis.RedisService; +import net.lab1024.sa.base.module.support.securityprotect.service.SecurityFileService; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @@ -39,7 +39,7 @@ import java.util.stream.Collectors; * @Date 2019年10月11日 15:34:47 * @Wechat zhuoda1024 * @Email lab1024@163.com - * @Copyright 1024创新实验室 + * @Copyright 1024创新实验室 */ @Service public class FileService { @@ -58,9 +58,8 @@ public class FileService { @Resource private RedisService redisService; - @Value("${spring.servlet.multipart.max-file-size}") - private String maxFileSize; - + @Resource + private SecurityFileService securityFileService; /** * 文件上传服务 @@ -89,11 +88,10 @@ public class FileService { return ResponseDTO.userErrorParam("文件名称最大长度为:" + FILE_NAME_MAX_LENGTH); } - // 校验文件大小 - String maxSizeStr = maxFileSize.toLowerCase().replace("mb", ""); - long maxSize = Integer.parseInt(maxSizeStr) * 1024 * 1024L; - if (file.getSize() > maxSize) { - return ResponseDTO.userErrorParam("上传文件最大为:" + maxSize); + // 校验文件大小以及安全性 + ResponseDTO validateFile = securityFileService.checkFile(file); + if (!validateFile.getOk()) { + return ResponseDTO.error(validateFile); } // 进行上传 @@ -192,7 +190,7 @@ public class FileService { // 根据文件服务类 获取对应文件服务 查询 url ResponseDTO download = fileStorageService.download(fileKey); - if(download.getOk()){ + if (download.getOk()) { download.getData().getMetadata().setFileName(fileVO.getFileName()); } return download; diff --git a/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/MailService.java b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/MailService.java new file mode 100644 index 00000000..3e2f533e --- /dev/null +++ b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/MailService.java @@ -0,0 +1,179 @@ +package net.lab1024.sa.base.module.support.mail; + + +import cn.hutool.core.lang.UUID; +import freemarker.cache.StringTemplateLoader; +import freemarker.template.Configuration; +import freemarker.template.Template; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.domain.SystemEnvironment; +import net.lab1024.sa.base.module.support.mail.constant.MailTemplateCodeEnum; +import net.lab1024.sa.base.module.support.mail.constant.MailTemplateTypeEnum; +import net.lab1024.sa.base.module.support.mail.domain.MailTemplateEntity; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.text.StringSubstitutor; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; +import java.io.File; +import java.io.StringWriter; +import java.io.Writer; +import java.util.List; +import java.util.Map; + +/** + * + * 发生邮件:
+ * 1、支持直接发送
+ * 2、支持使用邮件模板发送 + * + * @Author 1024创新实验室-创始人兼主任:卓大 + * @Date 2024/8/5 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 ,Since 2012 + */ +@Slf4j +@Component +public class MailService { + + @Autowired + private JavaMailSender javaMailSender; + + @Resource + private MailTemplateDao mailTemplateDao; + + @Resource + private SystemEnvironment systemEnvironment; + + @Value("${spring.mail.username}") + private String clientMail; + + + /** + * 使用模板发送邮件 + */ + public ResponseDTO sendMail(MailTemplateCodeEnum templateCode, Map templateParamsMap, List receiverUserList, List fileList) { + + MailTemplateEntity mailTemplateEntity = mailTemplateDao.selectById(templateCode.name().toLowerCase()); + if (mailTemplateEntity == null) { + return ResponseDTO.userErrorParam("模版不存在"); + } + + if (mailTemplateEntity.getDisableFlag()) { + return ResponseDTO.userErrorParam("模版已禁用,无法发送"); + } + + String content = null; + if (MailTemplateTypeEnum.FREEMARKER.name().equalsIgnoreCase(mailTemplateEntity.getTemplateType().trim())) { + content = freemarkerResolverContent(mailTemplateEntity.getTemplateContent(), templateParamsMap); + } else if (MailTemplateTypeEnum.STRING.name().equalsIgnoreCase(mailTemplateEntity.getTemplateType().trim())) { + content = stringResolverContent(mailTemplateEntity.getTemplateContent(), templateParamsMap); + } else { + return ResponseDTO.userErrorParam("模版类型不存在"); + } + + try { + + this.sendMail(mailTemplateEntity.getTemplateSubject(), content, fileList, receiverUserList, true); + + } catch (Throwable e) { + log.error("邮件发送失败", e); + return ResponseDTO.userErrorParam("邮件发送失败"); + } + return ResponseDTO.ok(); + } + + /** + * 使用模板发送邮件 + */ + public ResponseDTO sendMail(MailTemplateCodeEnum templateCode, Map templateParamsMap, List receiverUserList) { + return this.sendMail(templateCode, templateParamsMap, receiverUserList, null); + } + + + /** + * 发送邮件 + * + * @param subject 主题 + * @param content 内容 + * @param fileList 文件 + * @param receiverUserList 接收方 + * @throws MessagingException + */ + public void sendMail(String subject, String content, List fileList, List receiverUserList, boolean isHtml) throws MessagingException { + + if (CollectionUtils.isEmpty(receiverUserList)) { + throw new RuntimeException("接收方不能为空"); + } + + if (StringUtils.isBlank(content)) { + throw new RuntimeException("邮件内容不能为空"); + } + + if (!systemEnvironment.isProd()) { + subject = "(测试)" + subject; + } + + MimeMessage mimeMessage = javaMailSender.createMimeMessage(); + + //是否为多文件上传 + boolean multiparty = !CollectionUtils.isEmpty(fileList); + MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, multiparty); + helper.setFrom(clientMail); + helper.setTo(receiverUserList.toArray(new String[0])); + helper.setSubject(subject); + //发送html格式 + helper.setText(content, isHtml); + + //附件 + if (multiparty) { + for (File file : fileList) { + helper.addAttachment(file.getName(), file); + } + } + javaMailSender.send(mimeMessage); + } + + /** + * 使用字符串生成最终内容 + */ + private String stringResolverContent(String stringTemplate, Map templateParamsMap) { + StringSubstitutor stringSubstitutor = new StringSubstitutor(templateParamsMap); + String contractHtml = stringSubstitutor.replace(stringTemplate); + Document doc = Jsoup.parse(contractHtml); + doc.outputSettings().syntax(Document.OutputSettings.Syntax.xml); + return doc.outerHtml(); + } + + + /** + * 使用 freemarker 生成最终内容 + */ + private String freemarkerResolverContent(String htmlTemplate, Map templateParamsMap) { + Configuration configuration = new Configuration(Configuration.VERSION_2_3_23); + StringTemplateLoader stringLoader = new StringTemplateLoader(); + String templateName = UUID.fastUUID().toString(true); + stringLoader.putTemplate(templateName, htmlTemplate); + configuration.setTemplateLoader(stringLoader); + try { + Template template = configuration.getTemplate(templateName, "utf-8"); + Writer out = new StringWriter(2048); + template.process(templateParamsMap, out); + return out.toString(); + } catch (Throwable e) { + log.error("freemarkerResolverContent error: ", e); + } + return ""; + } +} diff --git a/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/MailTemplateDao.java b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/MailTemplateDao.java new file mode 100644 index 00000000..d57c1c79 --- /dev/null +++ b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/MailTemplateDao.java @@ -0,0 +1,22 @@ +package net.lab1024.sa.base.module.support.mail; + + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import net.lab1024.sa.base.module.support.mail.domain.MailTemplateEntity; +import org.apache.ibatis.annotations.Mapper; +import org.springframework.stereotype.Component; + +/** + * 邮件模板 + * + * @Author 1024创新实验室-创始人兼主任:卓大 + * @Date 2024/8/5 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 ,Since 2012 + */ +@Mapper +@Component +public interface MailTemplateDao extends BaseMapper { + +} diff --git a/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/constant/MailTemplateCodeEnum.java b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/constant/MailTemplateCodeEnum.java new file mode 100644 index 00000000..9eab6ca6 --- /dev/null +++ b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/constant/MailTemplateCodeEnum.java @@ -0,0 +1,19 @@ +package net.lab1024.sa.base.module.support.mail.constant; + +/** + * 模版编码 + * + * @Author 1024创新实验室-创始人兼主任:卓大 + * @Date 2024/8/5 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 ,Since 2012 + */ +public enum MailTemplateCodeEnum { + + /** + * 登录验证码 + */ + LOGIN_VERIFICATION_CODE + +} diff --git a/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/constant/MailTemplateTypeEnum.java b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/constant/MailTemplateTypeEnum.java new file mode 100644 index 00000000..f26ef24b --- /dev/null +++ b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/constant/MailTemplateTypeEnum.java @@ -0,0 +1,30 @@ +package net.lab1024.sa.base.module.support.mail.constant; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * 邮件模板类型 + * + * @Author 1024创新实验室-创始人兼主任:卓大 + * @Date 2024/8/5 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 ,Since 2012 + */ + +@Getter +@AllArgsConstructor +public enum MailTemplateTypeEnum implements BaseEnum { + + STRING("string", "字符串替代器"), + + FREEMARKER("freemarker", "freemarker模板引擎"); + + private String value; + + private String desc; + + +} \ No newline at end of file diff --git a/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/domain/MailTemplateEntity.java b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/domain/MailTemplateEntity.java new file mode 100644 index 00000000..8f161639 --- /dev/null +++ b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/domain/MailTemplateEntity.java @@ -0,0 +1,51 @@ +package net.lab1024.sa.base.module.support.mail.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * + * 邮件模板 + * + * @Author 1024创新实验室-创始人兼主任:卓大 + * @Date 2024/8/5 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 ,Since 2012 + */ +@Data +@TableName("t_mail_template") +public class MailTemplateEntity { + + @TableId(type = IdType.NONE) + private String templateCode; + + /** + * 主题 + */ + private String templateSubject; + + /** + * 模板类型 + */ + private String templateType; + + /** + * 模板内容 + */ + private String templateContent; + + /** + * 禁用标识 + */ + private Boolean disableFlag; + + + private LocalDateTime updateTime; + + private LocalDateTime createTime; +} diff --git a/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/domain/OperateLogQueryForm.java b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/domain/OperateLogQueryForm.java index 53bdbce7..5e9bc708 100644 --- a/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/domain/OperateLogQueryForm.java +++ b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/domain/OperateLogQueryForm.java @@ -22,6 +22,12 @@ public class OperateLogQueryForm extends PageParam { @Schema(description = "用户类型") private Integer operateUserType; + @Schema(description = "关键字:模块、操作内容") + private String keywords; + + @Schema(description = "请求关键字:请求地址、请求方法、请求参数") + private String requestKeywords; + @Schema(description = "开始日期") private String startDate; diff --git a/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/dao/PasswordLogDao.java b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/dao/PasswordLogDao.java new file mode 100644 index 00000000..8b424eab --- /dev/null +++ b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/dao/PasswordLogDao.java @@ -0,0 +1,34 @@ +package net.lab1024.sa.base.module.support.securityprotect.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import net.lab1024.sa.base.module.support.securityprotect.domain.PasswordLogEntity; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Mapper +@Component +public interface PasswordLogDao extends BaseMapper { + + /** + * 查询最后一次修改密码记录 + * + * @param userType + * @param userId + * @return + */ + PasswordLogEntity selectLastByUserTypeAndUserId(@Param("userType") Integer userType, @Param("userId") Long userId); + + + /** + * 查询最近几次修改后的密码 + * + * @param userType + * @param userId + * @return + */ + List selectOldPassword(@Param("userType") Integer userType, @Param("userId") Long userId, @Param("limit") int limit); + +} diff --git a/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/domain/Level3ProtectConfigForm.java b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/domain/Level3ProtectConfigForm.java new file mode 100644 index 00000000..3f8fa3bf --- /dev/null +++ b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/domain/Level3ProtectConfigForm.java @@ -0,0 +1,58 @@ +package net.lab1024.sa.base.module.support.securityprotect.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotNull; + +/** + * 三级等保相关配置 + * + * @Author 1024创新实验室-创始人兼主任:卓大 + * @Date 2024/7/30 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 ,Since 2012 + */ + +@Data +public class Level3ProtectConfigForm { + + @Schema(description = "连续登录失败次数则锁定") + @NotNull(message = "连续登录失败次数则锁定 不能为空") + private Integer loginFailMaxTimes; + + @Schema(description = "连续登录失败锁定时间(单位:分钟)") + @NotNull(message = "连续登录失败锁定时间(单位:分钟) 不能为空") + private Integer loginFailLockMinutes; + + @Schema(description = "最低活跃时间(单位:分钟)") + @NotNull(message = "最低活跃时间(单位:分钟) 不能为空") + private Integer loginActiveTimeoutMinutes; + + @Schema(description = "开启双因子登录") + @NotNull(message = "开启双因子登录 不能为空") + private Boolean twoFactorLoginEnabled; + + @Schema(description = "密码复杂度 是否开启,默认:开启") + @NotNull(message = "密码复杂度 是否开启 不能为空") + private Boolean passwordComplexityEnabled; + + @Schema(description = "定期修改密码时间间隔(默认:月)") + @NotNull(message = "定期修改密码时间间隔(默认:月) 不能为空") + private Integer regularChangePasswordMonths; + + @Schema(description = "定期修改密码不允许重复次数,默认:3次以内密码不能相同(默认:次)") + @NotNull(message = "定期修改密码不允许重复次数 不能为空") + private Integer regularChangePasswordNotAllowRepeatTimes; + + @Schema(description = "文件检测,默认:不开启") + @NotNull(message = "文件检测 是否开启 不能为空") + private Boolean fileDetectFlag; + + @Schema(description = "文件大小限制,单位 mb ,(默认:50 mb)") + @NotNull(message = "文件大小限制 不能为空") + private Long maxUploadFileSizeMb; + + +} diff --git a/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/domain/PasswordLogEntity.java b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/domain/PasswordLogEntity.java new file mode 100644 index 00000000..93927dc6 --- /dev/null +++ b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/domain/PasswordLogEntity.java @@ -0,0 +1,43 @@ +package net.lab1024.sa.base.module.support.securityprotect.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * @author yandy + * @description: + * @date 2024/7/15 1:39 下午 + */ +@Data +@TableName("t_password_log") +public class PasswordLogEntity { + + /** + * 主键id + */ + @TableId(type = IdType.AUTO) + private Long id; + + private Integer userType; + + private Long userId; + + private String oldPassword; + + private String newPassword; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + +} \ No newline at end of file diff --git a/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/Level3ProtectConfigService.java b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/Level3ProtectConfigService.java new file mode 100644 index 00000000..37bba688 --- /dev/null +++ b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/Level3ProtectConfigService.java @@ -0,0 +1,207 @@ +package net.lab1024.sa.base.module.support.securityprotect.service; + +import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.domain.ResponseDTO; +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 org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; + +/** + * 三级等保配置 + * + * @Author 1024创新实验室-创始人兼主任:卓大 + * @Date 2024/7/30 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 ,Since 2012 + */ + +@Service +@Slf4j +public class Level3ProtectConfigService { + + /** + * 开启双因子登录,默认:开启 + */ + private boolean twoFactorLoginEnabled = false; + + /** + * 连续登录失败次数则锁定,-1表示不受限制,可以一直尝试登录 + */ + private int loginFailMaxTimes = -1; + + /** + * 连续登录失败锁定时间(单位:秒),-1表示不锁定,建议锁定30分钟 + */ + private int loginFailLockSeconds = 1800; + + /** + * 最低活跃时间(单位:秒),超过此时间没有操作系统就会被冻结,默认-1 代表不限制,永不冻结; 默认 30分钟 + */ + private int loginActiveTimeoutSeconds = 1800; + + /** + * 密码复杂度 是否开启,默认:开启 + */ + private boolean passwordComplexityEnabled = true; + + /** + * 定期修改密码时间间隔(默认:天),默认:建议90天更换密码 + */ + private int regularChangePasswordDays = 90; + + /** + * 定期修改密码不允许相同次数,默认:3次以内密码不能相同 + */ + private int regularChangePasswordNotAllowRepeatTimes = 3; + + /** + * 文件大小限制,单位 mb ,(默认:50 mb) + */ + private long maxUploadFileSizeMb = 50; + + /** + * 文件检测,默认:不开启 + */ + private boolean fileDetectFlag = false; + + + @Resource + private ConfigService configService; + + /** + * 文件检测,默认:不开启 + */ + public boolean isFileDetectFlag() { + return fileDetectFlag; + } + + /** + * 文件大小限制,单位 mb ,(默认:50 mb) + */ + public long getMaxUploadFileSizeMb() { + return maxUploadFileSizeMb; + } + + /** + * 连续登录失败次数则锁定,-1表示不受限制,可以一直尝试登录 + */ + public int getLoginFailMaxTimes() { + return loginFailMaxTimes; + } + + /** + * 连续登录失败锁定时间(单位:秒),-1表示不锁定,建议锁定30分钟 + */ + public int getLoginFailLockSeconds() { + return loginFailLockSeconds; + } + + /** + * 最低活跃时间(单位:秒),超过此时间没有操作系统就会被冻结,默认-1 代表不限制,永不冻结; 默认 30分钟 + */ + public int getLoginActiveTimeoutSeconds() { + return loginActiveTimeoutSeconds; + } + + /** + * 定期修改密码时间间隔(默认:天),默认:建议90天更换密码 + */ + public int getRegularChangePasswordDays() { + return regularChangePasswordDays; + } + + /** + * 开启双因子登录,默认:开启 + */ + public boolean isTwoFactorLoginEnabled() { + return twoFactorLoginEnabled; + } + + /** + * 密码复杂度 是否开启,默认:开启 + */ + public boolean isPasswordComplexityEnabled() { + return passwordComplexityEnabled; + } + + /** + * 定期修改密码不允许相同次数,默认:3次以内密码不能相同 + */ + public int getRegularChangePasswordNotAllowRepeatTimes() { + return regularChangePasswordNotAllowRepeatTimes; + } + + @PostConstruct + void init() { + String configValue = configService.getConfigValue(ConfigKeyEnum.LEVEL3_PROTECT_CONFIG); + if (StrUtil.isEmpty(configValue)) { + throw new ExceptionInInitializerError("t_config 表 三级等保配置为空,请进行配置!"); + } + Level3ProtectConfigForm level3ProtectConfigForm = JSON.parseObject(configValue, Level3ProtectConfigForm.class); + setProp(level3ProtectConfigForm); + } + + /** + * 设置属性 + */ + private void setProp(Level3ProtectConfigForm configForm) { + + if (configForm.getFileDetectFlag() != null) { + this.fileDetectFlag = configForm.getFileDetectFlag(); + } + + if (configForm.getMaxUploadFileSizeMb() != null) { + this.maxUploadFileSizeMb = configForm.getMaxUploadFileSizeMb(); + } + + if (configForm.getLoginFailLockMinutes() != null) { + this.loginFailLockSeconds = configForm.getLoginFailLockMinutes() * 60; + } + + if (configForm.getLoginActiveTimeoutMinutes() != null) { + this.loginActiveTimeoutSeconds = configForm.getLoginActiveTimeoutMinutes() * 60; + } + + if (configForm.getPasswordComplexityEnabled() != null) { + this.passwordComplexityEnabled = configForm.getPasswordComplexityEnabled(); + } + + if (configForm.getRegularChangePasswordMonths() != null) { + this.regularChangePasswordDays = configForm.getRegularChangePasswordMonths() * 30; + } + + if (configForm.getTwoFactorLoginEnabled() != null) { + this.twoFactorLoginEnabled = configForm.getTwoFactorLoginEnabled(); + } + + if (configForm.getRegularChangePasswordNotAllowRepeatTimes() != null) { + this.regularChangePasswordNotAllowRepeatTimes = configForm.getRegularChangePasswordNotAllowRepeatTimes(); + } + + // 设置 最低活跃时间(单位:秒) + if (this.loginActiveTimeoutSeconds > 0) { + StpUtil.getStpLogic().getConfigOrGlobal().setActiveTimeout(getLoginActiveTimeoutSeconds()); + } else { + StpUtil.getStpLogic().getConfigOrGlobal().setActiveTimeout(-1); + } + } + + /** + * 更新三级等保配置 + */ + public ResponseDTO updateLevel3Config(Level3ProtectConfigForm configForm) { + // 设置属性 + setProp(configForm); + // 保存数据库 + String configFormJsonString = JSON.toJSONString(configForm, true); + return configService.updateValueByKey(ConfigKeyEnum.LEVEL3_PROTECT_CONFIG, configFormJsonString); + } +} diff --git a/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/ProtectPasswordService.java b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/ProtectPasswordService.java deleted file mode 100644 index 80549654..00000000 --- a/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/ProtectPasswordService.java +++ /dev/null @@ -1,99 +0,0 @@ -package net.lab1024.sa.base.module.support.securityprotect.service; - -import net.lab1024.sa.base.common.domain.ResponseDTO; -import net.lab1024.sa.base.common.util.SmartStringUtil; -import net.lab1024.sa.base.module.support.apiencrypt.service.ApiEncryptService; -import org.apache.commons.lang3.RandomStringUtils; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; - -import javax.annotation.Resource; - -/** - * 三级等保 密码 相关 - * - * @Author 1024创新实验室-主任:卓大 - * @Date 2023/10/11 19:25:59 - * @Wechat zhuoda1024 - * @Email lab1024@163.com - * @Copyright 1024创新实验室,Since 2012 - */ - -@Service -public class ProtectPasswordService { - - /** - * 密码长度8-20位且包含大写字母、小写字母、数字三种 - */ - public static final String PASSWORD_PATTERN = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d]{8,20}$"; - - /** - * 密码长度8-20位且包含大写字母、小写字母、数字三种 - */ - public static final String PASSWORD_FORMAT_MSG = "密码必须为长度8-20位且包含大写字母、小写字母、数字三种"; - - - private static final int PASSWORD_LENGTH = 8; - - - /** - * 密码复杂度开启, 默认为true 开启,false 不开启 - */ - @Value("${classified-protect.password-complexity-enabled}") - private Boolean passwordComplexityEnabled; - - - @Resource - private ApiEncryptService apiEncryptService; - - /** - * 校验密码复杂度 - * - * @return - */ - public ResponseDTO validatePassComplexity(String password) { - - // 无需校验 - if (!passwordComplexityEnabled) { - return ResponseDTO.ok(); - } - - if (SmartStringUtil.isEmpty(password)) { - return ResponseDTO.userErrorParam(PASSWORD_FORMAT_MSG); - } - - if (!password.matches(PASSWORD_PATTERN)) { - return ResponseDTO.userErrorParam(PASSWORD_FORMAT_MSG); - } - - return ResponseDTO.ok(); - } - - /** - * 随机生成密码 - * - * @return - */ - public String randomPassword() { - // 未开启密码复杂度,则由8为数字构成 - if (passwordComplexityEnabled) { - return RandomStringUtils.randomNumeric(PASSWORD_LENGTH); - } else { - // 3位大写字母,2位数字,3位小写字母 - return RandomStringUtils.randomAlphabetic(3).toUpperCase() + RandomStringUtils.randomNumeric(2) + RandomStringUtils.randomAlphabetic(3).toLowerCase(); - } - } - - - /** - * 解密 SM4 or AES 加密过的密码 - * - * @param encryptedPassword - * @return - */ - public String decryptPassword(String encryptedPassword) { - return apiEncryptService.decrypt(encryptedPassword); - } - - -} diff --git a/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/SecurityFileService.java b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/SecurityFileService.java new file mode 100644 index 00000000..cdea03a4 --- /dev/null +++ b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/SecurityFileService.java @@ -0,0 +1,52 @@ +package net.lab1024.sa.base.module.support.securityprotect.service; + +import net.lab1024.sa.base.common.domain.ResponseDTO; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import java.io.File; + +/** + * 三级等保 文件上传 相关 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2024/08/22 19:25:59 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室,Since 2012 + */ + +@Service +public class SecurityFileService { + + @Resource + private Level3ProtectConfigService level3ProtectConfigService; + + + /** + * 检测文件安全类型 + */ + public ResponseDTO checkFile(MultipartFile file) { + + // 检验文件大小 + if (level3ProtectConfigService.getMaxUploadFileSizeMb() > 0) { + long maxSize = level3ProtectConfigService.getMaxUploadFileSizeMb() * 1024 * 1024; + if (file.getSize() > maxSize) { + return ResponseDTO.userErrorParam("上传文件最大为:" + level3ProtectConfigService.getMaxUploadFileSizeMb() + " mb"); + } + } + + // 文件类型安全检测 + if (!level3ProtectConfigService.isFileDetectFlag()) { + return ResponseDTO.ok(); + } + + // 检测文件类型 + // ..... + + return ResponseDTO.ok(); + } + +} +; \ No newline at end of file diff --git a/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/ProtectLoginService.java b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/SecurityLoginService.java similarity index 63% rename from smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/ProtectLoginService.java rename to smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/SecurityLoginService.java index c20d5e8d..4088ff29 100644 --- a/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/ProtectLoginService.java +++ b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/SecurityLoginService.java @@ -12,7 +12,6 @@ import net.lab1024.sa.base.module.support.securityprotect.domain.LoginFailEntity import net.lab1024.sa.base.module.support.securityprotect.domain.LoginFailQueryForm; import net.lab1024.sa.base.module.support.securityprotect.domain.LoginFailVO; import org.apache.commons.collections4.CollectionUtils; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import javax.annotation.Resource; @@ -26,27 +25,18 @@ import java.util.List; * @Date 2023/10/11 19:25:59 * @Wechat zhuoda1024 * @Email lab1024@163.com - * @Copyright 1024创新实验室,Since 2012 + * @Copyright 1024创新实验室,Since 2012 */ @Service -public class ProtectLoginService { +public class SecurityLoginService { private static final String LOGIN_LOCK_MSG = "您已连续登录失败%s次,账号锁定%s分钟,解锁时间为:%s,请您耐心等待!"; private static final String LOGIN_FAIL_MSG = "登录名或密码错误!连续登录失败%s次,账号将锁定%s分钟!您还可以再尝试%s次!"; - /** - * 连续登录失败次数则锁定,-1表示不受限制,可以一直登录 - */ - @Value("${classified-protect.login-max-fail-times}") - private Integer loginMaxFailTimes; - - /** - * 连续登录失败锁定时间(单位:秒),-1表示不锁定 - */ - @Value("${classified-protect.login-fail-locked-seconds}") - private Integer loginFailLockedSeconds; + @Resource + private Level3ProtectConfigService level3ProtectConfigService; @Resource private LoginFailDao loginFailDao; @@ -61,8 +51,8 @@ public class ProtectLoginService { */ public ResponseDTO checkLogin(Long userId, UserTypeEnum userType) { - // 无需校验 - if (loginMaxFailTimes < 1) { + // 若登录最大失败次数小于1,无需校验 + if (level3ProtectConfigService.getLoginFailMaxTimes() < 1) { return ResponseDTO.ok(); } @@ -72,19 +62,24 @@ public class ProtectLoginService { return ResponseDTO.ok(); } - // 校验次数 - if (loginFailEntity.getLoginFailCount() < loginMaxFailTimes) { + // 校验登录失败次数 + if (loginFailEntity.getLoginFailCount() < level3ProtectConfigService.getLoginFailMaxTimes()) { + return ResponseDTO.ok(loginFailEntity); + } + + // 校验是否锁定 + if (loginFailEntity.getLoginLockBeginTime() == null) { return ResponseDTO.ok(loginFailEntity); } // 校验锁定时长 - if(loginFailEntity.getLoginLockBeginTime().plusSeconds(loginFailLockedSeconds).isBefore(LocalDateTime.now())){ + if (loginFailEntity.getLoginLockBeginTime().plusSeconds(level3ProtectConfigService.getLoginFailLockSeconds()).isBefore(LocalDateTime.now())) { // 过了锁定时间 return ResponseDTO.ok(loginFailEntity); } - LocalDateTime unlockTime = loginFailEntity.getLoginLockBeginTime().plusSeconds(loginFailLockedSeconds); - return ResponseDTO.error(UserErrorCode.LOGIN_FAIL_LOCK, String.format(LOGIN_LOCK_MSG, loginFailEntity.getLoginFailCount(), loginFailLockedSeconds / 60, LocalDateTimeUtil.formatNormal(unlockTime))); + LocalDateTime unlockTime = loginFailEntity.getLoginLockBeginTime().plusSeconds(level3ProtectConfigService.getLoginFailLockSeconds()); + return ResponseDTO.error(UserErrorCode.LOGIN_FAIL_LOCK, String.format(LOGIN_LOCK_MSG, loginFailEntity.getLoginFailCount(), level3ProtectConfigService.getLoginFailLockSeconds() / 60, LocalDateTimeUtil.formatNormal(unlockTime))); } /** @@ -96,43 +91,40 @@ public class ProtectLoginService { */ public String recordLoginFail(Long userId, UserTypeEnum userType, String loginName, LoginFailEntity loginFailEntity) { - // 无需校验 - if (loginMaxFailTimes < 1) { + // 若登录最大失败次数小于1,无需记录 + if (level3ProtectConfigService.getLoginFailMaxTimes() < 1) { return null; } + // 登录失败 + int loginFailCount = loginFailEntity == null ? 1 : loginFailEntity.getLoginFailCount() + 1; + boolean lockFlag = loginFailCount >= level3ProtectConfigService.getLoginFailMaxTimes(); + LocalDateTime lockBeginTime = lockFlag ? LocalDateTime.now() : null; + if (loginFailEntity == null) { loginFailEntity = LoginFailEntity.builder() .userId(userId) .userType(userType.getValue()) .loginName(loginName) - .loginFailCount(1) - .lockFlag(false) - .loginLockBeginTime(null).build(); + .loginFailCount(loginFailCount) + .lockFlag(lockFlag) + .loginLockBeginTime(lockBeginTime) + .build(); loginFailDao.insert(loginFailEntity); } else { - - // 如果是已经锁定状态,则重新计算 - if(loginFailEntity.getLockFlag()){ - loginFailEntity.setLockFlag(false); - loginFailEntity.setLoginFailCount(1); - loginFailEntity.setLoginLockBeginTime(null); - }else{ - loginFailEntity.setLoginLockBeginTime(LocalDateTime.now()); - loginFailEntity.setLoginFailCount(loginFailEntity.getLoginFailCount() + 1); - loginFailEntity.setLockFlag(loginFailEntity.getLoginFailCount() >= loginMaxFailTimes); - } - + loginFailEntity.setLoginLockBeginTime(lockBeginTime); + loginFailEntity.setLoginFailCount(loginFailCount); + loginFailEntity.setLockFlag(lockFlag); loginFailEntity.setLoginName(loginName); loginFailDao.updateById(loginFailEntity); } // 提示信息 - if (loginFailEntity.getLoginFailCount() >= loginMaxFailTimes) { - LocalDateTime unlockTime = loginFailEntity.getLoginLockBeginTime().plusSeconds(loginFailLockedSeconds); - return String.format(LOGIN_LOCK_MSG, loginFailEntity.getLoginFailCount(), loginFailLockedSeconds / 60, LocalDateTimeUtil.formatNormal(unlockTime)); + if (lockFlag) { + LocalDateTime unlockTime = loginFailEntity.getLoginLockBeginTime().plusSeconds(level3ProtectConfigService.getLoginFailLockSeconds()); + return String.format(LOGIN_LOCK_MSG, loginFailEntity.getLoginFailCount(), level3ProtectConfigService.getLoginFailLockSeconds() / 60, LocalDateTimeUtil.formatNormal(unlockTime)); } else { - return String.format(LOGIN_FAIL_MSG, loginMaxFailTimes, loginFailLockedSeconds / 60, loginMaxFailTimes - loginFailEntity.getLoginFailCount()); + return String.format(LOGIN_FAIL_MSG, level3ProtectConfigService.getLoginFailMaxTimes(), level3ProtectConfigService.getLoginFailLockSeconds() / 60, level3ProtectConfigService.getLoginFailMaxTimes() - loginFailEntity.getLoginFailCount()); } } @@ -143,8 +135,9 @@ public class ProtectLoginService { * @param userType */ public void removeLoginFail(Long userId, UserTypeEnum userType) { - // 无需校验 - if (loginMaxFailTimes < 1) { + + // 若登录最大失败次数小于1,无需校验 + if (level3ProtectConfigService.getLoginFailMaxTimes() < 1) { return; } @@ -160,8 +153,7 @@ public class ProtectLoginService { public PageResult queryPage(LoginFailQueryForm queryForm) { Page page = SmartPageUtil.convert2PageQuery(queryForm); List list = loginFailDao.queryPage(page, queryForm); - PageResult pageResult = SmartPageUtil.convert2PageResult(page, list); - return pageResult; + return SmartPageUtil.convert2PageResult(page, list); } /** diff --git a/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/SecurityPasswordService.java b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/SecurityPasswordService.java new file mode 100644 index 00000000..3d371b44 --- /dev/null +++ b/smart-admin-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/SecurityPasswordService.java @@ -0,0 +1,149 @@ +package net.lab1024.sa.base.module.support.securityprotect.service; + +import net.lab1024.sa.base.common.domain.RequestUser; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import net.lab1024.sa.base.module.support.securityprotect.dao.PasswordLogDao; +import net.lab1024.sa.base.module.support.securityprotect.domain.PasswordLogEntity; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.lang3.RandomStringUtils; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.time.LocalDateTime; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +/** + * 三级等保 密码 相关 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2023/10/11 19:25:59 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室,Since 2012 + */ + +@Service +public class SecurityPasswordService { + + /** + * 密码长度8-20位且包含大小写字母、数字、特殊符号三种及以上组合 + */ + public static final String PASSWORD_PATTERN = "^(?![a-zA-Z]+$)(?![A-Z0-9]+$)(?![A-Z\\W_!@#$%^&*`~()-+=]+$)(?![a-z0-9]+$)(?![a-z\\W_!@#$%^&*`~()-+=]+$)(?![0-9\\W_!@#$%^&*`~()-+=]+$)[a-zA-Z0-9\\W_!@#$%^&*`~()-+=]*$"; + + public static final String PASSWORD_FORMAT_MSG = "密码必须为长度8-20位且必须包含大小写字母、数字、特殊符号(如:@#$%^&*()_+-=)等三种字符"; + + + private static final int PASSWORD_LENGTH = 8; + + private static final String PASSWORD_SALT_FORMAT = "smart_%s_admin_$^&*"; + + + @Resource + private PasswordLogDao passwordLogDao; + + @Resource + private Level3ProtectConfigService level3ProtectConfigService; + + /** + * 校验密码复杂度 + */ + public ResponseDTO validatePasswordComplexity(String password) { + + if (SmartStringUtil.isEmpty(password)) { + return ResponseDTO.userErrorParam(PASSWORD_FORMAT_MSG); + } + + // 密码长度必须大于等于8位 + if (password.length() < PASSWORD_LENGTH) { + return ResponseDTO.userErrorParam(PASSWORD_FORMAT_MSG); + } + + // 无需校验 密码复杂度 + if (!level3ProtectConfigService.isPasswordComplexityEnabled()) { + return ResponseDTO.ok(); + } + + if (!password.matches(PASSWORD_PATTERN)) { + return ResponseDTO.userErrorParam(PASSWORD_FORMAT_MSG); + } + + return ResponseDTO.ok(); + } + + /** + * 校验密码重复次数 + */ + public ResponseDTO validatePasswordRepeatTimes(RequestUser requestUser, String newPassword) { + + // 密码重复次数小于1 无需校验 + if (level3ProtectConfigService.getRegularChangePasswordNotAllowRepeatTimes() < 1) { + return ResponseDTO.ok(); + } + + // 检查最近几次是否有重复密码 + List oldPasswords = passwordLogDao.selectOldPassword(requestUser.getUserType().getValue(), requestUser.getUserId(), level3ProtectConfigService.getRegularChangePasswordNotAllowRepeatTimes()); + if (oldPasswords != null && oldPasswords.contains(getEncryptPwd(newPassword))) { + return ResponseDTO.userErrorParam(String.format("与前%s个历史密码重复,请换个密码!", level3ProtectConfigService.getRegularChangePasswordNotAllowRepeatTimes())); + } + + return ResponseDTO.ok(); + } + + /** + * 随机生成密码 + */ + public String randomPassword() { + // 未开启密码复杂度,则由8为数字构成 + if (!level3ProtectConfigService.isPasswordComplexityEnabled()) { + return RandomStringUtils.randomNumeric(PASSWORD_LENGTH); + } + + // 3位大写字母,2位数字,2位小写字母 + 1位特殊符号 + return RandomStringUtils.randomAlphabetic(3).toUpperCase() + + RandomStringUtils.randomNumeric(2) + + RandomStringUtils.randomAlphabetic(2).toLowerCase() + + (ThreadLocalRandom.current().nextBoolean() ? "#" : "@"); + } + + + /** + * 保存修改密码 + */ + public void saveUserChangePasswordLog(RequestUser requestUser, String newPassword, String oldPassword) { + + PasswordLogEntity passwordLogEntity = new PasswordLogEntity(); + passwordLogEntity.setNewPassword(newPassword); + passwordLogEntity.setOldPassword(oldPassword); + passwordLogEntity.setUserId(requestUser.getUserId()); + passwordLogEntity.setUserType(requestUser.getUserType().getValue()); + passwordLogDao.insert(passwordLogEntity); + } + + /** + * 检查是否需要修改密码 + */ + public boolean checkNeedChangePassword(Integer userType, Long userId) { + + if (level3ProtectConfigService.getRegularChangePasswordDays() < 1) { + return false; + } + + PasswordLogEntity passwordLogEntity = passwordLogDao.selectLastByUserTypeAndUserId(userType, userId); + if (passwordLogEntity == null) { + return false; + } + + LocalDateTime nextUpdateTime = passwordLogEntity.getCreateTime().plusDays(level3ProtectConfigService.getRegularChangePasswordDays()); + return nextUpdateTime.isBefore(LocalDateTime.now()); + } + + /** + * 获取 加密后 的密码 + */ + public static String getEncryptPwd(String password) { + return DigestUtils.md5Hex(String.format(PASSWORD_SALT_FORMAT, password)); + } + +} diff --git a/smart-admin-api/sa-base/src/main/resources/dev/sa-base.yaml b/smart-admin-api/sa-base/src/main/resources/dev/sa-base.yaml index ed7d3064..5dacafb1 100644 --- a/smart-admin-api/sa-base/src/main/resources/dev/sa-base.yaml +++ b/smart-admin-api/sa-base/src/main/resources/dev/sa-base.yaml @@ -1,9 +1,9 @@ spring: # 数据库连接信息 datasource: - url: jdbc:p6spy:mysql://127.0.0.1:3306/smart_admin_v3?autoReconnect=true&useServerPreparedStmts=false&rewriteBatchedStatements=true&characterEncoding=UTF-8&useSSL=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai + url: jdbc:p6spy:mysql://47.96.105.74:11024/smart_admin_v3_dev?autoReconnect=true&useServerPreparedStmts=false&rewriteBatchedStatements=true&characterEncoding=UTF-8&useSSL=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai username: root - password: Zhuoda1024lab + password: 11024Lab initial-size: 2 min-idle: 2 max-active: 10 @@ -22,10 +22,10 @@ spring: # redis 连接池配置信息 redis: - database: 1 - host: 127.0.0.1 - port: 6379 - password: + database: 7 + host: 47.96.105.74 + port: 6666 + password: ASDasd123 timeout: 10000ms lettuce: pool: @@ -34,11 +34,22 @@ spring: max-idle: 3 max-wait: 30000ms - # 上传文件大小配置 - servlet: - multipart: - max-file-size: 30MB - max-request-size: 30MB + # 邮件,置以SSL的方式发送, 这个需要使用这种方式并且端口是465 + mail: + host: smtp.163.com + port: 465 + username: lab1024@163.com + password: ROIMSIQCEXHTQFTA + properties: + mail: + smtp: + auth: true + ssl: + enable: true + socketFactory: + class: com.sun.mail.util.MailSSLSocketFactory + fallback: false + debug: false # json序列化相关配置 jackson: @@ -76,8 +87,8 @@ file: region: oss-cn-hangzhou endpoint: oss-cn-hangzhou.aliyuncs.com bucket-name: 1024lab-smart-admin - access-key: - secret-key: + access-key: LTAI5tBAbehjXWyAqLhc58e1 + secret-key: asX6ZWutaoTbQL3GxsFs24CmfAcYu3 url-prefix: https://${file.storage.cloud.bucket-name}.${file.storage.cloud.endpoint}/ private-url-expire-seconds: 3600 @@ -87,6 +98,7 @@ springdoc: enabled: true # 开关 doc-expansion: none #关闭展开 tags-sorter: alpha + server-base-url: api-docs: enabled: true # 开关 knife4j: diff --git a/smart-admin-api/sa-base/src/main/resources/mapper/support/OperateLogMapper.xml b/smart-admin-api/sa-base/src/main/resources/mapper/support/OperateLogMapper.xml index 1d22bee1..fcf1d8e1 100644 --- a/smart-admin-api/sa-base/src/main/resources/mapper/support/OperateLogMapper.xml +++ b/smart-admin-api/sa-base/src/main/resources/mapper/support/OperateLogMapper.xml @@ -22,6 +22,12 @@ AND INSTR(operate_user_name,#{query.userName}) + + AND (INSTR(`module`,#{query.keywords}) OR INSTR(content,#{query.keywords})) + + + AND (INSTR(`url`,#{query.requestKeywords}) OR INSTR(`method`,#{query.requestKeywords}) OR INSTR(`param`,#{query.requestKeywords})) + AND success_flag = #{query.successFlag} diff --git a/smart-admin-api/sa-base/src/main/resources/mapper/support/PasswordLogMapper.xml b/smart-admin-api/sa-base/src/main/resources/mapper/support/PasswordLogMapper.xml new file mode 100644 index 00000000..84ef7980 --- /dev/null +++ b/smart-admin-api/sa-base/src/main/resources/mapper/support/PasswordLogMapper.xml @@ -0,0 +1,28 @@ + + + + + + + + + \ No newline at end of file diff --git a/smart-admin-api/sa-base/src/main/resources/pre/sa-base.yaml b/smart-admin-api/sa-base/src/main/resources/pre/sa-base.yaml index 257f8ac5..5dacafb1 100644 --- a/smart-admin-api/sa-base/src/main/resources/pre/sa-base.yaml +++ b/smart-admin-api/sa-base/src/main/resources/pre/sa-base.yaml @@ -1,9 +1,9 @@ spring: # 数据库连接信息 datasource: - url: jdbc:p6spy:mysql://127.0.0.1:3306/smart_admin_v3?autoReconnect=true&useServerPreparedStmts=false&rewriteBatchedStatements=true&characterEncoding=UTF-8&useSSL=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai + url: jdbc:p6spy:mysql://47.96.105.74:11024/smart_admin_v3_dev?autoReconnect=true&useServerPreparedStmts=false&rewriteBatchedStatements=true&characterEncoding=UTF-8&useSSL=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai username: root - password: Zhuoda1024lab + password: 11024Lab initial-size: 2 min-idle: 2 max-active: 10 @@ -22,10 +22,10 @@ spring: # redis 连接池配置信息 redis: - database: 1 - host: 127.0.0.1 - port: 6379 - password: + database: 7 + host: 47.96.105.74 + port: 6666 + password: ASDasd123 timeout: 10000ms lettuce: pool: @@ -34,11 +34,22 @@ spring: max-idle: 3 max-wait: 30000ms - # 上传文件大小配置 - servlet: - multipart: - max-file-size: 30MB - max-request-size: 30MB + # 邮件,置以SSL的方式发送, 这个需要使用这种方式并且端口是465 + mail: + host: smtp.163.com + port: 465 + username: lab1024@163.com + password: ROIMSIQCEXHTQFTA + properties: + mail: + smtp: + auth: true + ssl: + enable: true + socketFactory: + class: com.sun.mail.util.MailSSLSocketFactory + fallback: false + debug: false # json序列化相关配置 jackson: @@ -65,7 +76,6 @@ server: max-days: 7 pattern: "%t %{X-Forwarded-For}i %a %r %s (%D ms) %I (%B byte)" - # 文件上传 配置 file: storage: @@ -77,24 +87,24 @@ file: region: oss-cn-hangzhou endpoint: oss-cn-hangzhou.aliyuncs.com bucket-name: 1024lab-smart-admin - access-key: - secret-key: + access-key: LTAI5tBAbehjXWyAqLhc58e1 + secret-key: asX6ZWutaoTbQL3GxsFs24CmfAcYu3 url-prefix: https://${file.storage.cloud.bucket-name}.${file.storage.cloud.endpoint}/ private-url-expire-seconds: 3600 - # open api配置 springdoc: swagger-ui: enabled: true # 开关 doc-expansion: none #关闭展开 tags-sorter: alpha + server-base-url: api-docs: enabled: true # 开关 knife4j: enable: true basic: - enable: true + enable: false username: api # Basic认证用户名 password: 1024 # Basic认证密码 @@ -112,11 +122,11 @@ access-control-allow-origin: '*' # 心跳配置 heart-beat: - interval-seconds: 60 + interval-seconds: 300 # 热加载配置 reload: - interval-seconds: 60 + interval-seconds: 300 # sa-token 配置 sa-token: @@ -135,9 +145,9 @@ sa-token: # 是否打开自动续签 (如果此值为true,框架会在每次直接或间接调用 getLoginId() 时进行一次过期检查与续签操作) auto-renew: true # 是否输出操作日志 - is-log: false + is-log: true # 日志等级(trace、debug、info、warn、error、fatal) - log-level: warn + log-level: debug # 启动时的字符画打印 is-print: false # 是否从cookie读取token diff --git a/smart-admin-api/sa-base/src/main/resources/prod/sa-base.yaml b/smart-admin-api/sa-base/src/main/resources/prod/sa-base.yaml index 4758a75d..af8fa98d 100644 --- a/smart-admin-api/sa-base/src/main/resources/prod/sa-base.yaml +++ b/smart-admin-api/sa-base/src/main/resources/prod/sa-base.yaml @@ -33,12 +33,22 @@ spring: min-idle: 10 max-idle: 50 max-wait: 30000ms - - # 上传文件大小配置 - servlet: - multipart: - max-file-size: 30MB - max-request-size: 30MB + # 邮件,置以SSL的方式发送, 这个需要使用这种方式并且端口是465 + mail: + host: smtp.163.com + port: 465 + username: lab1024@163.com + password: 1024lab + properties: + mail: + smtp: + auth: true + ssl: + enable: true + socketFactory: + class: com.sun.mail.util.MailSSLSocketFactory + fallback: false + debug: false # json序列化相关配置 jackson: diff --git a/smart-admin-api/sa-base/src/main/resources/test/sa-base.yaml b/smart-admin-api/sa-base/src/main/resources/test/sa-base.yaml index e9fc251d..5dacafb1 100644 --- a/smart-admin-api/sa-base/src/main/resources/test/sa-base.yaml +++ b/smart-admin-api/sa-base/src/main/resources/test/sa-base.yaml @@ -1,9 +1,9 @@ spring: # 数据库连接信息 datasource: - url: jdbc:p6spy:mysql://127.0.0.1:3306/smart_admin_v3?autoReconnect=true&useServerPreparedStmts=false&rewriteBatchedStatements=true&characterEncoding=UTF-8&useSSL=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai + url: jdbc:p6spy:mysql://47.96.105.74:11024/smart_admin_v3_dev?autoReconnect=true&useServerPreparedStmts=false&rewriteBatchedStatements=true&characterEncoding=UTF-8&useSSL=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai username: root - password: Zhuoda1024lab + password: 11024Lab initial-size: 2 min-idle: 2 max-active: 10 @@ -22,10 +22,10 @@ spring: # redis 连接池配置信息 redis: - database: 1 - host: 127.0.0.1 - port: 6379 - password: + database: 7 + host: 47.96.105.74 + port: 6666 + password: ASDasd123 timeout: 10000ms lettuce: pool: @@ -34,11 +34,22 @@ spring: max-idle: 3 max-wait: 30000ms - # 上传文件大小配置 - servlet: - multipart: - max-file-size: 30MB - max-request-size: 30MB + # 邮件,置以SSL的方式发送, 这个需要使用这种方式并且端口是465 + mail: + host: smtp.163.com + port: 465 + username: lab1024@163.com + password: ROIMSIQCEXHTQFTA + properties: + mail: + smtp: + auth: true + ssl: + enable: true + socketFactory: + class: com.sun.mail.util.MailSSLSocketFactory + fallback: false + debug: false # json序列化相关配置 jackson: @@ -65,7 +76,6 @@ server: max-days: 7 pattern: "%t %{X-Forwarded-For}i %a %r %s (%D ms) %I (%B byte)" - # 文件上传 配置 file: storage: @@ -77,25 +87,24 @@ file: region: oss-cn-hangzhou endpoint: oss-cn-hangzhou.aliyuncs.com bucket-name: 1024lab-smart-admin - access-key: - secret-key: + access-key: LTAI5tBAbehjXWyAqLhc58e1 + secret-key: asX6ZWutaoTbQL3GxsFs24CmfAcYu3 url-prefix: https://${file.storage.cloud.bucket-name}.${file.storage.cloud.endpoint}/ private-url-expire-seconds: 3600 - # open api配置 springdoc: swagger-ui: enabled: true # 开关 doc-expansion: none #关闭展开 tags-sorter: alpha - server-base-url: http://smartadmin.dev.1024lab.net/api/ + server-base-url: api-docs: enabled: true # 开关 knife4j: enable: true basic: - enable: true + enable: false username: api # Basic认证用户名 password: 1024 # Basic认证密码 diff --git a/smart-admin-web/javascript-ant-design-vue3/.env.development b/smart-admin-web/javascript-ant-design-vue3/.env.development index df615c53..1058a48f 100644 --- a/smart-admin-web/javascript-ant-design-vue3/.env.development +++ b/smart-admin-web/javascript-ant-design-vue3/.env.development @@ -1,3 +1,3 @@ NODE_ENV=development VITE_APP_TITLE='SmartAdmin 开发环境(Dev)' -VITE_APP_API_URL='http://smartadmin.dev.1024lab.net/api/' +VITE_APP_API_URL='http://smartadmin.dev.1024lab.net/sa-api' diff --git a/smart-admin-web/javascript-ant-design-vue3/.env.test b/smart-admin-web/javascript-ant-design-vue3/.env.test index d034749e..aba8cd5c 100644 --- a/smart-admin-web/javascript-ant-design-vue3/.env.test +++ b/smart-admin-web/javascript-ant-design-vue3/.env.test @@ -1,3 +1,3 @@ NODE_ENV=production VITE_APP_TITLE='SmartAdmin 测试环境(Test)' -VITE_APP_API_URL='http://smartadmin.dev.1024lab.net/api/' +VITE_APP_API_URL='http://smartadmin.dev.1024lab.net/sa-api' diff --git a/smart-admin-web/javascript-ant-design-vue3/src/api/support/data-masking-api.js b/smart-admin-web/javascript-ant-design-vue3/src/api/support/data-masking-api.js new file mode 100644 index 00000000..16e670bb --- /dev/null +++ b/smart-admin-web/javascript-ant-design-vue3/src/api/support/data-masking-api.js @@ -0,0 +1,17 @@ +/** + * 数据脱敏api + * + * @Author: 1024创新实验室-主任-卓大 + * @Date: 2024-07-31 21:02:37 + * @Copyright 1024创新实验室 + */ +import { getRequest } from '/src/lib/axios'; + +export const dataMaskingApi = { + /** + * 查询脱敏数据 + */ + query: () => { + return getRequest('/support/dataMasking/demo/query'); + }, +}; diff --git a/smart-admin-web/javascript-ant-design-vue3/src/api/support/level3-protect-api.js b/smart-admin-web/javascript-ant-design-vue3/src/api/support/level3-protect-api.js new file mode 100644 index 00000000..c6be240f --- /dev/null +++ b/smart-admin-web/javascript-ant-design-vue3/src/api/support/level3-protect-api.js @@ -0,0 +1,24 @@ +/** + * 三级等保 api 封装 + * + * @Author: 1024创新实验室-主任-卓大 + * @Date: 2024-07-31 21:02:37 + * @Copyright 1024创新实验室 + */ +import { postRequest, getRequest } from '/src/lib/axios'; + +export const level3ProtectApi = { + /** + * 查询 三级等保配置 @author 1024创新实验室-主任-卓大 + */ + getConfig: () => { + return getRequest('/support/protect/level3protect/getConfig'); + }, + + /** + * 更新三级等保配置 @author 1024创新实验室-主任-卓大 + */ + updateConfig: (form) => { + return postRequest('/support/protect/level3protect/updateConfig', form); + }, +}; diff --git a/smart-admin-web/javascript-ant-design-vue3/src/api/system/employee-api.js b/smart-admin-web/javascript-ant-design-vue3/src/api/system/employee-api.js index dd02ec6f..3929911e 100644 --- a/smart-admin-web/javascript-ant-design-vue3/src/api/system/employee-api.js +++ b/smart-admin-web/javascript-ant-design-vue3/src/api/system/employee-api.js @@ -8,7 +8,7 @@ * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 */ -import { getRequest, postRequest } from '/src/lib/axios'; +import { getRequest, postEncryptRequest, postRequest } from '/src/lib/axios'; export const employeeApi = { /** @@ -72,11 +72,19 @@ export const employeeApi = { return getRequest(`/employee/update/password/reset/${employeeId}`); }, /** - * 修改面面 + * 修改密码 */ updateEmployeePassword: (param) => { - return postRequest('/employee/update/password', param); + return postEncryptRequest('/employee/update/password', param); }, + + /** + * 获取密码复杂度 + */ + getPasswordComplexityEnabled: () => { + return getRequest('/employee/getPasswordComplexityEnabled'); + }, + /** * 更新员工禁用状态 */ diff --git a/smart-admin-web/javascript-ant-design-vue3/src/api/system/login-api.js b/smart-admin-web/javascript-ant-design-vue3/src/api/system/login-api.js index 5c1593fc..08fe20ce 100644 --- a/smart-admin-web/javascript-ant-design-vue3/src/api/system/login-api.js +++ b/smart-admin-web/javascript-ant-design-vue3/src/api/system/login-api.js @@ -37,4 +37,18 @@ export const loginApi = { getLoginInfo: () => { return getRequest('/login/getLoginInfo'); }, + + /** + * 获取邮箱登录验证码 @author 卓大 + */ + sendLoginEmailCode: (loginName) => { + return getRequest(`/login/sendEmailCode/${loginName}`); + }, + + /** + * 获取双因子登录标识 @author 卓大 + */ + getTwoFactorLoginFlag: () => { + return getRequest('/login/getTwoFactorLoginFlag'); + }, }; diff --git a/smart-admin-web/javascript-ant-design-vue3/src/components/support/table-operator/index.vue b/smart-admin-web/javascript-ant-design-vue3/src/components/support/table-operator/index.vue index cf94f487..f77fc2a5 100644 --- a/smart-admin-web/javascript-ant-design-vue3/src/components/support/table-operator/index.vue +++ b/smart-admin-web/javascript-ant-design-vue3/src/components/support/table-operator/index.vue @@ -1,11 +1,11 @@ @@ -98,10 +98,12 @@ //取消全屏 exitFullscreen(document.querySelector('#smartAdminLayoutContent')); fullScreenFlag.value = false; + document.querySelector('#smartAdminPageTag').style.visibility = 'visible'; } else { //全屏 launchFullScreen(document.querySelector('#smartAdminLayoutContent')); fullScreenFlag.value = true; + document.querySelector('#smartAdminPageTag').style.visibility = 'hidden'; } } diff --git a/smart-admin-web/javascript-ant-design-vue3/src/constants/regular-const.js b/smart-admin-web/javascript-ant-design-vue3/src/constants/regular-const.js index 0633dc87..4c1338c8 100644 --- a/smart-admin-web/javascript-ant-design-vue3/src/constants/regular-const.js +++ b/smart-admin-web/javascript-ant-design-vue3/src/constants/regular-const.js @@ -25,4 +25,5 @@ export const regular = { isElseFileReg: new RegExp(/\.(doc|docx|xls|xlsx|txt|ppt|pptx|pps|ppxs)/), isIdentityCard: /^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X|x)$/, // 验证身份证号 isChinese: /^[\u4e00-\u9fa5]+$/gi, // 验证是否汉字 + }; diff --git a/smart-admin-web/javascript-ant-design-vue3/src/layout/components/change-password/regular-change-password-modal.vue b/smart-admin-web/javascript-ant-design-vue3/src/layout/components/change-password/regular-change-password-modal.vue new file mode 100644 index 00000000..2f863e6e --- /dev/null +++ b/smart-admin-web/javascript-ant-design-vue3/src/layout/components/change-password/regular-change-password-modal.vue @@ -0,0 +1,47 @@ + + + diff --git a/smart-admin-web/javascript-ant-design-vue3/src/layout/components/header-user-space/header-reset-password-modal/index.vue b/smart-admin-web/javascript-ant-design-vue3/src/layout/components/header-user-space/header-reset-password-modal/index.vue index 4de4c872..4166733c 100644 --- a/smart-admin-web/javascript-ant-design-vue3/src/layout/components/header-user-space/header-reset-password-modal/index.vue +++ b/smart-admin-web/javascript-ant-design-vue3/src/layout/components/header-user-space/header-reset-password-modal/index.vue @@ -1,11 +1,11 @@