Skip to content

Commit 73238dd

Browse files
committed
支持弱口令校验及密码过期
1 parent 658ae5d commit 73238dd

5 files changed

Lines changed: 163 additions & 1 deletion

File tree

config/application.properties

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,28 @@ media.server.media_type=1
111111
## false: 发送验证码和登录时,如果客户端传了slideVerifyToken就验证,没传就不验证
112112
slide.verify.force=false
113113

114+
## 弱口令校验配置(修改密码、重置密码时生效)
115+
## 是否开启弱口令校验:true=开启,false=关闭
116+
password.weak_check.enable=false
117+
## 密码最小长度(不满足则视为弱口令)
118+
password.weak_check.min_length=8
119+
## 是否要求密码包含大写字母
120+
password.weak_check.require_uppercase=false
121+
## 是否要求密码包含小写字母
122+
password.weak_check.require_lowercase=false
123+
## 是否要求密码包含数字
124+
password.weak_check.require_digit=false
125+
## 是否要求密码包含特殊字符(非字母数字字符)
126+
password.weak_check.require_special_char=false
127+
## 禁止使用的常见弱口令列表,英文逗号分隔,密码与列表中任意一项完全匹配则拒绝
128+
password.weak_check.forbidden_list=123456,password,123456789,12345678,111111,qwerty,abc123,123123,000000,iloveyou,admin
129+
130+
## 密码过期策略配置(仅对密码登录生效)
131+
## 是否开启密码过期校验:true=开启,false=关闭
132+
password.expiry.enable=false
133+
## 密码有效期(天),超过此天数后登录将被拒绝,要求用户重置密码。0 表示永不过期
134+
password.expiry.days=90
135+
114136
# 使用这个目录作为临时目录,必须配置有效目录。
115137
local.media.temp_storage=/Users/imhao/wildfire_upload_tmp/
116138

