diff --git a/pom.xml b/pom.xml index 62605294b1175e3b9d88046b7813f9c7b3631ccc..f29ad3d00ad7b48f8351b49c9c5a7a3490c61426 100644 --- a/pom.xml +++ b/pom.xml @@ -2,8 +2,8 @@ - 4.0.0 - + 4.0.0 + com.ruoyi ruoyi 3.8.2 @@ -11,7 +11,7 @@ ruoyi http://www.ruoyi.vip 若依管理系统 - + 3.8.2 UTF-8 @@ -26,14 +26,16 @@ 1.4.1 1.2.80 6.1.6 + 5.10.0 2.11.0 1.4 3.2.2 4.1.2 2.3 0.9.1 + 1.6.1 - + @@ -82,6 +84,18 @@ ${oshi.version} + + net.java.dev.jna + jna + ${jna.version} + + + + net.java.dev.jna + jna-platform + ${jna.version} + + io.springfox diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/PasswordConfigController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/PasswordConfigController.java new file mode 100644 index 0000000000000000000000000000000000000000..9f6be963b8af293eef374216eb57cf4067639b2a --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/PasswordConfigController.java @@ -0,0 +1,69 @@ +package com.ruoyi.web.controller.system; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.system.domain.PasswordConfig; +import com.ruoyi.system.domain.SysConfig; +import com.ruoyi.system.service.ISysConfigService; +import com.ruoyi.system.service.ISysPasswordService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * 参数配置 信息操作处理 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/system/password") +public class PasswordConfigController extends BaseController +{ + @Autowired + private ISysPasswordService sysPasswordService; + + /** + * 获取参数配置列表 + */ + @PreAuthorize("@ss.hasPermi('system:password:list')") + @GetMapping("/list") + public AjaxResult list() + { + PasswordConfig passwordConfig = sysPasswordService.selectConfig(); + return AjaxResult.success(passwordConfig); + } + + + + /** + * 修改参数配置 + */ + @PreAuthorize("@ss.hasPermi('system:config:edit')") + @Log(title = "参数管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody PasswordConfig config) + { + return toAjax(sysPasswordService.saveConfig(config)); + } + + /** + * 刷新参数缓存 + */ + @PreAuthorize("@ss.hasPermi('system:config:remove')") + @Log(title = "参数管理", businessType = BusinessType.CLEAN) + @DeleteMapping("/refreshCache") + public AjaxResult refreshCache() + { + sysPasswordService.resetConfigCache(); + return AjaxResult.success(); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java index be515fd610458f9cf16fe17834b08bba53418107..bf5560b988a5d4ad92e9e646203b2d1cb9225821 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java @@ -1,6 +1,9 @@ package com.ruoyi.web.controller.system; import java.io.IOException; + +import com.ruoyi.system.service.ISysPasswordService; +import com.ruoyi.system.service.impl.SysPasswordServiceImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -36,6 +39,8 @@ public class SysProfileController extends BaseController @Autowired private ISysUserService userService; + @Autowired + private ISysPasswordService passwordService; @Autowired private TokenService tokenService; @@ -106,6 +111,8 @@ public class SysProfileController extends BaseController { return AjaxResult.error("新密码不能与旧密码相同"); } + + passwordService.validatePasswordRule(newPassword); if (userService.resetUserPwd(userName, SecurityUtils.encryptPassword(newPassword)) > 0) { // 更新缓存用户密码 diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java index 60d9de07fbcda2f316f0ee222ec2ac6a74725fcf..8e2b7e57fedb3b5b941c1f310256bb4f8d1ddf9e 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java @@ -1,8 +1,11 @@ package com.ruoyi.web.controller.system; +import java.util.Date; import java.util.List; import java.util.stream.Collectors; import javax.servlet.http.HttpServletResponse; + +import com.ruoyi.system.service.ISysPasswordService; import org.apache.commons.lang3.ArrayUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; @@ -33,7 +36,7 @@ import com.ruoyi.system.service.ISysUserService; /** * 用户信息 - * + * * @author ruoyi */ @RestController @@ -49,6 +52,9 @@ public class SysUserController extends BaseController @Autowired private ISysPostService postService; + @Autowired + private ISysPasswordService passwordService; + /** * 获取用户列表 */ @@ -94,7 +100,7 @@ public class SysUserController extends BaseController * 根据用户编号获取详细信息 */ @PreAuthorize("@ss.hasPermi('system:user:query')") - @GetMapping(value = { "/", "/{userId}" }) + @GetMapping(value = {"/", "/{userId}"}) public AjaxResult getInfo(@PathVariable(value = "userId", required = false) Long userId) { userService.checkUserDataScope(userId); @@ -102,8 +108,7 @@ public class SysUserController extends BaseController List roles = roleService.selectRoleAll(); ajax.put("roles", SysUser.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList())); ajax.put("posts", postService.selectPostAll()); - if (StringUtils.isNotNull(userId)) - { + if (StringUtils.isNotNull(userId)) { SysUser sysUser = userService.selectUserById(userId); ajax.put(AjaxResult.DATA_TAG, sysUser); ajax.put("postIds", postService.selectPostListByUserId(userId)); @@ -118,20 +123,14 @@ public class SysUserController extends BaseController @PreAuthorize("@ss.hasPermi('system:user:add')") @Log(title = "用户管理", businessType = BusinessType.INSERT) @PostMapping - public AjaxResult add(@Validated @RequestBody SysUser user) - { - if (UserConstants.NOT_UNIQUE.equals(userService.checkUserNameUnique(user.getUserName()))) - { + public AjaxResult add(@Validated @RequestBody SysUser user) { + if (UserConstants.NOT_UNIQUE.equals(userService.checkUserNameUnique(user.getUserName()))) { return AjaxResult.error("新增用户'" + user.getUserName() + "'失败,登录账号已存在"); - } - else if (StringUtils.isNotEmpty(user.getPhonenumber()) - && UserConstants.NOT_UNIQUE.equals(userService.checkPhoneUnique(user))) - { + } else if (StringUtils.isNotEmpty(user.getPhonenumber()) + && UserConstants.NOT_UNIQUE.equals(userService.checkPhoneUnique(user))) { return AjaxResult.error("新增用户'" + user.getUserName() + "'失败,手机号码已存在"); - } - else if (StringUtils.isNotEmpty(user.getEmail()) - && UserConstants.NOT_UNIQUE.equals(userService.checkEmailUnique(user))) - { + } else if (StringUtils.isNotEmpty(user.getEmail()) + && UserConstants.NOT_UNIQUE.equals(userService.checkEmailUnique(user))) { return AjaxResult.error("新增用户'" + user.getUserName() + "'失败,邮箱账号已存在"); } user.setCreateBy(getUsername()); @@ -145,18 +144,14 @@ public class SysUserController extends BaseController @PreAuthorize("@ss.hasPermi('system:user:edit')") @Log(title = "用户管理", businessType = BusinessType.UPDATE) @PutMapping - public AjaxResult edit(@Validated @RequestBody SysUser user) - { + public AjaxResult edit(@Validated @RequestBody SysUser user) { userService.checkUserAllowed(user); userService.checkUserDataScope(user.getUserId()); if (StringUtils.isNotEmpty(user.getPhonenumber()) - && UserConstants.NOT_UNIQUE.equals(userService.checkPhoneUnique(user))) - { + && UserConstants.NOT_UNIQUE.equals(userService.checkPhoneUnique(user))) { return AjaxResult.error("修改用户'" + user.getUserName() + "'失败,手机号码已存在"); - } - else if (StringUtils.isNotEmpty(user.getEmail()) - && UserConstants.NOT_UNIQUE.equals(userService.checkEmailUnique(user))) - { + } else if (StringUtils.isNotEmpty(user.getEmail()) + && UserConstants.NOT_UNIQUE.equals(userService.checkEmailUnique(user))) { return AjaxResult.error("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在"); } user.setUpdateBy(getUsername()); @@ -169,10 +164,8 @@ public class SysUserController extends BaseController @PreAuthorize("@ss.hasPermi('system:user:remove')") @Log(title = "用户管理", businessType = BusinessType.DELETE) @DeleteMapping("/{userIds}") - public AjaxResult remove(@PathVariable Long[] userIds) - { - if (ArrayUtils.contains(userIds, getUserId())) - { + public AjaxResult remove(@PathVariable Long[] userIds) { + if (ArrayUtils.contains(userIds, getUserId())) { return error("当前用户不能删除"); } return toAjax(userService.deleteUserByIds(userIds)); @@ -184,10 +177,10 @@ public class SysUserController extends BaseController @PreAuthorize("@ss.hasPermi('system:user:resetPwd')") @Log(title = "用户管理", businessType = BusinessType.UPDATE) @PutMapping("/resetPwd") - public AjaxResult resetPwd(@RequestBody SysUser user) - { + public AjaxResult resetPwd(@RequestBody SysUser user) { userService.checkUserAllowed(user); userService.checkUserDataScope(user.getUserId()); + passwordService.validatePasswordRule(user.getPassword()); user.setPassword(SecurityUtils.encryptPassword(user.getPassword())); user.setUpdateBy(getUsername()); return toAjax(userService.resetPwd(user)); @@ -199,8 +192,7 @@ public class SysUserController extends BaseController @PreAuthorize("@ss.hasPermi('system:user:edit')") @Log(title = "用户管理", businessType = BusinessType.UPDATE) @PutMapping("/changeStatus") - public AjaxResult changeStatus(@RequestBody SysUser user) - { + public AjaxResult changeStatus(@RequestBody SysUser user) { userService.checkUserAllowed(user); userService.checkUserDataScope(user.getUserId()); user.setUpdateBy(getUsername()); @@ -212,8 +204,7 @@ public class SysUserController extends BaseController */ @PreAuthorize("@ss.hasPermi('system:user:query')") @GetMapping("/authRole/{userId}") - public AjaxResult authRole(@PathVariable("userId") Long userId) - { + public AjaxResult authRole(@PathVariable("userId") Long userId) { AjaxResult ajax = AjaxResult.success(); SysUser user = userService.selectUserById(userId); List roles = roleService.selectRolesByUserId(userId); @@ -228,8 +219,7 @@ public class SysUserController extends BaseController @PreAuthorize("@ss.hasPermi('system:user:edit')") @Log(title = "用户管理", businessType = BusinessType.GRANT) @PutMapping("/authRole") - public AjaxResult insertAuthRole(Long userId, Long[] roleIds) - { + public AjaxResult insertAuthRole(Long userId, Long[] roleIds) { userService.checkUserDataScope(userId); userService.insertUserAuth(userId, roleIds); return success(); diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java index 4aa1d2b2dee2bb56941c98aa1b032b160c0b1c95..5c3f8b7035e92ccfe68acdc6f7a8c0366e1b3cb5 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java @@ -94,6 +94,31 @@ public class SysUser extends BaseEntity /** 角色ID */ private Long roleId; + /** + * 尝试登录次数 + */ + private Long attemptCount; + /** + * 账户锁定时间 + */ + private Date lockTime; + + public Long getAttemptCount() { + return attemptCount; + } + + public void setAttemptCount(Long attemptCount) { + this.attemptCount = attemptCount; + } + + public Date getLockTime() { + return lockTime; + } + + public void setLockTime(Date lockTime) { + this.lockTime = lockTime; + } + public SysUser() { diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java index 6f8b9aa5c50dc89eff10d3576d0b8e97b32f2f35..cf33085b3d4e21c529441d32b7f11aa9bb1c51e6 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java @@ -1,6 +1,11 @@ package com.ruoyi.framework.web.service; import javax.annotation.Resource; + +import com.ruoyi.system.domain.PasswordConfig; +import com.ruoyi.system.service.ISysPasswordService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; @@ -25,6 +30,8 @@ import com.ruoyi.framework.manager.factory.AsyncFactory; import com.ruoyi.system.service.ISysConfigService; import com.ruoyi.system.service.ISysUserService; +import java.util.Date; + /** * 登录校验方法 * @@ -33,6 +40,7 @@ import com.ruoyi.system.service.ISysUserService; @Component public class SysLoginService { + private static final Logger log = LoggerFactory.getLogger(SysLoginService.class); @Autowired private TokenService tokenService; @@ -48,6 +56,8 @@ public class SysLoginService @Autowired private ISysConfigService configService; + @Autowired + private ISysPasswordService passwordService; /** * 登录验证 * @@ -60,6 +70,7 @@ public class SysLoginService public String login(String username, String password, String code, String uuid) { boolean captchaOnOff = configService.selectCaptchaOnOff(); + SysUser user = null; // 验证码开关 if (captchaOnOff) { @@ -77,6 +88,25 @@ public class SysLoginService { if (e instanceof BadCredentialsException) { + user = userService.selectUserByUserName(username); + if (user != null) { + // 用户失败次数 + Long fails = user.getAttemptCount(); + // 系统配置失败次数 + PasswordConfig config = passwordService.selectConfig(); + int fails_count = config.getMaxFailedLoginAttempts(); + // 超出失败次数,停用账户 + if (fails >= fails_count) { + userService.setAccountLocked(user); + throw new ServiceException("账号尝试次数超过"+fails_count+"次,将被锁定"+config.getUserLockPeriod()+"分钟"); + + } else { + // 失败次数++ + fails++; + user.setAttemptCount(fails); + userService.updateUserAttemptCount(user); + } + } AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"))); throw new UserPasswordNotMatchException(); } @@ -86,6 +116,9 @@ public class SysLoginService throw new ServiceException(e.getMessage()); } } + user = userService.selectUserByUserName(username); + user.setAttemptCount(0L); + userService.updateUserAttemptCount(user); AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"))); LoginUser loginUser = (LoginUser) authentication.getPrincipal(); recordLoginInfo(loginUser.getUserId()); diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/UserDetailsServiceImpl.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/UserDetailsServiceImpl.java index 575bd8d808ea93da19319814e4ab0da51e493700..b6179fcb0b63edd73659684ca98f07e770b0438f 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/UserDetailsServiceImpl.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/UserDetailsServiceImpl.java @@ -1,5 +1,8 @@ package com.ruoyi.framework.web.service; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.system.domain.PasswordConfig; +import com.ruoyi.system.service.ISysPasswordService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -14,6 +17,9 @@ import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.system.service.ISysUserService; +import java.util.Date; +import java.util.Optional; + /** * 用户验证处理 * @@ -27,6 +33,9 @@ public class UserDetailsServiceImpl implements UserDetailsService @Autowired private ISysUserService userService; + @Autowired + private ISysPasswordService passwordService; + @Autowired private SysPermissionService permissionService; @@ -34,20 +43,34 @@ public class UserDetailsServiceImpl implements UserDetailsService public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { SysUser user = userService.selectUserByUserName(username); + PasswordConfig passwordConfig = passwordService.selectConfig(); + + String message=null; if (StringUtils.isNull(user)) { - log.info("登录用户:{} 不存在.", username); - throw new ServiceException("登录用户:" + username + " 不存在"); + message = StringUtils.format("登录用户:{} 不存在",username); } else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) { - log.info("登录用户:{} 已被删除.", username); - throw new ServiceException("对不起,您的账号:" + username + " 已被删除"); + message = StringUtils.format("登录用户:{} 已被删除.", username); } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) { - log.info("登录用户:{} 已被停用.", username); - throw new ServiceException("对不起,您的账号:" + username + " 已停用"); + message = StringUtils.format ("登录用户:{} 已被停用.", username); + } + else if(System.currentTimeMillis() -user.getLockTime().getTime() <1000*60*passwordConfig.getUserLockPeriod() ) + { + String lockTimeStr = DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss",user.getLockTime() ); + message = StringUtils.format("登录用户:{}已经于{}被锁定,请于{}分钟后重试",username,lockTimeStr,passwordConfig.getUserLockPeriod()); + } + else if(DateUtils.addDays(Optional.ofNullable(user.getUpdateTime()).orElse(DateUtils.addYears(new Date(),-1)),passwordConfig.getPasswordExpirationPeriodDays()).compareTo(new Date()) <= 0 ) + { + message =StringUtils.format("登录用户:{} 密码已经超过{}天未修改,请联系管理员重置密码.", username,passwordConfig.getPasswordExpirationPeriodDays()); + } + if(message!=null) + { + log.info(message); + throw new ServiceException(message); } return createLoginUser(user); diff --git a/ruoyi-system/pom.xml b/ruoyi-system/pom.xml index cdd5bb637f1b8e8d6f2e7b66194986c9430dffa3..b950c01d384c27ce7f087e839b84de24ade41fa2 100644 --- a/ruoyi-system/pom.xml +++ b/ruoyi-system/pom.xml @@ -22,6 +22,19 @@ com.ruoyi ruoyi-common + + junit + junit + test + + + + org.passay + passay + ${passay.version} + diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/PasswordConfig.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/PasswordConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..9c784e036cf12bb35cfa802e78cba0d0aee3f209 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/PasswordConfig.java @@ -0,0 +1,196 @@ +package com.ruoyi.system.domain; + +import com.ruoyi.common.core.domain.BaseEntity; +import com.ruoyi.common.utils.StringUtils; + +import java.io.Serializable; + +/** + * 密码强度配置 + * @author boger + * @since 2022-05-14 + */ +public class PasswordConfig extends BaseEntity +{ + private static final long serialVersionUID = 234L; + private Integer id; + /** + * 最多尝试次数,默认5次 + */ + private Integer maxFailedLoginAttempts = 5; + /** + * 锁定后通知邮箱 + */ + private String userLockoutNotificationEmail; + + + + /** + * 锁定后解锁时间间隔,默认10分钟 + */ + private Integer userLockPeriod= 10; + + /** + * 密码过期时间,默认90天 + */ + private Integer passwordExpirationPeriodDays = 90; + + /** + * 密码最小长度,默认8个字符 + */ + private Integer minimumLength = 8; + + /** + * 密码最大长度,默认20个字符 + */ + private Integer maximumLength = 20; + + + /** + * 最少大写字符数 + */ + private Integer minimumUppercaseLetters = 1; + /** + * 最少小写字符数 + */ + private Integer minimumLowercaseLetters = 1; + /** + * 最少数字字符数 + */ + private Integer minimumDigits = 1; + /** + * 最少特殊字符数 + */ + private Integer minimumSpecialCharacters = 1; + + + /** + * 检验提示 + */ + private String passwordPolicyDesc; + + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Integer getMaxFailedLoginAttempts() { + return maxFailedLoginAttempts; + } + + public void setMaxFailedLoginAttempts(Integer maxFailedLoginAttempts) { + this.maxFailedLoginAttempts = maxFailedLoginAttempts; + } + + public String getUserLockoutNotificationEmail() { + return userLockoutNotificationEmail; + } + + public void setUserLockoutNotificationEmail(String userLockoutNotificationEmail) { + this.userLockoutNotificationEmail = userLockoutNotificationEmail; + } + + public Integer getUserLockPeriod() { + return userLockPeriod; + } + + public void setUserLockPeriod(Integer userLockPeriod) { + this.userLockPeriod = userLockPeriod; + } + + public Integer getMinimumLength() { + return minimumLength; + } + + public void setMinimumLength(Integer minimumLength) { + this.minimumLength = minimumLength; + } + + public Integer getMaximumLength() { + return maximumLength; + } + + public void setMaximumLength(Integer maximumLength) { + this.maximumLength = maximumLength; + } + + public Integer getMinimumUppercaseLetters() { + return minimumUppercaseLetters; + } + + public void setMinimumUppercaseLetters(Integer minimumUppercaseLetters) { + this.minimumUppercaseLetters = minimumUppercaseLetters; + } + + public Integer getMinimumLowercaseLetters() { + return minimumLowercaseLetters; + } + + public void setMinimumLowercaseLetters(Integer minimumLowercaseLetters) { + this.minimumLowercaseLetters = minimumLowercaseLetters; + } + + public Integer getMinimumDigits() { + return minimumDigits; + } + + public void setMinimumDigits(Integer minimumDigits) { + this.minimumDigits = minimumDigits; + } + + public Integer getMinimumSpecialCharacters() { + return minimumSpecialCharacters; + } + + public void setMinimumSpecialCharacters(Integer minimumSpecialCharacters) { + this.minimumSpecialCharacters = minimumSpecialCharacters; + } + + public Integer getPasswordExpirationPeriodDays() { + return passwordExpirationPeriodDays; + } + + public void setPasswordExpirationPeriodDays(Integer passwordExpirationPeriodDays) { + this.passwordExpirationPeriodDays = passwordExpirationPeriodDays; + } + + public String getPasswordPolicyDesc() { + String lengthRule; + String characterRule = ""; + if(isPositiveInteger(this.getMinimumUppercaseLetters())){ + characterRule += StringUtils.format("{}个大写字母,",this.getMinimumUppercaseLetters()); + } + if(isPositiveInteger(this.getMinimumLowercaseLetters())){ + characterRule += StringUtils.format("{}个小写字母,",this.getMinimumLowercaseLetters()); + } + if(isPositiveInteger(this.getMinimumDigits())){ + characterRule += StringUtils.format("{}个数字,",this.getMinimumDigits()); + } + if(isPositiveInteger(this.getMinimumSpecialCharacters())){ + characterRule += StringUtils.format("{}个特殊字符",this.getMinimumSpecialCharacters()); + } + if(this.getMinimumLength().equals(this.getMaximumLength())){ + lengthRule = StringUtils.format("密码长度必须是{}位数",this.getMinimumLength()); + }else { + lengthRule = StringUtils.format("密码长度最少{}位,最多{}位",this.getMinimumLength(),this.getMaximumLength()); + } + if(StringUtils.isNotEmpty(characterRule)){ + this.setPasswordPolicyDesc(lengthRule+",至少包含:"+characterRule); + }else { + this.setPasswordPolicyDesc(lengthRule); + } + return this.passwordPolicyDesc; + } + + public void setPasswordPolicyDesc(String passwordPolicyDesc) { + this.passwordPolicyDesc = passwordPolicyDesc; + } + + private boolean isPositiveInteger(Integer val) { + return val != null && val > 0; + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/PasswordConfigMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/PasswordConfigMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..29e6273820488ad36bf59dc10bc6eac304481634 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/PasswordConfigMapper.java @@ -0,0 +1,61 @@ +package com.ruoyi.system.mapper; + +import java.util.List; +import com.ruoyi.system.domain.PasswordConfig; + +/** + * 密码策略Mapper接口 + * + * @author boger + * @date 2022-05-15 + */ +public interface PasswordConfigMapper +{ + /** + * 查询密码策略 + * + * @param id 密码策略主键 + * @return 密码策略 + */ + public PasswordConfig selectPasswordConfigById(Long id); + + /** + * 查询密码策略列表 + * + * @param passwordConfig 密码策略 + * @return 密码策略集合 + */ + public List selectPasswordConfigList(PasswordConfig passwordConfig); + + /** + * 新增密码策略 + * + * @param passwordConfig 密码策略 + * @return 结果 + */ + public int insertPasswordConfig(PasswordConfig passwordConfig); + + /** + * 修改密码策略 + * + * @param passwordConfig 密码策略 + * @return 结果 + */ + public int updatePasswordConfig(PasswordConfig passwordConfig); + + /** + * 删除密码策略 + * + * @param id 密码策略主键 + * @return 结果 + */ + public int deletePasswordConfigById(Long id); + + /** + * 批量删除密码策略 + * + * @param ids 需要删除的数据主键集合 + * @return 结果 + */ + public int deletePasswordConfigByIds(Long[] ids); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java index 28713f6e6d60872b18f7962d22fc0b0549c38f75..7edcde6261ea13c61080bafb95fe8af3b1573a8e 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java @@ -124,4 +124,8 @@ public interface SysUserMapper * @return 结果 */ public SysUser checkEmailUnique(String email); + + int setAccountLocked(SysUser user); + + int updateUserAttemptCount(SysUser user); } diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysPasswordService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysPasswordService.java new file mode 100644 index 0000000000000000000000000000000000000000..79234a6c2580e3103ba9c1a39730e106c2192076 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysPasswordService.java @@ -0,0 +1,70 @@ +package com.ruoyi.system.service; + + +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.system.domain.PasswordConfig; +import com.ruoyi.system.domain.SysConfig; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + *

