代码整合优化
This commit is contained in:
@@ -49,7 +49,9 @@ public interface UserInfoMapper extends IJpaMapper<UserInfo>{
|
||||
|
||||
public void updateLockout(UserInfo userInfo);
|
||||
|
||||
public void updateBadPWDCount(UserInfo userInfo);
|
||||
public void badPasswordCount(UserInfo userInfo);
|
||||
|
||||
public void badPasswordCountReset(UserInfo userInfo);
|
||||
|
||||
public int changePassword(ChangePassword changePassword);
|
||||
|
||||
|
||||
@@ -26,33 +26,32 @@ import java.util.List;
|
||||
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dromara.maxkey.constants.ConstsPasswordSetType;
|
||||
import org.dromara.maxkey.constants.ConstsRoles;
|
||||
import org.dromara.maxkey.constants.ConstsStatus;
|
||||
import org.dromara.maxkey.entity.cnf.CnfPasswordPolicy;
|
||||
import org.dromara.maxkey.entity.idm.Groups;
|
||||
import org.dromara.maxkey.entity.idm.UserInfo;
|
||||
import org.dromara.maxkey.persistence.service.CnfPasswordPolicyService;
|
||||
import org.dromara.maxkey.persistence.service.UserInfoService;
|
||||
import org.dromara.maxkey.web.WebConstants;
|
||||
import org.dromara.maxkey.web.WebContext;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Duration;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
|
||||
public class LoginRepository {
|
||||
private static final Logger _logger = LoggerFactory.getLogger(LoginRepository.class);
|
||||
|
||||
private static final String LOCK_USER_UPDATE_STATEMENT = "update mxk_userinfo set islocked = ? , unlocktime = ? where id = ?";
|
||||
|
||||
private static final String UNLOCK_USER_UPDATE_STATEMENT = "update mxk_userinfo set islocked = ? , unlocktime = ? where id = ?";
|
||||
|
||||
private static final String BADPASSWORDCOUNT_UPDATE_STATEMENT = "update mxk_userinfo set badpasswordcount = ? , badpasswordtime = ? where id = ?";
|
||||
|
||||
private static final String BADPASSWORDCOUNT_RESET_UPDATE_STATEMENT = "update mxk_userinfo set badpasswordcount = ? , islocked = ? ,unlocktime = ? where id = ?";
|
||||
|
||||
private static final String LOGIN_USERINFO_UPDATE_STATEMENT = "update mxk_userinfo set lastlogintime = ? , lastloginip = ? , logincount = ?, online = "
|
||||
+ UserInfo.ONLINE.ONLINE + " where id = ?";
|
||||
|
||||
|
||||
|
||||
private static final String GROUPS_SELECT_STATEMENT = "select distinct g.id,g.groupcode,g.groupname from mxk_userinfo u,mxk_groups g,mxk_group_member gm where u.id = ? and u.id=gm.memberid and gm.groupid=g.id ";
|
||||
|
||||
private static final String DEFAULT_USERINFO_SELECT_STATEMENT = "select * from mxk_userinfo where username = ? ";
|
||||
@@ -64,6 +63,10 @@ public class LoginRepository {
|
||||
private static final String DEFAULT_MYAPPS_SELECT_STATEMENT = "select distinct app.id,app.appname from mxk_apps app,mxk_access gp,mxk_groups g where app.id=gp.appid and app.status = 1 and gp.groupid=g.id and g.id in(%s)";
|
||||
|
||||
protected JdbcTemplate jdbcTemplate;
|
||||
|
||||
UserInfoService userInfoService;
|
||||
|
||||
CnfPasswordPolicyService cnfPasswordPolicyService;
|
||||
|
||||
/**
|
||||
* 1 (USERNAME) 2 (USERNAME | MOBILE) 3 (USERNAME | MOBILE | EMAIL)
|
||||
@@ -74,8 +77,10 @@ public class LoginRepository {
|
||||
|
||||
}
|
||||
|
||||
public LoginRepository(JdbcTemplate jdbcTemplate){
|
||||
public LoginRepository(UserInfoService userInfoService,CnfPasswordPolicyService cnfPasswordPolicyService,JdbcTemplate jdbcTemplate){
|
||||
this.jdbcTemplate=jdbcTemplate;
|
||||
this.userInfoService = userInfoService;
|
||||
this.cnfPasswordPolicyService = cnfPasswordPolicyService;
|
||||
}
|
||||
|
||||
public UserInfo find(String username, String password) {
|
||||
@@ -116,36 +121,135 @@ public class LoginRepository {
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 閿佸畾鐢ㄦ埛锛歩slock锛<EFBFBD>1 鐢ㄦ埛瑙i攣 2 鐢ㄦ埛閿佸畾
|
||||
*
|
||||
* dynamic passwordPolicy Valid for user login.
|
||||
* @param userInfo
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean passwordPolicyValid(UserInfo userInfo) {
|
||||
|
||||
CnfPasswordPolicy passwordPolicy = cnfPasswordPolicyService.getPasswordPolicy();
|
||||
|
||||
DateTime currentdateTime = new DateTime();
|
||||
/*
|
||||
* check login attempts fail times
|
||||
*/
|
||||
if (userInfo.getBadPasswordCount() >= passwordPolicy.getAttempts() && userInfo.getBadPasswordTime() != null) {
|
||||
_logger.debug("login Attempts is {} , bad Password Time {}" , userInfo.getBadPasswordCount(),userInfo.getBadPasswordTime());
|
||||
|
||||
Duration duration = new Duration(new DateTime(userInfo.getBadPasswordTime()), currentdateTime);
|
||||
int intDuration = Integer.parseInt(duration.getStandardMinutes() + "");
|
||||
_logger.debug("bad Password duration {} , " +
|
||||
"password policy Duration {} , "+
|
||||
"validate result {}" ,
|
||||
intDuration,
|
||||
passwordPolicy.getDuration(),
|
||||
(intDuration > passwordPolicy.getDuration())
|
||||
);
|
||||
//auto unlock attempts when intDuration >= set Duration
|
||||
if(intDuration >= passwordPolicy.getDuration()) {
|
||||
_logger.debug("resetAttempts ...");
|
||||
resetAttempts(userInfo);
|
||||
}else {
|
||||
lockUser(userInfo);
|
||||
throw new BadCredentialsException(
|
||||
WebContext.getI18nValue("login.error.attempts",
|
||||
new Object[]{userInfo.getBadPasswordCount(),passwordPolicy.getDuration()})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//locked
|
||||
if(userInfo.getIsLocked()==ConstsStatus.LOCK) {
|
||||
throw new BadCredentialsException(
|
||||
userInfo.getUsername()+ " "+
|
||||
WebContext.getI18nValue("login.error.locked")
|
||||
);
|
||||
}
|
||||
// inactive
|
||||
if(userInfo.getStatus()!=ConstsStatus.ACTIVE) {
|
||||
throw new BadCredentialsException(
|
||||
userInfo.getUsername()+
|
||||
WebContext.getI18nValue("login.error.inactive")
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void applyPasswordPolicy(UserInfo userInfo) {
|
||||
CnfPasswordPolicy passwordPolicy = cnfPasswordPolicyService.getPasswordPolicy();
|
||||
|
||||
DateTime currentdateTime = new DateTime();
|
||||
//initial password need change
|
||||
if(userInfo.getLoginCount()<=0) {
|
||||
WebContext.getSession().setAttribute(WebConstants.CURRENT_USER_PASSWORD_SET_TYPE,
|
||||
ConstsPasswordSetType.INITIAL_PASSWORD);
|
||||
}
|
||||
|
||||
if (userInfo.getPasswordSetType() != ConstsPasswordSetType.PASSWORD_NORMAL) {
|
||||
WebContext.getSession().setAttribute(WebConstants.CURRENT_USER_PASSWORD_SET_TYPE,
|
||||
userInfo.getPasswordSetType());
|
||||
return;
|
||||
} else {
|
||||
WebContext.getSession().setAttribute(WebConstants.CURRENT_USER_PASSWORD_SET_TYPE,
|
||||
ConstsPasswordSetType.PASSWORD_NORMAL);
|
||||
}
|
||||
|
||||
/*
|
||||
* check password is Expired,Expiration is Expired date ,if Expiration equals 0,not need check
|
||||
*
|
||||
*/
|
||||
if (passwordPolicy.getExpiration() > 0 && userInfo.getPasswordLastSetTime() != null) {
|
||||
_logger.info("last password set date {}" , userInfo.getPasswordLastSetTime());
|
||||
Duration duration = new Duration(new DateTime(userInfo.getPasswordLastSetTime()), currentdateTime);
|
||||
int intDuration = Integer.parseInt(duration.getStandardDays() + "");
|
||||
_logger.debug("password Last Set duration day {} , " +
|
||||
"password policy Expiration {} , " +
|
||||
"validate result {}",
|
||||
intDuration,
|
||||
passwordPolicy.getExpiration(),
|
||||
intDuration <= passwordPolicy.getExpiration()
|
||||
);
|
||||
if (intDuration > passwordPolicy.getExpiration()) {
|
||||
WebContext.getSession().setAttribute(WebConstants.CURRENT_USER_PASSWORD_SET_TYPE,
|
||||
ConstsPasswordSetType.PASSWORD_EXPIRED);
|
||||
}
|
||||
}
|
||||
|
||||
resetBadPasswordCount(userInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* lockUser
|
||||
*
|
||||
* @param userInfo
|
||||
*/
|
||||
public void updateLock(UserInfo userInfo) {
|
||||
public void lockUser(UserInfo userInfo) {
|
||||
try {
|
||||
if (userInfo != null && StringUtils.isNotEmpty(userInfo.getId())) {
|
||||
jdbcTemplate.update(LOCK_USER_UPDATE_STATEMENT,
|
||||
new Object[] { ConstsStatus.LOCK, new Date(), userInfo.getId() },
|
||||
new int[] { Types.VARCHAR, Types.TIMESTAMP, Types.VARCHAR });
|
||||
if (userInfo != null
|
||||
&& StringUtils.isNotEmpty(userInfo.getId())
|
||||
&& userInfo.getIsLocked() == ConstsStatus.ACTIVE) {
|
||||
userInfo.setIsLocked(ConstsStatus.LOCK);
|
||||
userInfoService.locked(userInfo);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
_logger.error("lockUser Exception",e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 閿佸畾鐢ㄦ埛锛歩slock锛<EFBFBD>1 鐢ㄦ埛瑙i攣 2 鐢ㄦ埛閿佸畾
|
||||
*
|
||||
* unlockUser
|
||||
*
|
||||
* @param userInfo
|
||||
*/
|
||||
public void updateUnlock(UserInfo userInfo) {
|
||||
public void unlockUser(UserInfo userInfo) {
|
||||
try {
|
||||
if (userInfo != null && StringUtils.isNotEmpty(userInfo.getId())) {
|
||||
jdbcTemplate.update(UNLOCK_USER_UPDATE_STATEMENT,
|
||||
new Object[] { ConstsStatus.ACTIVE, new Date(), userInfo.getId() },
|
||||
new int[] { Types.VARCHAR, Types.TIMESTAMP, Types.VARCHAR });
|
||||
userInfo.setIsLocked(ConstsStatus.ACTIVE);
|
||||
userInfoService.lockout(userInfo);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
_logger.error("unlockUser Exception",e);
|
||||
@@ -154,39 +258,52 @@ public class LoginRepository {
|
||||
|
||||
/**
|
||||
* reset BadPasswordCount And Lockout
|
||||
*
|
||||
*
|
||||
* @param userInfo
|
||||
*/
|
||||
public void updateLockout(UserInfo userInfo) {
|
||||
public void resetAttempts(UserInfo userInfo) {
|
||||
try {
|
||||
if (userInfo != null && StringUtils.isNotEmpty(userInfo.getId())) {
|
||||
jdbcTemplate.update(BADPASSWORDCOUNT_RESET_UPDATE_STATEMENT,
|
||||
new Object[] { 0, ConstsStatus.ACTIVE, new Date(), userInfo.getId() },
|
||||
new int[] { Types.INTEGER, Types.INTEGER, Types.TIMESTAMP, Types.VARCHAR });
|
||||
userInfo.setIsLocked(ConstsStatus.ACTIVE);
|
||||
userInfo.setBadPasswordCount(0);
|
||||
userInfoService.badPasswordCountReset(userInfo);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
_logger.error("resetBadPasswordCountAndLockout Exception",e);
|
||||
_logger.error("resetAttempts Exception",e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* if login password is error ,BadPasswordCount++ and set bad date
|
||||
*
|
||||
*
|
||||
* @param userInfo
|
||||
*/
|
||||
public void updateBadPasswordCount(UserInfo userInfo) {
|
||||
private void setBadPasswordCount(String userId,int badPasswordCount) {
|
||||
try {
|
||||
if (userInfo != null && StringUtils.isNotEmpty(userInfo.getId())) {
|
||||
int badPasswordCount = userInfo.getBadPasswordCount() + 1;
|
||||
userInfo.setBadPasswordCount(badPasswordCount);
|
||||
jdbcTemplate.update(BADPASSWORDCOUNT_UPDATE_STATEMENT,
|
||||
new Object[] { badPasswordCount, new Date(), userInfo.getId() },
|
||||
new int[] { Types.INTEGER, Types.TIMESTAMP, Types.VARCHAR });
|
||||
}
|
||||
UserInfo user = new UserInfo();
|
||||
user.setId(userId);
|
||||
user.setBadPasswordCount(badPasswordCount);
|
||||
userInfoService.badPasswordCount(user);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
_logger.error(e.getMessage());
|
||||
_logger.error("setBadPasswordCount Exception",e);
|
||||
}
|
||||
}
|
||||
|
||||
public void plusBadPasswordCount(UserInfo userInfo) {
|
||||
if (userInfo != null && StringUtils.isNotEmpty(userInfo.getId())) {
|
||||
setBadPasswordCount(userInfo.getId(),userInfo.getBadPasswordCount());
|
||||
CnfPasswordPolicy passwordPolicy = cnfPasswordPolicyService.getPasswordPolicy();
|
||||
if(userInfo.getBadPasswordCount() >= passwordPolicy.getAttempts()) {
|
||||
_logger.debug("Bad Password Count {} , Max Attempts {}",
|
||||
userInfo.getBadPasswordCount() + 1,passwordPolicy.getAttempts());
|
||||
this.lockUser(userInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void resetBadPasswordCount(UserInfo userInfo) {
|
||||
if (userInfo != null && StringUtils.isNotEmpty(userInfo.getId()) && userInfo.getBadPasswordCount()>0) {
|
||||
setBadPasswordCount(userInfo.getId(),0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* Copyright [2020] [MaxKey of copyright http://www.maxkey.top]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package org.dromara.maxkey.persistence.repository;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.passay.MessageResolver;
|
||||
import org.passay.PropertiesMessageResolver;
|
||||
import org.passay.RuleResultDetail;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.NoSuchMessageException;
|
||||
import org.springframework.context.support.MessageSourceAccessor;
|
||||
|
||||
|
||||
public class PasswordPolicyMessageResolver implements MessageResolver{
|
||||
|
||||
/** A accessor for Spring's {@link MessageSource} */
|
||||
private final MessageSourceAccessor messageSourceAccessor;
|
||||
|
||||
/** The {@link MessageResolver} for fallback */
|
||||
private final MessageResolver fallbackMessageResolver = new PropertiesMessageResolver();
|
||||
|
||||
/**
|
||||
* Create a new instance with the locale associated with the current thread.
|
||||
* @param messageSource a message source managed by spring
|
||||
*/
|
||||
public PasswordPolicyMessageResolver(final MessageSource messageSource)
|
||||
{
|
||||
this.messageSourceAccessor = new MessageSourceAccessor(messageSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance with the specified locale.
|
||||
* @param messageSource a message source managed by spring
|
||||
* @param locale the locale to use for message access
|
||||
*/
|
||||
public PasswordPolicyMessageResolver(final MessageSource messageSource, final Locale locale)
|
||||
{
|
||||
this.messageSourceAccessor = new MessageSourceAccessor(messageSource, locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the message for the supplied rule result detail using Spring's {@link MessageSource}.
|
||||
* (If the message can't retrieve from a {@link MessageSource}, return default message provided by passay)
|
||||
* @param detail rule result detail
|
||||
* @return message for the detail error code
|
||||
*/
|
||||
@Override
|
||||
public String resolve(final RuleResultDetail detail)
|
||||
{
|
||||
try {
|
||||
return this.messageSourceAccessor.getMessage("PasswordPolicy."+detail.getErrorCode(), detail.getValues());
|
||||
} catch (NoSuchMessageException e) {
|
||||
return this.fallbackMessageResolver.resolve(detail);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
/*
|
||||
* Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package org.dromara.maxkey.persistence.repository;
|
||||
|
||||
import java.io.InputStreamReader;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.dromara.maxkey.constants.ConstsProperties;
|
||||
import org.dromara.maxkey.entity.cnf.CnfPasswordPolicy;
|
||||
import org.passay.CharacterOccurrencesRule;
|
||||
import org.passay.CharacterRule;
|
||||
import org.passay.DictionaryRule;
|
||||
import org.passay.EnglishCharacterData;
|
||||
import org.passay.EnglishSequenceData;
|
||||
import org.passay.IllegalSequenceRule;
|
||||
import org.passay.LengthRule;
|
||||
import org.passay.Rule;
|
||||
import org.passay.UsernameRule;
|
||||
import org.passay.WhitespaceRule;
|
||||
import org.passay.dictionary.Dictionary;
|
||||
import org.passay.dictionary.DictionaryBuilder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
|
||||
public class PasswordPolicyRepository {
|
||||
static final Logger _logger = LoggerFactory.getLogger(PasswordPolicyRepository.class);
|
||||
|
||||
//Dictionary topWeakPassword Source
|
||||
public static final String TOPWEAKPASSWORD_PROPERTYSOURCE = "classpath:/top_weak_password.txt";
|
||||
|
||||
//Cache PasswordPolicy in memory ONE_HOUR
|
||||
protected static final Cache<String, CnfPasswordPolicy> passwordPolicyStore =
|
||||
Caffeine.newBuilder()
|
||||
.expireAfterWrite(60, TimeUnit.MINUTES)
|
||||
.build();
|
||||
|
||||
protected CnfPasswordPolicy passwordPolicy;
|
||||
|
||||
protected JdbcTemplate jdbcTemplate;
|
||||
|
||||
ArrayList <Rule> passwordPolicyRuleList;
|
||||
|
||||
private static final String PASSWORD_POLICY_KEY = "PASSWORD_POLICY_KEY";
|
||||
|
||||
private static final String PASSWORD_POLICY_SELECT_STATEMENT = "select * from mxk_cnf_password_policy ";
|
||||
|
||||
public PasswordPolicyRepository(JdbcTemplate jdbcTemplate) {
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* init PasswordPolicy and load Rules
|
||||
* @return
|
||||
*/
|
||||
public CnfPasswordPolicy getPasswordPolicy() {
|
||||
passwordPolicy = passwordPolicyStore.getIfPresent(PASSWORD_POLICY_KEY);
|
||||
|
||||
if (passwordPolicy == null) {
|
||||
passwordPolicy = jdbcTemplate.queryForObject(PASSWORD_POLICY_SELECT_STATEMENT,
|
||||
new PasswordPolicyRowMapper());
|
||||
_logger.debug("query PasswordPolicy : {}" , passwordPolicy);
|
||||
passwordPolicyStore.put(PASSWORD_POLICY_KEY,passwordPolicy);
|
||||
|
||||
//RandomPasswordLength =(MaxLength +MinLength)/2
|
||||
passwordPolicy.setRandomPasswordLength(
|
||||
Math.round(
|
||||
(
|
||||
passwordPolicy.getMaxLength() +
|
||||
passwordPolicy.getMinLength()
|
||||
)/2
|
||||
)
|
||||
);
|
||||
|
||||
passwordPolicyRuleList = new ArrayList<>();
|
||||
passwordPolicyRuleList.add(new WhitespaceRule());
|
||||
passwordPolicyRuleList.add(new LengthRule(passwordPolicy.getMinLength(), passwordPolicy.getMaxLength()));
|
||||
|
||||
if(passwordPolicy.getUpperCase()>0) {
|
||||
passwordPolicyRuleList.add(new CharacterRule(EnglishCharacterData.UpperCase, passwordPolicy.getUpperCase()));
|
||||
}
|
||||
|
||||
if(passwordPolicy.getLowerCase()>0) {
|
||||
passwordPolicyRuleList.add(new CharacterRule(EnglishCharacterData.LowerCase, passwordPolicy.getLowerCase()));
|
||||
}
|
||||
|
||||
if(passwordPolicy.getDigits()>0) {
|
||||
passwordPolicyRuleList.add(new CharacterRule(EnglishCharacterData.Digit, passwordPolicy.getDigits()));
|
||||
}
|
||||
|
||||
if(passwordPolicy.getSpecialChar()>0) {
|
||||
passwordPolicyRuleList.add(new CharacterRule(EnglishCharacterData.Special, passwordPolicy.getSpecialChar()));
|
||||
}
|
||||
|
||||
if(passwordPolicy.getUsername()>0) {
|
||||
passwordPolicyRuleList.add(new UsernameRule());
|
||||
}
|
||||
|
||||
if(passwordPolicy.getOccurances()>0) {
|
||||
passwordPolicyRuleList.add(new CharacterOccurrencesRule(passwordPolicy.getOccurances()));
|
||||
}
|
||||
|
||||
if(passwordPolicy.getAlphabetical()>0) {
|
||||
passwordPolicyRuleList.add(new IllegalSequenceRule(EnglishSequenceData.Alphabetical, 4, false));
|
||||
}
|
||||
|
||||
if(passwordPolicy.getNumerical()>0) {
|
||||
passwordPolicyRuleList.add(new IllegalSequenceRule(EnglishSequenceData.Numerical, 4, false));
|
||||
}
|
||||
|
||||
if(passwordPolicy.getQwerty()>0) {
|
||||
passwordPolicyRuleList.add(new IllegalSequenceRule(EnglishSequenceData.USQwerty, 4, false));
|
||||
}
|
||||
|
||||
if(passwordPolicy.getDictionary()>0 ) {
|
||||
try {
|
||||
ClassPathResource dictFile=
|
||||
new ClassPathResource(
|
||||
ConstsProperties.classPathResource(TOPWEAKPASSWORD_PROPERTYSOURCE));
|
||||
Dictionary dictionary =new DictionaryBuilder().addReader(new InputStreamReader(dictFile.getInputStream())).build();
|
||||
passwordPolicyRuleList.add(new DictionaryRule(dictionary));
|
||||
}catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
return passwordPolicy;
|
||||
}
|
||||
|
||||
|
||||
public List<Rule> getPasswordPolicyRuleList() {
|
||||
getPasswordPolicy();
|
||||
return passwordPolicyRuleList;
|
||||
}
|
||||
|
||||
|
||||
public class PasswordPolicyRowMapper implements RowMapper<CnfPasswordPolicy> {
|
||||
|
||||
@Override
|
||||
public CnfPasswordPolicy mapRow(ResultSet rs, int rowNum) throws SQLException {
|
||||
CnfPasswordPolicy newPasswordPolicy = new CnfPasswordPolicy();
|
||||
newPasswordPolicy.setId(rs.getString("id"));
|
||||
newPasswordPolicy.setMinLength(rs.getInt("minlength"));
|
||||
newPasswordPolicy.setMaxLength(rs.getInt("maxlength"));
|
||||
newPasswordPolicy.setLowerCase(rs.getInt("lowercase"));
|
||||
newPasswordPolicy.setUpperCase(rs.getInt("uppercase"));
|
||||
newPasswordPolicy.setDigits(rs.getInt("digits"));
|
||||
newPasswordPolicy.setSpecialChar(rs.getInt("specialchar"));
|
||||
newPasswordPolicy.setAttempts(rs.getInt("attempts"));
|
||||
newPasswordPolicy.setDuration(rs.getInt("duration"));
|
||||
newPasswordPolicy.setExpiration(rs.getInt("expiration"));
|
||||
newPasswordPolicy.setUsername(rs.getInt("username"));
|
||||
newPasswordPolicy.setHistory(rs.getInt("history"));
|
||||
newPasswordPolicy.setDictionary(rs.getInt("dictionary"));
|
||||
newPasswordPolicy.setAlphabetical(rs.getInt("alphabetical"));
|
||||
newPasswordPolicy.setNumerical(rs.getInt("numerical"));
|
||||
newPasswordPolicy.setQwerty(rs.getInt("qwerty"));
|
||||
newPasswordPolicy.setOccurances(rs.getInt("occurances"));
|
||||
return newPasswordPolicy;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,323 +0,0 @@
|
||||
/*
|
||||
* Copyright [2020] [MaxKey of copyright http://www.maxkey.top]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package org.dromara.maxkey.persistence.repository;
|
||||
|
||||
import java.sql.Types;
|
||||
import java.util.Date;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dromara.maxkey.constants.ConstsPasswordSetType;
|
||||
import org.dromara.maxkey.constants.ConstsStatus;
|
||||
import org.dromara.maxkey.crypto.password.PasswordGen;
|
||||
import org.dromara.maxkey.entity.ChangePassword;
|
||||
import org.dromara.maxkey.entity.cnf.CnfPasswordPolicy;
|
||||
import org.dromara.maxkey.entity.idm.UserInfo;
|
||||
import org.dromara.maxkey.web.WebConstants;
|
||||
import org.dromara.maxkey.web.WebContext;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Duration;
|
||||
import org.passay.PasswordData;
|
||||
import org.passay.PasswordValidator;
|
||||
import org.passay.RuleResult;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
|
||||
public class PasswordPolicyValidator {
|
||||
static final Logger _logger = LoggerFactory.getLogger(PasswordPolicyValidator.class);
|
||||
|
||||
PasswordPolicyRepository passwordPolicyRepository;
|
||||
|
||||
protected JdbcTemplate jdbcTemplate;
|
||||
|
||||
MessageSource messageSource;
|
||||
|
||||
public static final String PASSWORD_POLICY_VALIDATE_RESULT = "PASSWORD_POLICY_SESSION_VALIDATE_RESULT_KEY";
|
||||
|
||||
private static final String LOCK_USER_UPDATE_STATEMENT = "update mxk_userinfo set islocked = ? , unlocktime = ? where id = ?";
|
||||
|
||||
private static final String UNLOCK_USER_UPDATE_STATEMENT = "update mxk_userinfo set islocked = ? , unlocktime = ? where id = ?";
|
||||
|
||||
private static final String BADPASSWORDCOUNT_UPDATE_STATEMENT = "update mxk_userinfo set badpasswordcount = ? , badpasswordtime = ? where id = ?";
|
||||
|
||||
private static final String BADPASSWORDCOUNT_RESET_UPDATE_STATEMENT = "update mxk_userinfo set badpasswordcount = ? , islocked = ? ,unlocktime = ? where id = ?";
|
||||
|
||||
public PasswordPolicyValidator() {
|
||||
}
|
||||
|
||||
public PasswordPolicyValidator(JdbcTemplate jdbcTemplate,MessageSource messageSource) {
|
||||
this.messageSource=messageSource;
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
this.passwordPolicyRepository = new PasswordPolicyRepository(jdbcTemplate);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* static validator .
|
||||
* @param userInfo
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean validator(ChangePassword changePassword) {
|
||||
|
||||
|
||||
String password = changePassword.getPassword();
|
||||
String username = changePassword.getUsername();
|
||||
|
||||
if(StringUtils.isBlank(username)){
|
||||
_logger.debug("username is Empty ");
|
||||
return false;
|
||||
}
|
||||
|
||||
if(StringUtils.isBlank(password)){
|
||||
_logger.debug("password is Empty ");
|
||||
return false;
|
||||
}
|
||||
|
||||
PasswordValidator validator = new PasswordValidator(
|
||||
new PasswordPolicyMessageResolver(messageSource),passwordPolicyRepository.getPasswordPolicyRuleList());
|
||||
|
||||
RuleResult result = validator.validate(new PasswordData(username,password));
|
||||
|
||||
if (result.isValid()) {
|
||||
_logger.debug("Password is valid");
|
||||
return true;
|
||||
} else {
|
||||
_logger.debug("Invalid password:");
|
||||
String passwordPolicyMessage = "";
|
||||
for (String msg : validator.getMessages(result)) {
|
||||
passwordPolicyMessage = passwordPolicyMessage + msg + "<br>";
|
||||
_logger.debug("Rule Message {}" , msg);
|
||||
}
|
||||
WebContext.setAttribute(PasswordPolicyValidator.PASSWORD_POLICY_VALIDATE_RESULT, passwordPolicyMessage);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* dynamic passwordPolicy Valid for user login.
|
||||
* @param userInfo
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean passwordPolicyValid(UserInfo userInfo) {
|
||||
|
||||
CnfPasswordPolicy passwordPolicy = passwordPolicyRepository.getPasswordPolicy();
|
||||
|
||||
DateTime currentdateTime = new DateTime();
|
||||
/*
|
||||
* check login attempts fail times
|
||||
*/
|
||||
if (userInfo.getBadPasswordCount() >= passwordPolicy.getAttempts() && userInfo.getBadPasswordTime() != null) {
|
||||
_logger.debug("login Attempts is {} , bad Password Time {}" , userInfo.getBadPasswordCount(),userInfo.getBadPasswordTime());
|
||||
|
||||
Duration duration = new Duration(new DateTime(userInfo.getBadPasswordTime()), currentdateTime);
|
||||
int intDuration = Integer.parseInt(duration.getStandardMinutes() + "");
|
||||
_logger.debug("bad Password duration {} , " +
|
||||
"password policy Duration {} , "+
|
||||
"validate result {}" ,
|
||||
intDuration,
|
||||
passwordPolicy.getDuration(),
|
||||
(intDuration > passwordPolicy.getDuration())
|
||||
);
|
||||
//auto unlock attempts when intDuration >= set Duration
|
||||
if(intDuration >= passwordPolicy.getDuration()) {
|
||||
_logger.debug("resetAttempts ...");
|
||||
resetAttempts(userInfo);
|
||||
}else {
|
||||
lockUser(userInfo);
|
||||
throw new BadCredentialsException(
|
||||
WebContext.getI18nValue("login.error.attempts",
|
||||
new Object[]{userInfo.getBadPasswordCount(),passwordPolicy.getDuration()})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//locked
|
||||
if(userInfo.getIsLocked()==ConstsStatus.LOCK) {
|
||||
throw new BadCredentialsException(
|
||||
userInfo.getUsername()+ " "+
|
||||
WebContext.getI18nValue("login.error.locked")
|
||||
);
|
||||
}
|
||||
// inactive
|
||||
if(userInfo.getStatus()!=ConstsStatus.ACTIVE) {
|
||||
throw new BadCredentialsException(
|
||||
userInfo.getUsername()+
|
||||
WebContext.getI18nValue("login.error.inactive")
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void applyPasswordPolicy(UserInfo userInfo) {
|
||||
CnfPasswordPolicy passwordPolicy = passwordPolicyRepository.getPasswordPolicy();
|
||||
|
||||
DateTime currentdateTime = new DateTime();
|
||||
//initial password need change
|
||||
if(userInfo.getLoginCount()<=0) {
|
||||
WebContext.getSession().setAttribute(WebConstants.CURRENT_USER_PASSWORD_SET_TYPE,
|
||||
ConstsPasswordSetType.INITIAL_PASSWORD);
|
||||
}
|
||||
|
||||
if (userInfo.getPasswordSetType() != ConstsPasswordSetType.PASSWORD_NORMAL) {
|
||||
WebContext.getSession().setAttribute(WebConstants.CURRENT_USER_PASSWORD_SET_TYPE,
|
||||
userInfo.getPasswordSetType());
|
||||
return;
|
||||
} else {
|
||||
WebContext.getSession().setAttribute(WebConstants.CURRENT_USER_PASSWORD_SET_TYPE,
|
||||
ConstsPasswordSetType.PASSWORD_NORMAL);
|
||||
}
|
||||
|
||||
/*
|
||||
* check password is Expired,Expiration is Expired date ,if Expiration equals 0,not need check
|
||||
*
|
||||
*/
|
||||
if (passwordPolicy.getExpiration() > 0 && userInfo.getPasswordLastSetTime() != null) {
|
||||
_logger.info("last password set date {}" , userInfo.getPasswordLastSetTime());
|
||||
Duration duration = new Duration(new DateTime(userInfo.getPasswordLastSetTime()), currentdateTime);
|
||||
int intDuration = Integer.parseInt(duration.getStandardDays() + "");
|
||||
_logger.debug("password Last Set duration day {} , " +
|
||||
"password policy Expiration {} , " +
|
||||
"validate result {}",
|
||||
intDuration,
|
||||
passwordPolicy.getExpiration(),
|
||||
intDuration <= passwordPolicy.getExpiration()
|
||||
);
|
||||
if (intDuration > passwordPolicy.getExpiration()) {
|
||||
WebContext.getSession().setAttribute(WebConstants.CURRENT_USER_PASSWORD_SET_TYPE,
|
||||
ConstsPasswordSetType.PASSWORD_EXPIRED);
|
||||
}
|
||||
}
|
||||
|
||||
resetBadPasswordCount(userInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* lockUser
|
||||
*
|
||||
* @param userInfo
|
||||
*/
|
||||
public void lockUser(UserInfo userInfo) {
|
||||
try {
|
||||
if (userInfo != null
|
||||
&& StringUtils.isNotEmpty(userInfo.getId())
|
||||
&& userInfo.getIsLocked() == ConstsStatus.ACTIVE) {
|
||||
jdbcTemplate.update(LOCK_USER_UPDATE_STATEMENT,
|
||||
new Object[] { ConstsStatus.LOCK, new Date(), userInfo.getId() },
|
||||
new int[] { Types.VARCHAR, Types.TIMESTAMP, Types.VARCHAR });
|
||||
userInfo.setIsLocked(ConstsStatus.LOCK);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
_logger.error("lockUser Exception",e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* unlockUser
|
||||
*
|
||||
* @param userInfo
|
||||
*/
|
||||
public void unlockUser(UserInfo userInfo) {
|
||||
try {
|
||||
if (userInfo != null && StringUtils.isNotEmpty(userInfo.getId())) {
|
||||
jdbcTemplate.update(UNLOCK_USER_UPDATE_STATEMENT,
|
||||
new Object[] { ConstsStatus.ACTIVE, new Date(), userInfo.getId() },
|
||||
new int[] { Types.VARCHAR, Types.TIMESTAMP, Types.VARCHAR });
|
||||
userInfo.setIsLocked(ConstsStatus.ACTIVE);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
_logger.error("unlockUser Exception",e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* reset BadPasswordCount And Lockout
|
||||
*
|
||||
* @param userInfo
|
||||
*/
|
||||
public void resetAttempts(UserInfo userInfo) {
|
||||
try {
|
||||
if (userInfo != null && StringUtils.isNotEmpty(userInfo.getId())) {
|
||||
jdbcTemplate.update(BADPASSWORDCOUNT_RESET_UPDATE_STATEMENT,
|
||||
new Object[] { 0, ConstsStatus.ACTIVE, new Date(), userInfo.getId() },
|
||||
new int[] { Types.INTEGER, Types.INTEGER, Types.TIMESTAMP, Types.VARCHAR });
|
||||
userInfo.setIsLocked(ConstsStatus.ACTIVE);
|
||||
userInfo.setBadPasswordCount(0);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
_logger.error("resetAttempts Exception",e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* if login password is error ,BadPasswordCount++ and set bad date
|
||||
*
|
||||
* @param userInfo
|
||||
*/
|
||||
private void setBadPasswordCount(String userId,int badPasswordCount) {
|
||||
try {
|
||||
jdbcTemplate.update(BADPASSWORDCOUNT_UPDATE_STATEMENT,
|
||||
new Object[] { badPasswordCount, new Date(), userId },
|
||||
new int[] { Types.INTEGER, Types.TIMESTAMP, Types.VARCHAR });
|
||||
} catch (Exception e) {
|
||||
_logger.error("setBadPasswordCount Exception",e);
|
||||
}
|
||||
}
|
||||
|
||||
public void plusBadPasswordCount(UserInfo userInfo) {
|
||||
if (userInfo != null && StringUtils.isNotEmpty(userInfo.getId())) {
|
||||
userInfo.setBadPasswordCount(userInfo.getBadPasswordCount() + 1);
|
||||
setBadPasswordCount(userInfo.getId(),userInfo.getBadPasswordCount());
|
||||
CnfPasswordPolicy passwordPolicy = passwordPolicyRepository.getPasswordPolicy();
|
||||
if(userInfo.getBadPasswordCount() >= passwordPolicy.getAttempts()) {
|
||||
_logger.debug("Bad Password Count {} , Max Attempts {}",
|
||||
userInfo.getBadPasswordCount() + 1,passwordPolicy.getAttempts());
|
||||
this.lockUser(userInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void resetBadPasswordCount(UserInfo userInfo) {
|
||||
if (userInfo != null && StringUtils.isNotEmpty(userInfo.getId()) && userInfo.getBadPasswordCount()>0) {
|
||||
setBadPasswordCount(userInfo.getId(),0);
|
||||
}
|
||||
}
|
||||
|
||||
public String generateRandomPassword() {
|
||||
CnfPasswordPolicy passwordPolicy = passwordPolicyRepository.getPasswordPolicy();
|
||||
|
||||
PasswordGen passwordGen = new PasswordGen(
|
||||
passwordPolicy.getRandomPasswordLength()
|
||||
);
|
||||
|
||||
return passwordGen.gen(
|
||||
passwordPolicy.getLowerCase(),
|
||||
passwordPolicy.getUpperCase(),
|
||||
passwordPolicy.getDigits(),
|
||||
passwordPolicy.getSpecialChar());
|
||||
}
|
||||
|
||||
public PasswordPolicyRepository getPasswordPolicyRepository() {
|
||||
return passwordPolicyRepository;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,9 +17,15 @@
|
||||
|
||||
package org.dromara.maxkey.persistence.service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.dromara.maxkey.entity.cnf.CnfPasswordPolicy;
|
||||
import org.dromara.mybatis.jpa.IJpaService;
|
||||
import org.passay.Rule;
|
||||
|
||||
public interface CnfPasswordPolicyService extends IJpaService<CnfPasswordPolicy>{
|
||||
|
||||
public CnfPasswordPolicy getPasswordPolicy();
|
||||
|
||||
public List<Rule> getPasswordPolicyRuleList();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.dromara.maxkey.persistence.service;
|
||||
|
||||
import org.dromara.maxkey.entity.ChangePassword;
|
||||
import org.dromara.maxkey.entity.cnf.CnfPasswordPolicy;
|
||||
|
||||
public interface PasswordPolicyValidatorService {
|
||||
|
||||
public CnfPasswordPolicy getPasswordPolicy();
|
||||
|
||||
public boolean validator(ChangePassword changePassword);
|
||||
|
||||
public String generateRandomPassword() ;
|
||||
}
|
||||
@@ -20,7 +20,6 @@ package org.dromara.maxkey.persistence.service;
|
||||
|
||||
import org.dromara.maxkey.entity.ChangePassword;
|
||||
import org.dromara.maxkey.entity.idm.UserInfo;
|
||||
import org.dromara.maxkey.persistence.repository.PasswordPolicyValidator;
|
||||
import org.dromara.mybatis.jpa.IJpaService;
|
||||
|
||||
/**
|
||||
@@ -84,19 +83,21 @@ public interface UserInfoService extends IJpaService<UserInfo> {
|
||||
* 锁定用户:islock:1 用户解锁 2 用户锁定
|
||||
* @param userInfo
|
||||
*/
|
||||
public void updateLocked(UserInfo userInfo) ;
|
||||
public void locked(UserInfo userInfo) ;
|
||||
|
||||
/**
|
||||
* 用户登录成功后,重置错误密码次数和解锁用户
|
||||
* @param userInfo
|
||||
*/
|
||||
public void updateLockout(UserInfo userInfo) ;
|
||||
public void lockout(UserInfo userInfo) ;
|
||||
|
||||
/**
|
||||
* 更新错误密码次数
|
||||
* @param userInfo
|
||||
*/
|
||||
public void updateBadPasswordCount(UserInfo userInfo) ;
|
||||
public void badPasswordCount(UserInfo userInfo) ;
|
||||
|
||||
public void badPasswordCountReset(UserInfo userInfo);
|
||||
|
||||
public boolean updateSharedSecret(UserInfo userInfo);
|
||||
|
||||
@@ -112,6 +113,4 @@ public interface UserInfoService extends IJpaService<UserInfo> {
|
||||
|
||||
public boolean updateStatus(UserInfo userInfo);
|
||||
|
||||
public void setPasswordPolicyValidator(PasswordPolicyValidator passwordPolicyValidator);
|
||||
|
||||
}
|
||||
|
||||
@@ -17,13 +17,139 @@
|
||||
|
||||
package org.dromara.maxkey.persistence.service.impl;
|
||||
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.dromara.maxkey.constants.ConstsProperties;
|
||||
import org.dromara.maxkey.entity.cnf.CnfPasswordPolicy;
|
||||
import org.dromara.maxkey.persistence.mapper.CnfPasswordPolicyMapper;
|
||||
import org.dromara.maxkey.persistence.service.CnfPasswordPolicyService;
|
||||
import org.dromara.mybatis.jpa.query.LambdaQuery;
|
||||
import org.dromara.mybatis.jpa.service.impl.JpaServiceImpl;
|
||||
import org.passay.CharacterOccurrencesRule;
|
||||
import org.passay.CharacterRule;
|
||||
import org.passay.DictionaryRule;
|
||||
import org.passay.EnglishCharacterData;
|
||||
import org.passay.EnglishSequenceData;
|
||||
import org.passay.IllegalSequenceRule;
|
||||
import org.passay.LengthRule;
|
||||
import org.passay.Rule;
|
||||
import org.passay.UsernameRule;
|
||||
import org.passay.WhitespaceRule;
|
||||
import org.passay.dictionary.Dictionary;
|
||||
import org.passay.dictionary.DictionaryBuilder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
|
||||
@Repository
|
||||
public class CnfPasswordPolicyServiceImpl extends JpaServiceImpl<CnfPasswordPolicyMapper,CnfPasswordPolicy> implements CnfPasswordPolicyService{
|
||||
static final Logger _logger = LoggerFactory.getLogger(CnfPasswordPolicyServiceImpl.class);
|
||||
|
||||
//Dictionary topWeakPassword Source
|
||||
public static final String TOPWEAKPASSWORD_PROPERTYSOURCE = "classpath:/top_weak_password.txt";
|
||||
|
||||
//Cache PasswordPolicy in memory ONE_HOUR
|
||||
protected static final Cache<String, CnfPasswordPolicy> passwordPolicyStore =
|
||||
Caffeine.newBuilder()
|
||||
.expireAfterWrite(60, TimeUnit.MINUTES)
|
||||
.build();
|
||||
|
||||
protected CnfPasswordPolicy passwordPolicy;
|
||||
|
||||
ArrayList <Rule> passwordPolicyRuleList;
|
||||
|
||||
private static final String PASSWORD_POLICY_KEY = "PASSWORD_POLICY_KEY";
|
||||
|
||||
/**
|
||||
* init PasswordPolicy and load Rules
|
||||
* @return
|
||||
*/
|
||||
public CnfPasswordPolicy getPasswordPolicy() {
|
||||
passwordPolicy = passwordPolicyStore.getIfPresent(PASSWORD_POLICY_KEY);
|
||||
|
||||
if (passwordPolicy == null) {
|
||||
LambdaQuery<CnfPasswordPolicy>query = new LambdaQuery<>();
|
||||
query.notNull(CnfPasswordPolicy::getId);
|
||||
passwordPolicy = this.get(query);
|
||||
_logger.debug("query PasswordPolicy : {}" , passwordPolicy);
|
||||
passwordPolicyStore.put(PASSWORD_POLICY_KEY,passwordPolicy);
|
||||
|
||||
//RandomPasswordLength =(MaxLength +MinLength)/2
|
||||
passwordPolicy.setRandomPasswordLength(
|
||||
Math.round(
|
||||
(
|
||||
passwordPolicy.getMaxLength() +
|
||||
passwordPolicy.getMinLength()
|
||||
)/2
|
||||
)
|
||||
);
|
||||
|
||||
passwordPolicyRuleList = new ArrayList<>();
|
||||
passwordPolicyRuleList.add(new WhitespaceRule());
|
||||
passwordPolicyRuleList.add(new LengthRule(passwordPolicy.getMinLength(), passwordPolicy.getMaxLength()));
|
||||
|
||||
if(passwordPolicy.getUpperCase()>0) {
|
||||
passwordPolicyRuleList.add(new CharacterRule(EnglishCharacterData.UpperCase, passwordPolicy.getUpperCase()));
|
||||
}
|
||||
|
||||
if(passwordPolicy.getLowerCase()>0) {
|
||||
passwordPolicyRuleList.add(new CharacterRule(EnglishCharacterData.LowerCase, passwordPolicy.getLowerCase()));
|
||||
}
|
||||
|
||||
if(passwordPolicy.getDigits()>0) {
|
||||
passwordPolicyRuleList.add(new CharacterRule(EnglishCharacterData.Digit, passwordPolicy.getDigits()));
|
||||
}
|
||||
|
||||
if(passwordPolicy.getSpecialChar()>0) {
|
||||
passwordPolicyRuleList.add(new CharacterRule(EnglishCharacterData.Special, passwordPolicy.getSpecialChar()));
|
||||
}
|
||||
|
||||
if(passwordPolicy.getUsername()>0) {
|
||||
passwordPolicyRuleList.add(new UsernameRule());
|
||||
}
|
||||
|
||||
if(passwordPolicy.getOccurances()>0) {
|
||||
passwordPolicyRuleList.add(new CharacterOccurrencesRule(passwordPolicy.getOccurances()));
|
||||
}
|
||||
|
||||
if(passwordPolicy.getAlphabetical()>0) {
|
||||
passwordPolicyRuleList.add(new IllegalSequenceRule(EnglishSequenceData.Alphabetical, 4, false));
|
||||
}
|
||||
|
||||
if(passwordPolicy.getNumerical()>0) {
|
||||
passwordPolicyRuleList.add(new IllegalSequenceRule(EnglishSequenceData.Numerical, 4, false));
|
||||
}
|
||||
|
||||
if(passwordPolicy.getQwerty()>0) {
|
||||
passwordPolicyRuleList.add(new IllegalSequenceRule(EnglishSequenceData.USQwerty, 4, false));
|
||||
}
|
||||
|
||||
if(passwordPolicy.getDictionary()>0 ) {
|
||||
try {
|
||||
ClassPathResource dictFile=
|
||||
new ClassPathResource(
|
||||
ConstsProperties.classPathResource(TOPWEAKPASSWORD_PROPERTYSOURCE));
|
||||
Dictionary dictionary =new DictionaryBuilder().addReader(new InputStreamReader(dictFile.getInputStream())).build();
|
||||
passwordPolicyRuleList.add(new DictionaryRule(dictionary));
|
||||
}catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
return passwordPolicy;
|
||||
}
|
||||
|
||||
|
||||
public List<Rule> getPasswordPolicyRuleList() {
|
||||
getPasswordPolicy();
|
||||
return passwordPolicyRuleList;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Copyright [2020] [MaxKey of copyright http://www.maxkey.top]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package org.dromara.maxkey.persistence.service.impl;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dromara.maxkey.crypto.password.PasswordGen;
|
||||
import org.dromara.maxkey.entity.ChangePassword;
|
||||
import org.dromara.maxkey.entity.cnf.CnfPasswordPolicy;
|
||||
import org.dromara.maxkey.persistence.service.CnfPasswordPolicyService;
|
||||
import org.dromara.maxkey.persistence.service.PasswordPolicyValidatorService;
|
||||
import org.dromara.maxkey.web.WebContext;
|
||||
import org.passay.MessageResolver;
|
||||
import org.passay.PasswordData;
|
||||
import org.passay.PasswordValidator;
|
||||
import org.passay.PropertiesMessageResolver;
|
||||
import org.passay.RuleResult;
|
||||
import org.passay.RuleResultDetail;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.NoSuchMessageException;
|
||||
import org.springframework.context.support.MessageSourceAccessor;
|
||||
|
||||
public class PasswordPolicyValidatorServiceImpl implements PasswordPolicyValidatorService{
|
||||
static final Logger _logger = LoggerFactory.getLogger(PasswordPolicyValidatorServiceImpl.class);
|
||||
|
||||
CnfPasswordPolicyService cnfPasswordPolicyService;
|
||||
|
||||
MessageSource messageSource;
|
||||
|
||||
public static final String PASSWORD_POLICY_VALIDATE_RESULT = "PASSWORD_POLICY_SESSION_VALIDATE_RESULT_KEY";
|
||||
|
||||
public PasswordPolicyValidatorServiceImpl() {
|
||||
}
|
||||
|
||||
public PasswordPolicyValidatorServiceImpl(CnfPasswordPolicyService cnfPasswordPolicyService,MessageSource messageSource) {
|
||||
this.messageSource=messageSource;
|
||||
this.cnfPasswordPolicyService = cnfPasswordPolicyService;
|
||||
|
||||
}
|
||||
|
||||
public CnfPasswordPolicy getPasswordPolicy(){
|
||||
return cnfPasswordPolicyService.getPasswordPolicy();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* static validator .
|
||||
* @param userInfo
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean validator(ChangePassword changePassword) {
|
||||
|
||||
|
||||
String password = changePassword.getPassword();
|
||||
String username = changePassword.getUsername();
|
||||
|
||||
if(StringUtils.isBlank(username)){
|
||||
_logger.debug("username is Empty ");
|
||||
return false;
|
||||
}
|
||||
|
||||
if(StringUtils.isBlank(password)){
|
||||
_logger.debug("password is Empty ");
|
||||
return false;
|
||||
}
|
||||
|
||||
PasswordValidator validator = new PasswordValidator(
|
||||
new PasswordPolicyMessageResolver(messageSource),cnfPasswordPolicyService.getPasswordPolicyRuleList());
|
||||
|
||||
RuleResult result = validator.validate(new PasswordData(username,password));
|
||||
|
||||
if (result.isValid()) {
|
||||
_logger.debug("Password is valid");
|
||||
return true;
|
||||
} else {
|
||||
_logger.debug("Invalid password:");
|
||||
String passwordPolicyMessage = "";
|
||||
for (String msg : validator.getMessages(result)) {
|
||||
passwordPolicyMessage = passwordPolicyMessage + msg + "<br>";
|
||||
_logger.debug("Rule Message {}" , msg);
|
||||
}
|
||||
WebContext.setAttribute(PasswordPolicyValidatorServiceImpl.PASSWORD_POLICY_VALIDATE_RESULT, passwordPolicyMessage);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public String generateRandomPassword() {
|
||||
CnfPasswordPolicy passwordPolicy = cnfPasswordPolicyService.getPasswordPolicy();
|
||||
|
||||
PasswordGen passwordGen = new PasswordGen(
|
||||
passwordPolicy.getRandomPasswordLength()
|
||||
);
|
||||
|
||||
return passwordGen.gen(
|
||||
passwordPolicy.getLowerCase(),
|
||||
passwordPolicy.getUpperCase(),
|
||||
passwordPolicy.getDigits(),
|
||||
passwordPolicy.getSpecialChar());
|
||||
}
|
||||
|
||||
public class PasswordPolicyMessageResolver implements MessageResolver{
|
||||
|
||||
/** A accessor for Spring's {@link MessageSource} */
|
||||
private final MessageSourceAccessor messageSourceAccessor;
|
||||
|
||||
/** The {@link MessageResolver} for fallback */
|
||||
private final MessageResolver fallbackMessageResolver = new PropertiesMessageResolver();
|
||||
|
||||
/**
|
||||
* Create a new instance with the locale associated with the current thread.
|
||||
* @param messageSource a message source managed by spring
|
||||
*/
|
||||
public PasswordPolicyMessageResolver(final MessageSource messageSource)
|
||||
{
|
||||
this.messageSourceAccessor = new MessageSourceAccessor(messageSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance with the specified locale.
|
||||
* @param messageSource a message source managed by spring
|
||||
* @param locale the locale to use for message access
|
||||
*/
|
||||
public PasswordPolicyMessageResolver(final MessageSource messageSource, final Locale locale)
|
||||
{
|
||||
this.messageSourceAccessor = new MessageSourceAccessor(messageSource, locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the message for the supplied rule result detail using Spring's {@link MessageSource}.
|
||||
* (If the message can't retrieve from a {@link MessageSource}, return default message provided by passay)
|
||||
* @param detail rule result detail
|
||||
* @return message for the detail error code
|
||||
*/
|
||||
@Override
|
||||
public String resolve(final RuleResultDetail detail)
|
||||
{
|
||||
try {
|
||||
return this.messageSourceAccessor.getMessage("PasswordPolicy."+detail.getErrorCode(), detail.getValues());
|
||||
} catch (NoSuchMessageException e) {
|
||||
return this.fallbackMessageResolver.resolve(detail);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -28,8 +28,8 @@ import org.dromara.maxkey.entity.Accounts;
|
||||
import org.dromara.maxkey.entity.ChangePassword;
|
||||
import org.dromara.maxkey.entity.idm.UserInfo;
|
||||
import org.dromara.maxkey.persistence.mapper.UserInfoMapper;
|
||||
import org.dromara.maxkey.persistence.repository.PasswordPolicyValidator;
|
||||
import org.dromara.maxkey.persistence.service.AccountsService;
|
||||
import org.dromara.maxkey.persistence.service.PasswordPolicyValidatorService;
|
||||
import org.dromara.maxkey.persistence.service.UserInfoService;
|
||||
import org.dromara.maxkey.provision.ProvisionAct;
|
||||
import org.dromara.maxkey.provision.ProvisionService;
|
||||
@@ -55,7 +55,7 @@ public class UserInfoServiceImpl extends JpaServiceImpl<UserInfoMapper,UserInfo>
|
||||
private PasswordEncoder passwordEncoder;
|
||||
|
||||
@Autowired
|
||||
PasswordPolicyValidator passwordPolicyValidator;
|
||||
PasswordPolicyValidatorService passwordPolicyValidatorService;
|
||||
|
||||
@Autowired
|
||||
ProvisionService provisionService;
|
||||
@@ -256,7 +256,7 @@ public class UserInfoServiceImpl extends JpaServiceImpl<UserInfoMapper,UserInfo>
|
||||
*/
|
||||
public boolean changePassword( ChangePassword changePassword) {
|
||||
try {
|
||||
WebContext.setAttribute(PasswordPolicyValidator.PASSWORD_POLICY_VALIDATE_RESULT, "");
|
||||
WebContext.setAttribute(PasswordPolicyValidatorServiceImpl.PASSWORD_POLICY_VALIDATE_RESULT, "");
|
||||
UserInfo userInfo = this.findByUsername(changePassword.getUsername());
|
||||
if(changePassword.getPassword().equals(changePassword.getConfirmPassword())){
|
||||
if(StringUtils.isNotBlank(changePassword.getOldPassword()) &&
|
||||
@@ -268,15 +268,15 @@ public class UserInfoServiceImpl extends JpaServiceImpl<UserInfoMapper,UserInfo>
|
||||
}else {
|
||||
if(StringUtils.isNotBlank(changePassword.getOldPassword())&&
|
||||
passwordEncoder.matches(changePassword.getPassword(), userInfo.getPassword())) {
|
||||
WebContext.setAttribute(PasswordPolicyValidator.PASSWORD_POLICY_VALIDATE_RESULT,
|
||||
WebContext.setAttribute(PasswordPolicyValidatorServiceImpl.PASSWORD_POLICY_VALIDATE_RESULT,
|
||||
WebContext.getI18nValue("PasswordPolicy.OLD_PASSWORD_MATCH"));
|
||||
}else {
|
||||
WebContext.setAttribute(PasswordPolicyValidator.PASSWORD_POLICY_VALIDATE_RESULT,
|
||||
WebContext.setAttribute(PasswordPolicyValidatorServiceImpl.PASSWORD_POLICY_VALIDATE_RESULT,
|
||||
WebContext.getI18nValue("PasswordPolicy.OLD_PASSWORD_NOT_MATCH"));
|
||||
}
|
||||
}
|
||||
}else {
|
||||
WebContext.setAttribute(PasswordPolicyValidator.PASSWORD_POLICY_VALIDATE_RESULT,
|
||||
WebContext.setAttribute(PasswordPolicyValidatorServiceImpl.PASSWORD_POLICY_VALIDATE_RESULT,
|
||||
WebContext.getI18nValue("PasswordPolicy.CONFIRMPASSWORD_NOT_MATCH"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@@ -297,7 +297,7 @@ public class UserInfoServiceImpl extends JpaServiceImpl<UserInfoMapper,UserInfo>
|
||||
_logger.debug("decipherable old : {}" , changePassword.getDecipherable());
|
||||
_logger.debug("decipherable new : {}" , PasswordReciprocal.getInstance().encode(changePassword.getDecipherable()));
|
||||
|
||||
if (passwordPolicy && !passwordPolicyValidator.validator(changePassword)) {
|
||||
if (passwordPolicy && !passwordPolicyValidatorService.validator(changePassword)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -317,7 +317,7 @@ public class UserInfoServiceImpl extends JpaServiceImpl<UserInfoMapper,UserInfo>
|
||||
}
|
||||
|
||||
public String randomPassword() {
|
||||
return passwordPolicyValidator.generateRandomPassword();
|
||||
return passwordPolicyValidatorService.generateRandomPassword();
|
||||
}
|
||||
|
||||
public void changePasswordProvisioning(ChangePassword changePassworded) {
|
||||
@@ -340,10 +340,10 @@ public class UserInfoServiceImpl extends JpaServiceImpl<UserInfoMapper,UserInfo>
|
||||
|
||||
|
||||
/**
|
||||
* 锁定用户:islock:1 用户解锁 2 用户锁定
|
||||
* 锁定用户:islock:1 解锁 5 锁定
|
||||
* @param userInfo
|
||||
*/
|
||||
public void updateLocked(UserInfo userInfo) {
|
||||
public void locked(UserInfo userInfo) {
|
||||
try {
|
||||
if(userInfo != null && StringUtils.isNotEmpty(userInfo.getId())) {
|
||||
userInfo.setIsLocked(ConstsStatus.LOCK);
|
||||
@@ -358,10 +358,10 @@ public class UserInfoServiceImpl extends JpaServiceImpl<UserInfoMapper,UserInfo>
|
||||
* 用户登录成功后,重置错误密码次数和解锁用户
|
||||
* @param userInfo
|
||||
*/
|
||||
public void updateLockout(UserInfo userInfo) {
|
||||
public void lockout(UserInfo userInfo) {
|
||||
try {
|
||||
if(userInfo != null && StringUtils.isNotEmpty(userInfo.getId())) {
|
||||
userInfo.setIsLocked(ConstsStatus.START);
|
||||
userInfo.setIsLocked(ConstsStatus.ACTIVE);
|
||||
userInfo.setBadPasswordCount(0);
|
||||
getMapper().updateLockout(userInfo);
|
||||
}
|
||||
@@ -374,12 +374,26 @@ public class UserInfoServiceImpl extends JpaServiceImpl<UserInfoMapper,UserInfo>
|
||||
* 更新错误密码次数
|
||||
* @param userInfo
|
||||
*/
|
||||
public void updateBadPasswordCount(UserInfo userInfo) {
|
||||
public void badPasswordCount(UserInfo userInfo) {
|
||||
try {
|
||||
if(userInfo != null && StringUtils.isNotEmpty(userInfo.getId())) {
|
||||
int updateBadPWDCount = userInfo.getBadPasswordCount() + 1;
|
||||
userInfo.setBadPasswordCount(updateBadPWDCount);
|
||||
getMapper().updateBadPWDCount(userInfo);
|
||||
getMapper().badPasswordCount(userInfo);
|
||||
}
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置错误密码次数
|
||||
* @param userInfo
|
||||
*/
|
||||
public void badPasswordCountReset(UserInfo userInfo) {
|
||||
try {
|
||||
if(userInfo != null && StringUtils.isNotEmpty(userInfo.getId())) {
|
||||
getMapper().badPasswordCountReset(userInfo);
|
||||
}
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
@@ -414,8 +428,4 @@ public class UserInfoServiceImpl extends JpaServiceImpl<UserInfoMapper,UserInfo>
|
||||
return getMapper().updateStatus(userInfo) > 0;
|
||||
}
|
||||
|
||||
public void setPasswordPolicyValidator(PasswordPolicyValidator passwordPolicyValidator) {
|
||||
this.passwordPolicyValidator = passwordPolicyValidator;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -65,8 +65,9 @@
|
||||
<update id="updateLocked" parameterType="UserInfo" >
|
||||
update mxk_userinfo set
|
||||
<if test="isLocked != null">
|
||||
islocked = #{isLocked},
|
||||
islocked = #{isLocked},
|
||||
</if>
|
||||
unlockdate = current_timestamp
|
||||
modifieddate = current_timestamp
|
||||
where
|
||||
id = #{id}
|
||||
@@ -75,14 +76,29 @@
|
||||
<update id="updateLockout" parameterType="UserInfo" >
|
||||
update mxk_userinfo set
|
||||
<if test="isLocked != null">
|
||||
islocked = #{isLocked},
|
||||
badpwdcount = 0,
|
||||
islocked = #{isLocked},
|
||||
badpasswordcount = 0,
|
||||
</if>
|
||||
unlockdate = current_timestamp,
|
||||
modifieddate = current_timestamp
|
||||
where
|
||||
id = #{id}
|
||||
</update>
|
||||
|
||||
<update id="badPasswordCount" parameterType="UserInfo" >
|
||||
update mxk_userinfo set
|
||||
badpasswordcount = badpasswordcount + 1 ,
|
||||
badpasswordtime = current_timestamp
|
||||
where id = #{id}
|
||||
</update>
|
||||
|
||||
<update id="badPasswordCountReset" parameterType="UserInfo" >
|
||||
update mxk_userinfo set
|
||||
badpasswordcount = 0 ,
|
||||
islocked = 1 ,
|
||||
unlocktime = current_timestamp
|
||||
where id = #{id}
|
||||
</update>
|
||||
|
||||
<update id="changePassword" parameterType="ChangePassword" >
|
||||
update mxk_userinfo set
|
||||
|
||||
Reference in New Issue
Block a user