用户登录问题修改

This commit is contained in:
jiang 2024-09-13 15:58:13 +08:00
parent 74373883ca
commit 12bd9c20c3
2 changed files with 212 additions and 116 deletions

View File

@ -51,11 +51,11 @@ public class PasswordValidatorService {
return AjaxResult.error("密码包含常见的弱密码片段!"); return AjaxResult.error("密码包含常见的弱密码片段!");
} }
if (containsConsecutiveCharacters(newPassword, config.getMaxConsecutiveChars())) { if (containsConsecutiveCharacters(newPassword.toLowerCase(), config.getMaxConsecutiveChars())) {
return AjaxResult.error("密码不能包含超过" + config.getMaxConsecutiveChars() + "位连续字符!"); return AjaxResult.error("密码不能包含超过" + config.getMaxConsecutiveChars() + "个连续相同字符、连续递增/递减的数字或字母(不区分大小写)");
} }
if (newPassword.contains(username)) { if (newPassword.toLowerCase().contains(username.toLowerCase())) {
return AjaxResult.error("密码不能包含账号!"); return AjaxResult.error("密码不能包含账号!");
} }
@ -120,7 +120,7 @@ public class PasswordValidatorService {
*/ */
private boolean containsWeakPassword(String password) { private boolean containsWeakPassword(String password) {
for (String weakPwd : config.getWeakPasswords()) { for (String weakPwd : config.getWeakPasswords()) {
if (password.contains(weakPwd)) { if (password.toLowerCase().contains(weakPwd)) {
return true; return true;
} }
} }
@ -128,24 +128,83 @@ public class PasswordValidatorService {
} }
/** /**
* 检查密码中是否包含超过 n 个连续相同字符 * 检查密码中是否包含超过 n 个连续相同字符连续递增/递减的数字或字母不区分大小写
*/ */
private boolean containsConsecutiveCharacters(String password, int n) { private boolean containsConsecutiveCharacters(String password, int n) {
// 检查连续相同字符
n = n + 1;
for (int i = 0; i <= password.length() - n; i++) { for (int i = 0; i <= password.length() - n; i++) {
boolean consecutive = true; boolean consecutiveSameChar = true;
for (int j = 1; j < n; j++) { for (int j = 1; j < n; j++) {
if (password.charAt(i + j) != password.charAt(i)) { if (password.charAt(i + j) != password.charAt(i)) {
consecutive = false; consecutiveSameChar = false;
break; break;
} }
} }
if (consecutive) { if (consecutiveSameChar) {
return true; // 包含超过 n 个连续相同字符
}
}
// 检查连续递增或递减的数字
for (int i = 0; i <= password.length() - n; i++) {
boolean consecutiveIncreasing = true;
boolean consecutiveDecreasing = true;
for (int j = 1; j < n; j++) {
char currentChar = password.charAt(i);
char nextChar = password.charAt(i + j);
// 检查数字递增或递减
if (Character.isDigit(currentChar) && Character.isDigit(nextChar)) {
if (nextChar != currentChar + j) {
consecutiveIncreasing = false;
}
if (nextChar != currentChar - j) {
consecutiveDecreasing = false;
}
} else {
consecutiveIncreasing = false;
consecutiveDecreasing = false;
break;
}
}
if (consecutiveIncreasing || consecutiveDecreasing) {
return true; // 包含超过 n 个递增或递减的连续数字
}
}
// 检查连续递增或递减的字母不区分大小写
for (int i = 0; i <= password.length() - n; i++) {
boolean consecutiveIncreasing = true;
boolean consecutiveDecreasing = true;
for (int j = 1; j < n; j++) {
char currentChar = Character.toLowerCase(password.charAt(i)); // 转为小写
char nextChar = Character.toLowerCase(password.charAt(i + j)); // 转为小写
// 检查字母递增或递减
if (Character.isLetter(currentChar) && Character.isLetter(nextChar)) {
if (nextChar != currentChar + j) {
consecutiveIncreasing = false;
}
if (nextChar != currentChar - j) {
consecutiveDecreasing = false;
}
} else {
consecutiveIncreasing = false;
consecutiveDecreasing = false;
break;
}
}
if (consecutiveIncreasing || consecutiveDecreasing) {
// 包含超过 n 个递增或递减的连续字母
return true; return true;
} }
} }
// 不包含连续相同字符数字或字母序列
return false; return false;
} }
/** /**
* 验证登录参数 * 验证登录参数
*/ */

View File

@ -14,11 +14,10 @@ import org.springframework.stereotype.Service;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.Date; import java.util.Date;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
/**
* 密码校验器类用于根据配置对用户输入的密码进行验证
*/
@Service @Service
public class PasswordValidatorServiceImpl implements PasswordValidatorService { public class PasswordValidatorServiceImpl implements PasswordValidatorService {
@ -31,195 +30,233 @@ public class PasswordValidatorServiceImpl implements PasswordValidatorService {
@Resource @Resource
private PasswordValidatorMapper passwordValidatorMapper; private PasswordValidatorMapper passwordValidatorMapper;
@Override @Override
public AjaxResult validatePassword(Long userId, String username, String oldPassword, String newPassword) { public AjaxResult validatePassword(Long userId, String username, String oldPassword, String newPassword) {
// 1. 检查密码长度是否符合配置 // 1. 检查密码长度
if (newPassword.length() < config.getMinLength() || newPassword.length() > config.getMaxLength()) { if (!isPasswordLengthValid(newPassword)) {
return AjaxResult.error("密码长度应为" + config.getMinLength() + "" + config.getMaxLength() + "位!"); return AjaxResult.error("密码长度应为" + config.getMinLength() + "" + config.getMaxLength() + "位!");
} }
// 2. 检查密码是否包含大写字母小写字母数字和特殊字符
boolean hasUpperCase = false, hasLowerCase = false, hasDigit = false, hasSpecialChar = false; // 2. 检查密码字符类型
for (char c : newPassword.toCharArray()) { if (!isPasswordCharacterValid(newPassword)) {
if (Character.isUpperCase(c)) { return AjaxResult.error(getCharacterRequirementErrorMessage());
hasUpperCase = true;
}
if (Character.isLowerCase(c)) {
hasLowerCase = true;
}
if (Character.isDigit(c)) {
hasDigit = true;
}
if ("!@#$%^&*()-_=+[{]};:'\",<.>/?".indexOf(c) >= 0) {
hasSpecialChar = true;
}
} }
// 根据配置检查大写字母小写字母数字和特殊字符的要求 // 3. 检查常见弱密码
if (config.isRequireUpperCase() && !hasUpperCase) { if (containsWeakPassword(newPassword.toLowerCase())) {
return AjaxResult.error("密码必须包含大写字母!"); return AjaxResult.error("密码包含常见的弱密码片段!");
} }
if (config.isRequireLowerCase() && !hasLowerCase) { // 4. 检查连续字符
return AjaxResult.error("密码必须包含小写字母!"); if (config.isRestrictConsecutiveChars() && containsConsecutiveCharacters(newPassword.toLowerCase(), config.getMaxConsecutiveChars())) {
}
if (config.isRequireDigit() && !hasDigit) {
return AjaxResult.error("密码必须包含数字!");
}
if (config.isRequireSpecialChar() && !hasSpecialChar) {
return AjaxResult.error("密码必须包含特殊字符!");
}
// 3. 检查是否包含常见弱密码
for (String weakPwd : config.getWeakPasswords()) {
if (newPassword.contains(weakPwd)) {
return AjaxResult.error("密码包含常见的弱密码片段: " + weakPwd);
}
}
// 4. 检查是否包含超过规定数量的连续字符
if (config.isRestrictConsecutiveChars() && containsConsecutiveCharacters(newPassword, config.getMaxConsecutiveChars())) {
return AjaxResult.error("密码不能包含超过" + config.getMaxConsecutiveChars() + "位连续字符!"); return AjaxResult.error("密码不能包含超过" + config.getMaxConsecutiveChars() + "位连续字符!");
} }
// 5. 检查密码中是否包含用户名 // 5. 检查用户名
if (config.isExcludeUsernameInPassword() && newPassword.contains(username)) { if (config.isExcludeUsernameInPassword() && newPassword.toLowerCase().contains(username.toLowerCase())) {
return AjaxResult.error("密码不能包含账号!"); return AjaxResult.error("密码不能包含账号!");
} }
// 6. 检查新密码是否与旧密码相同 // 6. 检查新旧密码是否相同
if (SecurityUtils.matchesPassword(newPassword, oldPassword)) { if (SecurityUtils.matchesPassword(newPassword, oldPassword)) {
return AjaxResult.error("新密码不能与原密码相同!"); return AjaxResult.error("新密码不能与原密码相同!");
} }
// 7. 检查新密码是否与历史密码相同 // 7. 检查密码历史
List<UserPasswordHistory> userPasswordHistories = passwordValidatorMapper.checkPasswordExpiry(userId); if (isPasswordInHistory(userId, newPassword)) {
// 如果没有找到密码历史记录返回提示 return AjaxResult.error("新密码不能与最近的" + config.getPasswordHistoryLimit() + "个旧密码相同!");
if (userPasswordHistories.isEmpty()) {
return AjaxResult.success("没有找到密码历史记录。");
}
// 只取最近的五次修改记录
// 使用 Math.min 以确保不会超过实际记录数
int limit = Math.min(userPasswordHistories.size(), config.getPasswordHistoryLimit());
for (int i = 0; i < limit; i++) {
UserPasswordHistory history = userPasswordHistories.get(i);
// 比较新密码与历史密码
if (SecurityUtils.matchesPassword(newPassword, history.getNewPassword())) {
return AjaxResult.error("新密码不能与最近的" + config.getPasswordHistoryLimit() + "个旧密码相同!");
}
} }
return AjaxResult.success(); return AjaxResult.success();
} }
private boolean isPasswordLengthValid(String password) {
return password.length() >= config.getMinLength() && password.length() <= config.getMaxLength();
}
private boolean isPasswordCharacterValid(String password) {
boolean hasUpperCase = false, hasLowerCase = false, hasDigit = false, hasSpecialChar = false;
for (char c : password.toCharArray()) {
if (Character.isUpperCase(c)) hasUpperCase = true;
if (Character.isLowerCase(c)) hasLowerCase = true;
if (Character.isDigit(c)) hasDigit = true;
if ("!@#$%^&*()-_=+[{]};:'\",<.>/?".indexOf(c) >= 0) hasSpecialChar = true;
}
if (config.isRequireUpperCase() && !hasUpperCase) return false;
if (config.isRequireLowerCase() && !hasLowerCase) return false;
if (config.isRequireDigit() && !hasDigit) return false;
return !(config.isRequireSpecialChar() && !hasSpecialChar);
}
private boolean containsWeakPassword(String password) {
Set<String> weakPasswords = new HashSet<>(config.getWeakPasswords());
for (String weakPwd : weakPasswords) {
if (password.contains(weakPwd)) {
return true;
}
}
return false;
}
/** /**
* 检查用户是否是首次登录 * 根据配置返回密码不符合要求时的错误提示信息
*
* @return 如果密码符合策略要求则返回 true否则返回 false
*/ */
private String getCharacterRequirementErrorMessage() {
if (config.isRequireUpperCase()) {
return "密码必须包含大写字母!";
}
if (config.isRequireLowerCase()) {
return "密码必须包含小写字母!";
}
if (config.isRequireDigit()) {
return "密码必须包含数字!";
}
if (config.isRequireSpecialChar()) {
return "密码必须包含特殊字符!";
}
return "密码不符合字符要求!";
}
private boolean isPasswordInHistory(Long userId, String newPassword) {
List<UserPasswordHistory> userPasswordHistories = passwordValidatorMapper.checkPasswordExpiry(userId);
if (userPasswordHistories.isEmpty()) return false;
// 只取最近的五次修改记录
int limit = Math.min(userPasswordHistories.size(), config.getPasswordHistoryLimit());
for (int i = 0; i < limit; i++) {
UserPasswordHistory history = userPasswordHistories.get(i);
if (SecurityUtils.matchesPassword(newPassword, history.getNewPassword())) {
return true;
}
}
return false;
}
@Override @Override
public boolean checkFirstLogin() { public boolean checkFirstLogin() {
// 如果未开启首次登录强制修改密码功能直接返回错误
if (!config.isForcePasswordChangeOnFirstLogin()) { if (!config.isForcePasswordChangeOnFirstLogin()) {
return false; return false;
} }
LoginUser loginUser = SecurityUtils.getLoginUser(); LoginUser loginUser = SecurityUtils.getLoginUser();
SysUser sysUser = loginUser.getSysUser(); SysUser sysUser = loginUser.getSysUser();
// 获取初始密码配置
String initialPassword = configService.selectConfigByKey("sys.user.initPassword"); String initialPassword = configService.selectConfigByKey("sys.user.initPassword");
// 检查当前用户密码是否是初始密码
// 根据检查结果返回相应的响应
return SecurityUtils.matchesPassword(initialPassword, sysUser.getPassword()); return SecurityUtils.matchesPassword(initialPassword, sysUser.getPassword());
} }
/**
* 检查用户密码是否已过期提示用户定期修改密码
*
* @return 如果需要修改密码返回 AjaxResult 提示用户修改否则返回成功信息
*/
@Override @Override
public boolean checkPasswordExpiry() { public boolean checkPasswordExpiry() {
Long userId = SecurityUtils.getUserId(); Long userId = SecurityUtils.getUserId();
List<UserPasswordHistory> userPasswordHistories = passwordValidatorMapper.checkPasswordExpiry(userId); List<UserPasswordHistory> userPasswordHistories = passwordValidatorMapper.checkPasswordExpiry(userId);
// 如果没有找到密码历史记录返回错误
if (userPasswordHistories.isEmpty()) { if (userPasswordHistories.isEmpty()) {
return false; return false;
} }
// 获取最近的密码修改记录
UserPasswordHistory userPasswordHistory = userPasswordHistories.get(0); UserPasswordHistory userPasswordHistory = userPasswordHistories.get(0);
// 获取当前日期
Date currentDate = DateUtils.getNowDate(); Date currentDate = DateUtils.getNowDate();
// 获取最近密码修改日期
Date changeDate = DateUtils.toDate(userPasswordHistory.getChangeTimestamp()); Date changeDate = DateUtils.toDate(userPasswordHistory.getChangeTimestamp());
// 计算最近密码修改到当前日期的天数
long daysSinceChange = DateUtils.daysBetween(changeDate, currentDate); long daysSinceChange = DateUtils.daysBetween(changeDate, currentDate);
// 检查是否达到定期修改密码的天数要求
return daysSinceChange > config.getRegularlyChangePassword(); return daysSinceChange > config.getRegularlyChangePassword();
} }
/**
* 检查新密码是否在最近五次修改的密码中
*
* @param userId 用户ID
* @param newPassword 新密码
* @return 如果新密码在最近的五次修改记录中返回错误提示否则返回成功提示
*/
@Override @Override
public AjaxResult isPasswordInRecentHistory(Long userId, String newPassword) { public AjaxResult isPasswordInRecentHistory(Long userId, String newPassword) {
List<UserPasswordHistory> userPasswordHistories = passwordValidatorMapper.checkPasswordExpiry(userId); List<UserPasswordHistory> userPasswordHistories = passwordValidatorMapper.checkPasswordExpiry(userId);
// 如果没有找到密码历史记录返回提示
if (userPasswordHistories.isEmpty()) { if (userPasswordHistories.isEmpty()) {
return AjaxResult.success("没有找到密码历史记录。"); return AjaxResult.success("没有找到密码历史记录。");
} }
// 只取最近的五次修改记录
// 使用 Math.min 以确保不会超过实际记录数
int limit = Math.min(userPasswordHistories.size(), config.getPasswordHistoryLimit()); int limit = Math.min(userPasswordHistories.size(), config.getPasswordHistoryLimit());
for (int i = 0; i < limit; i++) { for (int i = 0; i < limit; i++) {
UserPasswordHistory history = userPasswordHistories.get(i); UserPasswordHistory history = userPasswordHistories.get(i);
// 比较新密码与历史密码
if (SecurityUtils.matchesPassword(newPassword, history.getNewPassword())) { if (SecurityUtils.matchesPassword(newPassword, history.getNewPassword())) {
return AjaxResult.error("新密码不能与最近的密码相同。"); // 新密码在最近五次记录中 return AjaxResult.error("新密码不能与最近的密码相同。");
} }
} }
return AjaxResult.success("新密码有效。"); // 新密码不在记录中 return AjaxResult.success("新密码有效。");
} }
/**
* @param userPasswordHistory
* @return
*/
@Override @Override
public int addPasswordExpiry(UserPasswordHistory userPasswordHistory) { public int addPasswordExpiry(UserPasswordHistory userPasswordHistory) {
return passwordValidatorMapper.addPasswordExpiry(userPasswordHistory); return passwordValidatorMapper.addPasswordExpiry(userPasswordHistory);
} }
/** /**
* 帮助方法用于检测密码是否包含超过 n 个连续相同字符 * 检查密码中是否包含超过 n 个连续相同字符连续递增/递减的数字或字母不区分大小写
*
* @param password 需要检查的密码
* @param n 允许的最大连续相同字符数量
* @return 如果包含连续字符则返回 true否则返回 false
*/ */
private boolean containsConsecutiveCharacters(String password, int n) { private boolean containsConsecutiveCharacters(String password, int n) {
// 检查连续相同字符
n = n + 1;
for (int i = 0; i <= password.length() - n; i++) { for (int i = 0; i <= password.length() - n; i++) {
boolean consecutive = true; boolean consecutiveSameChar = true;
for (int j = 1; j < n; j++) { for (int j = 1; j < n; j++) {
if (password.charAt(i + j) != password.charAt(i)) { if (password.charAt(i + j) != password.charAt(i)) {
consecutive = false; consecutiveSameChar = false;
break; break;
} }
} }
if (consecutive) { if (consecutiveSameChar) {
return true; // 包含超过 n 个连续相同字符
}
}
// 检查连续递增或递减的数字
for (int i = 0; i <= password.length() - n; i++) {
boolean consecutiveIncreasing = true;
boolean consecutiveDecreasing = true;
for (int j = 1; j < n; j++) {
char currentChar = password.charAt(i);
char nextChar = password.charAt(i + j);
// 检查数字递增或递减
if (Character.isDigit(currentChar) && Character.isDigit(nextChar)) {
if (nextChar != currentChar + j) {
consecutiveIncreasing = false;
}
if (nextChar != currentChar - j) {
consecutiveDecreasing = false;
}
} else {
consecutiveIncreasing = false;
consecutiveDecreasing = false;
break;
}
}
if (consecutiveIncreasing || consecutiveDecreasing) {
return true; // 包含超过 n 个递增或递减的连续数字
}
}
// 检查连续递增或递减的字母不区分大小写
for (int i = 0; i <= password.length() - n; i++) {
boolean consecutiveIncreasing = true;
boolean consecutiveDecreasing = true;
for (int j = 1; j < n; j++) {
char currentChar = Character.toLowerCase(password.charAt(i)); // 转为小写
char nextChar = Character.toLowerCase(password.charAt(i + j)); // 转为小写
// 检查字母递增或递减
if (Character.isLetter(currentChar) && Character.isLetter(nextChar)) {
if (nextChar != currentChar + j) {
consecutiveIncreasing = false;
}
if (nextChar != currentChar - j) {
consecutiveDecreasing = false;
}
} else {
consecutiveIncreasing = false;
consecutiveDecreasing = false;
break;
}
}
if (consecutiveIncreasing || consecutiveDecreasing) {
// 包含超过 n 个递增或递减的连续字母
return true; return true;
} }
} }
// 不包含连续相同字符数字或字母序列
return false; return false;
} }
} }