+ * 系统用户 服务类 + *

+ * + * @author boger + * @since 2022-05-18 + */ +public interface ISysPasswordService { + + /** + * 设置passwordConfig + * @param passwordConfig + */ + void setPasswordConfig(PasswordConfig passwordConfig); + + /** + * 验证密码强度,如果不符合强度要求,则报出ServiceException + * @param password + */ + public void validatePasswordRule(String password) throws ServiceException; + + /** + * 验证密码规则,如果不符合规则将报出ServiceException + * @param password + * @param passwordConfig 密码强度配置 + */ + public void validatePasswordRule(String password, PasswordConfig passwordConfig ) throws ServiceException; + + /** + * 查询密码强度配置信息 + * @return 参数配置信息 + */ + public PasswordConfig selectConfig(); + + /** + * 保存密码强度配置 + * + * @param config 密码强度配置信息 + * @return 结果 + */ + public int saveConfig(PasswordConfig config); + + + /** + * 加载参数缓存数据 + */ + public void CachePasswordConfig(); + + /** + * 清空参数缓存数据 + */ + public void clearConfigCache(); + + /** + * 重置参数缓存数据 + */ + public void resetConfigCache(); + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java index 29193ceabcec0a18b997c7e3156652b823e6a398..2cc3c5620702b967a92acb19161789078920aadd 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java @@ -203,4 +203,18 @@ public interface ISysUserService * @return 结果 */ public String importUser(List userList, Boolean isUpdateSupport, String operName); + + /** + * 修改尝试登录次数 + * @param user 用户信息 + * @return 结果 + */ + int updateUserAttemptCount(SysUser user); + + /** + * 锁定账号 + * @param user 用户信息 + * @return 结果 + */ + int setAccountLocked(SysUser user); } diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysPasswordServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysPasswordServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..779642714ad42114908857da24cd9743677d49c1 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysPasswordServiceImpl.java @@ -0,0 +1,167 @@ +package com.ruoyi.system.service.impl; + + +import com.ruoyi.common.core.redis.RedisCache; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.system.domain.PasswordConfig; +import com.ruoyi.system.mapper.PasswordConfigMapper; +import com.ruoyi.system.service.ISysPasswordService; +import org.passay.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import java.util.ArrayList; +import java.util.List; + +/** + *

+ * 密码强度设置 服务类 + *

+ * + * @author boger + * @since 2022-05-15 + */ + +@Service +public class SysPasswordServiceImpl implements ISysPasswordService { + + private PasswordConfig passwordConfig; + @Autowired + private RedisCache redisCache; + @Autowired + private PasswordConfigMapper passwordConfigMapper; + /** + * 自动装配时,初始化密码强度设置参数到缓存 + */ + @PostConstruct + public void init() + { + resetConfigCache(); + } + + /** + * 设置passwordConfig + * @param passwordConfig + */ + @Override + public void setPasswordConfig(PasswordConfig passwordConfig) + { + this.passwordConfig = passwordConfig; + } + public void validatePasswordRule(String password){ + validatePasswordRule(password, passwordConfig); + } + + /** + * 根据指定的密码强度策略验证密码是否满足策略要求,不满足策略要求抛出异常 + * @param password 待验证密码 + * @param passwordConfig 密码强度配置 + */ + public void validatePasswordRule(String password, PasswordConfig passwordConfig){ + + List passwordRules = new ArrayList<>(); + //密码组成规则校验 + passwordRules.add(new LengthRule(passwordConfig.getMinimumLength(), passwordConfig.getMaximumLength()));//密码长度范围 + if (isPositiveInteger(passwordConfig.getMinimumUppercaseLetters())) { + passwordRules.add(new CharacterRule(EnglishCharacterData.UpperCase, passwordConfig.getMinimumUppercaseLetters()));//包含的大写字母个数 + } + if (isPositiveInteger(passwordConfig.getMinimumLowercaseLetters())) { + passwordRules.add(new CharacterRule(EnglishCharacterData.LowerCase, passwordConfig.getMinimumLowercaseLetters()));//包含的小写字母个数 + } + if (isPositiveInteger(passwordConfig.getMinimumDigits())) { + passwordRules.add(new CharacterRule(EnglishCharacterData.Digit, passwordConfig.getMinimumDigits()));//包含的数字个数 + } + if (isPositiveInteger(passwordConfig.getMinimumSpecialCharacters())) { + passwordRules.add(new CharacterRule(EnglishCharacterData.Special, passwordConfig.getMinimumSpecialCharacters()));//包含的特殊字符个数 + } + PasswordValidator validator = new PasswordValidator(passwordRules); + PasswordData passwordData = new PasswordData(password); + RuleResult result = validator.validate(passwordData);//校验密码组成规则 + if (!result.isValid()) { + String message = passwordConfig.getPasswordPolicyDesc(); + throw new ServiceException(message); + } + } + + /** + * 获取密码强度配置 + * @return 密码策略配置对象 + */ + @Override + public PasswordConfig selectConfig() { + PasswordConfig config; + config = redisCache.getCacheObject("sys:password"); + if (config!=null) + { + return config; + } + PasswordConfig passwordConfig = passwordConfigMapper.selectPasswordConfigById(1L); + if (passwordConfig!=null) + { + this.passwordConfig = passwordConfig; + redisCache.setCacheObject("sys:password", passwordConfig); + } + return passwordConfig; + } + + /** + * 保存密码强度配置策略 + * @param passwordConfig 密码强度配置信息 + * @return 保存结果 + */ + @Override + public int saveConfig(PasswordConfig passwordConfig) { + passwordConfig.setUpdateTime(DateUtils.getNowDate()); + int result= passwordConfigMapper.updatePasswordConfig(passwordConfig); + if(result>0){ + this.passwordConfig = passwordConfig; + redisCache.setCacheObject("sys:password", passwordConfig); + } + return result; + } + + /** + * 将保存的密码强度策略保存到缓存中 + */ + @Override + public void CachePasswordConfig() + { + PasswordConfig passwordConfig = passwordConfigMapper.selectPasswordConfigById(1L); + if (passwordConfig!=null) + { + this.passwordConfig = passwordConfig; + redisCache.setCacheObject("sys:password", passwordConfig); + } + + } + + /** + * 清理密码强度策略缓存 + */ + @Override + public void clearConfigCache() { + redisCache.deleteObject("sys:password"); + } + + /** + * 重置密码策略缓存内容 + */ + @Override + public void resetConfigCache() { + clearConfigCache(); + CachePasswordConfig(); + } + + + /** + * 判断是否为正整数 + * @param val 待判定整数 + * @return 正整数返回true,否则返回false + */ + private boolean isPositiveInteger(Integer val) { + return val != null && val > 0; + } + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java index 56f3dac725d3c992442cd0c5586ad21ee603008e..dbb09a58d2fd012d78834a8987dd79e637b83916 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java @@ -559,4 +559,26 @@ public class SysUserServiceImpl implements ISysUserService } return successMsg.toString(); } + + /** + * 修改尝试登录次数 + * @param user 用户信息 + * @return 结果 + */ + @Override + public int updateUserAttemptCount(SysUser user) + { + return userMapper.updateUserAttemptCount(user); + } + + /** + * 锁定账户 + * @param user 用户信息 + * @return 结果 + */ + @Override + public int setAccountLocked(SysUser user) + { + return userMapper.setAccountLocked(user); + } } diff --git a/ruoyi-system/src/main/resources/mapper/system/PasswordConfigMapper.xml b/ruoyi-system/src/main/resources/mapper/system/PasswordConfigMapper.xml new file mode 100644 index 0000000000000000000000000000000000000000..f1d751ee965b6df0fa4777e01cd336fb943986d5 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/PasswordConfigMapper.xml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + select id, max_failed, userlock_period, expiration_period, notification_email, min_length, max_length, min_upper, min_lower, min_digits, min_special, create_by, create_time, update_by, update_time from sys_password + + + + + + + + insert into sys_password + + max_failed, + userlock_period, + expiration_period, + notification_email, + min_length, + max_length, + min_upper, + min_lower, + min_digits, + min_special, + create_by, + create_time, + update_by, + update_time, + + + #{maxFailedLoginAttempts}, + #{userLockPeriod}, + #{passwordExpirationPeriodDays}, + #{userLockoutNotificationEmail}, + #{minimumLength}, + #{maximumLength}, + #{minimumUppercaseLetters}, + #{minimumLowercaseLetters}, + #{minimumDigits}, + #{minimumSpecialCharacters}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + + + + + update sys_password + + max_failed = #{maxFailedLoginAttempts}, + userlock_period = #{userLockPeriod}, + expiration_period = #{passwordExpirationPeriodDays}, + notification_email = #{userLockoutNotificationEmail}, + min_length = #{minimumLength}, + max_length = #{maximumLength}, + min_upper = #{minimumUppercaseLetters}, + min_lower = #{minimumLowercaseLetters}, + min_digits = #{minimumDigits}, + min_special = #{minimumSpecialCharacters}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + + where id = #{id} + + + + delete from sys_password where id = #{id} + + + + delete from sys_password where id in + + #{id} + + + \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml index 16a64c2a333771aebc48248c421dd44b6772916a..7275df9ce43cbf6a6eb0fc3cfec369c5e7af5f79 100644 --- a/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml +++ b/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml @@ -23,10 +23,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + - + @@ -36,7 +38,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - + @@ -45,9 +47,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - + - select u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, + select u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.avatar, u.phonenumber, u.password, + u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, + u.attempt_count, u.lock_time,u.update_time, d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.status as dept_status, r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status as role_status from sys_user u @@ -55,7 +59,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" left join sys_user_role ur on u.user_id = ur.user_id left join sys_role r on r.role_id = ur.role_id - + - + - + - + - + - + - + - + - + insert into sys_user( user_id, @@ -173,7 +177,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" sysdate() ) - + update sys_user @@ -194,28 +198,41 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" where user_id = #{userId} - + update sys_user set status = #{status} where user_id = #{userId} - + update sys_user set avatar = #{avatar} where user_name = #{userName} - + + + update sys_user set attempt_count = #{attemptCount} where user_name = #{userName} + + + + update sys_user + + attempt_count= 0, + lock_time = sysdate() + + where user_name = #{userName} + + update sys_user set password = #{password} where user_name = #{userName} - + update sys_user set del_flag = '2' where user_id = #{userId} - + update sys_user set del_flag = '2' where user_id in #{userId} - + - + \ No newline at end of file diff --git a/ruoyi-system/src/test/java/ValidatePasswordTest.java b/ruoyi-system/src/test/java/ValidatePasswordTest.java new file mode 100644 index 0000000000000000000000000000000000000000..88014897a40108d9c4d6bca599d0d51c45ed2b9b --- /dev/null +++ b/ruoyi-system/src/test/java/ValidatePasswordTest.java @@ -0,0 +1,63 @@ +import com.ruoyi.system.domain.PasswordConfig; +import com.ruoyi.system.service.ISysPasswordService; +import com.ruoyi.system.service.impl.SysPasswordServiceImpl; +import org.junit.Before; +import org.junit.Test; + +public class ValidatePasswordTest { + + ISysPasswordService service = null; + PasswordConfig passwordConfig = null; + @Before + public void initSysPasswordService() + { + service = new SysPasswordServiceImpl(); + passwordConfig = new PasswordConfig(); + passwordConfig.setMaxFailedLoginAttempts(5); + passwordConfig.setPasswordExpirationPeriodDays(90); + passwordConfig.setUserLockPeriod(10); + passwordConfig.setMaxFailedLoginAttempts(5); + passwordConfig.setMaximumLength(20); + passwordConfig.setMinimumLength(5); + passwordConfig.setMinimumDigits(1); + passwordConfig.setMinimumSpecialCharacters(1); + passwordConfig.setMinimumLowercaseLetters(1); + passwordConfig.setMinimumUppercaseLetters(1); + service.setPasswordConfig(passwordConfig); + } + + /** + * 满足策略要求用例 + */ + @Test + public void testValidedByConfig(){ + ISysPasswordService serviceNew = new SysPasswordServiceImpl(); + serviceNew.validatePasswordRule("Hello@123",passwordConfig); + } + + /** + * 不满足策略要求用例 + */ + @Test + public void testInvalidByConfig(){ + ISysPasswordService serviceNew = new SysPasswordServiceImpl(); + serviceNew.validatePasswordRule("Hello123",passwordConfig); + } + + /** + * 满足策略要求用例 + */ + @Test + public void testValided(){ + service.validatePasswordRule("Hello@123"); + } + + /** + * 不满足策略要求用例 + */ + @Test + public void testInValid(){ + service.validatePasswordRule("Hello123"); + + } +} \ No newline at end of file diff --git a/ruoyi-ui/src/api/system/password.js b/ruoyi-ui/src/api/system/password.js new file mode 100644 index 0000000000000000000000000000000000000000..0821d505b6f6585e449b94ae53dba291590097f6 --- /dev/null +++ b/ruoyi-ui/src/api/system/password.js @@ -0,0 +1,44 @@ +import request from '@/utils/request' + +// 查询密码策略列表 +export function listPassword(query) { + return request({ + url: '/system/password/list', + method: 'get', + params: query + }) +} + +// 查询密码策略详细 +export function getPassword(id) { + return request({ + url: '/system/password/' + id, + method: 'get' + }) +} + +// 新增密码策略 +export function addPassword(data) { + return request({ + url: '/system/password', + method: 'post', + data: data + }) +} + +// 修改密码策略 +export function updatePassword(data) { + return request({ + url: '/system/password', + method: 'put', + data: data + }) +} + +// 删除密码策略 +export function delPassword(id) { + return request({ + url: '/system/password/' + id, + method: 'delete' + }) +} diff --git a/ruoyi-ui/src/views/system/config/index.vue b/ruoyi-ui/src/views/system/config/index.vue index f580b983ee106222680cf0303f86f64ec958f789..92c340b6157766e73a342eefd795f80aed066bc6 100644 --- a/ruoyi-ui/src/views/system/config/index.vue +++ b/ruoyi-ui/src/views/system/config/index.vue @@ -1,4 +1,5 @@