src/main/java/cn/wildfirechat/app/RestResult.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ public enum RestCode {
2424
ERROR_FAILURE_TOO_MUCH_TIMES(20, "密码错误次数太多,请等5分钟再试试"),
2525
ERROR_USER_FORBIDDEN(21, "用户被封禁"),
2626
ERROR_SLIDE_VERIFY_NOT_PASS(22, "滑动验证未通过"),
27-
ERROR_CONFERENCE_QUOTA_EXCEEDED(23, "会议额度已用完");
27+
ERROR_CONFERENCE_QUOTA_EXCEEDED(23, "会议额度已用完"),
28+
ERROR_WEAK_PASSWORD(24, "密码强度不够,请设置更复杂的密码"),
29+
ERROR_PASSWORD_EXPIRED(25, "密码已过期,请重置密码");
2830
public int code;
2931
public String msg;
3032

src/main/java/cn/wildfirechat/app/ServiceImpl.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,15 @@ public class ServiceImpl implements Service {
8989
@Autowired
9090
private cn.wildfirechat.app.slide.SlideVerifyService slideVerifyService;
9191

92+
@Autowired
93+
private cn.wildfirechat.app.tools.WeakPasswordChecker weakPasswordChecker;
94+
95+
@Value("${password.expiry.enable:false}")
96+
private boolean passwordExpiryEnable;
97+
98+
@Value("${password.expiry.days:90}")
99+
private int passwordExpiryDays;
100+
92101
@Value("${slide.verify.force:false}")
93102
private boolean forceSlideVerify;
94103

@@ -615,6 +624,15 @@ public RestResult loginWithPassword(HttpServletResponse response, String mobile,
615624
up.setTryCount(0);
616625
up.setLastTryTime(0);
617626
userPasswordRepository.save(up);
627+
628+
// 检查密码是否已过期
629+
if (passwordExpiryEnable && passwordExpiryDays > 0) {
630+
long updateTime = up.getPasswordUpdateTime();
631+
// updateTime 为 0 表示历史数据未记录更新时间,视为已过期
632+
if (updateTime == 0 || System.currentTimeMillis() - updateTime > (long) passwordExpiryDays * 24 * 60 * 60 * 1000) {
633+
return RestResult.error(ERROR_PASSWORD_EXPIRED);
634+
}
635+
}
618636
} else {
619637
return RestResult.error(RestResult.RestCode.ERROR_CODE_INCORRECT);
620638
}
@@ -648,6 +666,9 @@ public RestResult changePassword(String oldPwd, String newPwd, String slideVerif
648666

649667
Subject subject = SecurityUtils.getSubject();
650668
String userId = (String) subject.getSession().getAttribute("userId");
669+
if (weakPasswordChecker.isWeakPassword(newPwd)) {
670+
return RestResult.error(ERROR_WEAK_PASSWORD);
671+
}
651672
Optional<UserPassword> optional = userPasswordRepository.findById(userId);
652673
if (optional.isPresent()) {
653674
try {
@@ -697,6 +718,9 @@ public RestResult resetPassword(String mobile, String resetCode, String newPwd)
697718
return RestResult.error(ERROR_CODE_EXPIRED);
698719
}
699720
if(resetCode.equals(up.getResetCode()) || (!StringUtils.isEmpty(superCode) && resetCode.equals(superCode))) {
721+
if (weakPasswordChecker.isWeakPassword(newPwd)) {
722+
return RestResult.error(ERROR_WEAK_PASSWORD);
723+
}
700724
try {
701725
changePassword(up, newPwd);
702726
up.setResetCode(null);
@@ -723,6 +747,7 @@ private UserPassword changePassword(UserPassword up, String password) throws Exc
723747
String hashedPwd = Base64.getEncoder().encodeToString(hashed);
724748
up.setPassword(hashedPwd);
725749
up.setSalt(salt);
750+
up.setPasswordUpdateTime(System.currentTimeMillis());
726751
userPasswordRepository.save(up);
727752
return up;
728753
}

src/main/java/cn/wildfirechat/app/jpa/UserPassword.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ public class UserPassword {
2424

2525
private long lastTryTime;
2626

27+
/** 密码最后一次更新时间(毫秒时间戳),0 表示从未设置过密码或历史数据无记录 */
28+
private long passwordUpdateTime;
29+
2730
public UserPassword() {
2831
}
2932

@@ -105,4 +108,12 @@ public long getLastTryTime() {
105108
public void setLastTryTime(long lastTryTime) {
106109
this.lastTryTime = lastTryTime;
107110
}
111+
112+
public long getPasswordUpdateTime() {
113+
return passwordUpdateTime;
114+
}
115+
116+
public void setPasswordUpdateTime(long passwordUpdateTime) {
117+
this.passwordUpdateTime = passwordUpdateTime;
118+
}
108119
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package cn.wildfirechat.app.tools;
2+
3+
import org.springframework.beans.factory.annotation.Value;
4+
import org.springframework.stereotype.Component;
5+
6+
import java.util.Arrays;
7+
import java.util.HashSet;
8+
import java.util.Set;
9+
10+
/**
11+
* 弱口令检验组件
12+
* <p>
13+
* 通过配置文件控制检验规则:
14+
* <ul>
15+
* <li>password.weak_check.enable — 是否开启弱口令校验(默认 true)</li>
16+
* <li>password.weak_check.min_length — 最小密码长度(默认 8)</li>
17+
* <li>password.weak_check.require_uppercase — 是否要求大写字母(默认 false)</li>
18+
* <li>password.weak_check.require_lowercase — 是否要求小写字母(默认 false)</li>
19+
* <li>password.weak_check.require_digit — 是否要求数字(默认 false)</li>
20+
* <li>password.weak_check.require_special_char — 是否要求特殊字符(默认 false)</li>
21+
* <li>password.weak_check.forbidden_list — 禁止使用的常见弱口令,逗号分隔</li>
22+
* </ul>
23+
*/
24+
@Component
25+
public class WeakPasswordChecker {
26+
27+
@Value("${password.weak_check.enable:true}")
28+
private boolean enable;
29+
30+
@Value("${password.weak_check.min_length:8}")
31+
private int minLength;
32+
33+
@Value("${password.weak_check.require_uppercase:false}")
34+
private boolean requireUppercase;
35+
36+
@Value("${password.weak_check.require_lowercase:false}")
37+
private boolean requireLowercase;
38+
39+
@Value("${password.weak_check.require_digit:false}")
40+
private boolean requireDigit;
41+
42+
@Value("${password.weak_check.require_special_char:false}")
43+
private boolean requireSpecialChar;
44+
45+
/**
46+
* 逗号分隔的禁止密码列表,例如:
47+
* password.weak_check.forbidden_list=123456,password,111111,qwerty,abc123
48+
*/
49+
@Value("${password.weak_check.forbidden_list:123456,password,123456789,12345678,111111,qwerty,abc123,123123,000000,iloveyou,admin,letmein,welcome,monkey,dragon}")
50+
private String forbiddenListStr;
51+
52+
private Set<String> forbiddenSet;
53+
54+
@javax.annotation.PostConstruct
55+
private void init() {
56+
forbiddenSet = new HashSet<>();
57+
if (forbiddenListStr != null && !forbiddenListStr.isEmpty()) {
58+
Arrays.stream(forbiddenListStr.split(","))
59+
.map(String::trim)
60+
.filter(s -> !s.isEmpty())
61+
.forEach(forbiddenSet::add);
62+
}
63+
}
64+
65+
/**
66+
* 检查密码是否为弱口令。
67+
*
68+
* @param password 待检查的明文密码
69+
* @return {@code true} 表示是弱口令(不符合要求),{@code false} 表示密码合格
70+
*/
71+
public boolean isWeakPassword(String password) {
72+
if (!enable) {
73+
return false;
74+
}
75+
76+
if (password == null || password.length() < minLength) {
77+
return true;
78+
}
79+
80+
if (requireUppercase && password.chars().noneMatch(Character::isUpperCase)) {
81+
return true;
82+
}
83+
84+
if (requireLowercase && password.chars().noneMatch(Character::isLowerCase)) {
85+
return true;
86+
}
87+
88+
if (requireDigit && password.chars().noneMatch(Character::isDigit)) {
89+
return true;
90+
}
91+
92+
if (requireSpecialChar && password.chars().allMatch(c -> Character.isLetterOrDigit(c) || Character.isWhitespace(c))) {
93+
return true;
94+
}
95+
96+
if (forbiddenSet != null && forbiddenSet.contains(password)) {
97+
return true;
98+
}
99+
100+
return false;
101+
}
102+
}

0 commit comments

Comments
 (0)