PasswordPolicyValidator

PasswordPolicyValidator
This commit is contained in:
Crystal.Sea 2020-08-24 23:27:29 +08:00
parent 7bbe1f472f
commit 4f67f93c28
17 changed files with 1807 additions and 231 deletions

View File

@ -1,8 +1,13 @@
MaxKey v 2.2.0 GA 2020/**/**
*(MAXKEY-200801) 官方网站内容调整,初步增加英文版支持
*(MAXKEY-200802) CAS协议增加自定义参数回传
*(MAXKEY-200803) 优化开发集成指南
*(MAXKEY-200804) 删除冗余的文件和文件夹
*(MAXKEY-200802) 密码策略优化
*(MAXKEY-200803) CAS协议增加自定义参数回传
*(MAXKEY-200804) 优化开发集成指南
*(MAXKEY-200805) 删除冗余的文件和文件夹
*(MAXKEY-200806) CAS适配器支持
*(MAXKEY-200807) Maven版本支持
*(MAXKEY-200808) CAS spring boot demo
MaxKey v 2.1.0 GA 2020/08/01

View File

@ -291,6 +291,7 @@ subprojects {
compile group: 'org.apache.santuario', name: 'xmlsec', version: '1.5.8'
compile group: 'org.ogce', name: 'xpp3', version: '1.1.6'
compile group: 'com.thoughtworks.xstream', name: 'xstream', version: '1.4.10'
compile group: 'org.passay', name: 'passay', version: '1.6.0'
//local jars
compile fileTree(dir: "${rootDir}/maxkey-lib/", include: '*.jar')

View File

@ -68,7 +68,7 @@ public class RealmAuthenticationProvider extends AbstractAuthenticationProvider
tftcaptchaValid(auth.getOtpCaptcha(),auth.getAuthType(),userInfo);
authenticationRealm.passwordPolicyValid(userInfo);
authenticationRealm.getPasswordPolicyValidator().passwordPolicyValid(userInfo);
authenticationRealm.passwordMatches(userInfo, auth.getPassword());
authenticationRealm.grantAuthority(userInfo);

View File

@ -17,27 +17,17 @@
package org.maxkey.authn.realm;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.joda.time.format.DateTimeFormat;
import org.maxkey.authn.support.rememberme.AbstractRemeberMeService;
import org.maxkey.constants.ConstantsLoginType;
import org.maxkey.constants.ConstantsPasswordSetType;
import org.maxkey.constants.ConstantsStatus;
import org.maxkey.domain.Groups;
import org.maxkey.domain.PasswordPolicy;
import org.maxkey.domain.UserInfo;
import org.maxkey.persistence.db.PasswordPolicyRowMapper;
import org.maxkey.persistence.db.UserInfoRowMapper;
import org.maxkey.persistence.db.LoginHistoryService;
import org.maxkey.persistence.db.PasswordPolicyValidator;
import org.maxkey.persistence.db.LoginService;
import org.maxkey.util.DateUtils;
import org.maxkey.util.StringUtils;
import org.maxkey.web.WebConstants;
import org.maxkey.web.WebContext;
import org.slf4j.Logger;
@ -45,10 +35,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
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;
/**
* AbstractAuthenticationRealm.
@ -58,35 +45,18 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority;
public abstract class AbstractAuthenticationRealm {
private static Logger _logger = LoggerFactory.getLogger(AbstractAuthenticationRealm.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 HISTORY_LOGIN_INSERT_STATEMENT = "INSERT INTO MXK_HISTORY_LOGIN (ID , SESSIONID , UID , USERNAME , DISPLAYNAME , LOGINTYPE , MESSAGE , CODE , PROVIDER , SOURCEIP , BROWSER , PLATFORM , APPLICATION , LOGINURL )VALUES( ? , ? , ? , ? , ?, ? , ? , ?, ? , ? , ?, ? , ? , ?)";
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 LOGOUT_USERINFO_UPDATE_STATEMENT = "UPDATE MXK_USERINFO SET LASTLOGOFFTIME = ? , ONLINE = "
+ UserInfo.ONLINE.OFFLINE + " WHERE ID = ?";
private static final String HISTORY_LOGOUT_UPDATE_STATEMENT = "UPDATE MXK_HISTORY_LOGIN SET LOGOUTTIME = ? WHERE SESSIONID = ?";
private static final String GROUPS_SELECT_STATEMENT = "SELECT DISTINCT G.ID,G.NAME 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 = ?";
private static final String PASSWORD_POLICY_SELECT_STATEMENT = "SELECT ID,MINLENGTH,MAXLENGTH,LOWERCASE,UPPERCASE,DIGITS,SPECIALCHAR,ATTEMPTS,DURATION,EXPIRATION,USERNAME,SIMPLEPASSWORDS FROM MXK_PASSWORD_POLICY ";
protected PasswordPolicy passwordPolicy;
protected JdbcTemplate jdbcTemplate;
protected boolean provisioning;
@Autowired
protected PasswordPolicyValidator passwordPolicyValidator;
@Autowired
protected LoginService loginService;
@Autowired
protected LoginHistoryService loginHistoryService;
@Autowired
@Qualifier("remeberMeService")
@ -103,70 +73,16 @@ public abstract class AbstractAuthenticationRealm {
this.jdbcTemplate = jdbcTemplate;
}
public PasswordPolicy getPasswordPolicy() {
if (passwordPolicy == null) {
passwordPolicy = jdbcTemplate.queryForObject(PASSWORD_POLICY_SELECT_STATEMENT,
new PasswordPolicyRowMapper());
_logger.debug("query PasswordPolicy : " + passwordPolicy);
}
return passwordPolicy;
public PasswordPolicyValidator getPasswordPolicyValidator() {
return passwordPolicyValidator;
}
public boolean passwordPolicyValid(UserInfo userInfo) {
/*
* check login attempts fail times
*/
if (userInfo.getBadPasswordCount() >= getPasswordPolicy().getAttempts()) {
_logger.debug("PasswordPolicy : " + passwordPolicy);
_logger.debug("login Attempts is " + userInfo.getBadPasswordCount());
lockUser(userInfo);
throw new BadCredentialsException(
WebContext.getI18nValue("login.error.attempts") + " " + userInfo.getBadPasswordCount());
}
if (userInfo.getPasswordSetType() != ConstantsPasswordSetType.PASSWORD_NORMAL) {
WebContext.getSession().setAttribute(WebConstants.CURRENT_LOGIN_USER_PASSWORD_SET_TYPE,
userInfo.getPasswordSetType());
return true;
} else {
WebContext.getSession().setAttribute(WebConstants.CURRENT_LOGIN_USER_PASSWORD_SET_TYPE,
ConstantsPasswordSetType.PASSWORD_NORMAL);
}
/*
* check password is Expired,if Expiration equals 0,not need check
*/
if (getPasswordPolicy().getExpiration() > 0) {
String passwordLastSetTimeString = userInfo.getPasswordLastSetTime().substring(0, 19);
_logger.info("last password set date " + passwordLastSetTimeString);
DateTime currentdateTime = new DateTime();
DateTime changePwdDateTime = DateTime.parse(passwordLastSetTimeString,
DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss"));
Duration duration = new Duration(changePwdDateTime, currentdateTime);
int intDuration = Integer.parseInt(duration.getStandardDays() + "");
_logger.debug("validate duration " + intDuration);
_logger.debug("validate result " + (intDuration <= getPasswordPolicy().getExpiration()));
if (intDuration > getPasswordPolicy().getExpiration()) {
WebContext.getSession().setAttribute(WebConstants.CURRENT_LOGIN_USER_PASSWORD_SET_TYPE,
ConstantsPasswordSetType.PASSWORD_EXPIRED);
}
}
return true;
public LoginService getUserInfoLoginService() {
return loginService;
}
public UserInfo loadUserInfo(String username, String password) {
List<UserInfo> listUserInfo = jdbcTemplate.query(DEFAULT_USERINFO_SELECT_STATEMENT, new UserInfoRowMapper(),
username);
UserInfo userInfo = null;
if (listUserInfo != null && listUserInfo.size() > 0) {
userInfo = listUserInfo.get(0);
}
_logger.debug("load UserInfo : " + userInfo);
return userInfo;
return loginService.loadUserInfo(username, password);
}
public abstract boolean passwordMatches(UserInfo userInfo, String password);
@ -179,90 +95,9 @@ public abstract class AbstractAuthenticationRealm {
}
}
/**
* 閿佸畾鐢ㄦ埛锛歩slock锛<EFBFBD>1 鐢ㄦ埛瑙i攣 2 鐢ㄦ埛閿佸畾
*
* @param userInfo
*/
public void lockUser(UserInfo userInfo) {
try {
if (userInfo != null && StringUtils.isNotEmpty(userInfo.getId())) {
jdbcTemplate.update(LOCK_USER_UPDATE_STATEMENT,
new Object[] { ConstantsStatus.LOCK, new Date(), userInfo.getId() },
new int[] { Types.VARCHAR, Types.TIMESTAMP, Types.VARCHAR });
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 閿佸畾鐢ㄦ埛锛歩slock锛<EFBFBD>1 鐢ㄦ埛瑙i攣 2 鐢ㄦ埛閿佸畾
*
* @param userInfo
*/
public void unlockUser(UserInfo userInfo) {
try {
if (userInfo != null && StringUtils.isNotEmpty(userInfo.getId())) {
jdbcTemplate.update(UNLOCK_USER_UPDATE_STATEMENT,
new Object[] { ConstantsStatus.ACTIVE, new Date(), userInfo.getId() },
new int[] { Types.VARCHAR, Types.TIMESTAMP, Types.VARCHAR });
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 閲嶇疆閿欒瀵嗙爜娆暟鍜岃В閿佺敤鎴<EFBFBD>
*
* @param userInfo
*/
public void resetBadPasswordCountAndLockout(UserInfo userInfo) {
try {
if (userInfo != null && StringUtils.isNotEmpty(userInfo.getId())) {
jdbcTemplate.update(BADPASSWORDCOUNT_RESET_UPDATE_STATEMENT,
new Object[] { 0, ConstantsStatus.ACTIVE, new Date(), userInfo.getId() },
new int[] { Types.INTEGER, Types.INTEGER, Types.TIMESTAMP, Types.VARCHAR });
}
} catch (Exception e) {
e.printStackTrace();
_logger.error(e.getMessage());
}
}
/**
* 鏇存柊閿欒瀵嗙爜娆
*
* @param userInfo
*/
public void setBadPasswordCount(UserInfo userInfo) {
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 });
insertLoginHistory(userInfo, ConstantsLoginType.LOCAL, "", "xe00000004", "password error");
}
} catch (Exception e) {
e.printStackTrace();
_logger.error(e.getMessage());
}
}
public List<Groups> queryGroups(UserInfo userInfo) {
List<Groups> listGroups = jdbcTemplate.query(GROUPS_SELECT_STATEMENT, new RowMapper<Groups>() {
public Groups mapRow(ResultSet rs, int rowNum) throws SQLException {
Groups group = new Groups(rs.getString("ID"), rs.getString("NAME"), 0);
return group;
}
}, userInfo.getId());
_logger.debug("list Groups " + listGroups);
return listGroups;
return loginService.queryGroups(userInfo);
}
/**
@ -272,18 +107,7 @@ public abstract class AbstractAuthenticationRealm {
* @return ArrayList<GrantedAuthority>
*/
public ArrayList<GrantedAuthority> grantAuthority(UserInfo userInfo) {
// query roles for user
List<Groups> listGroups = queryGroups(userInfo);
// set role for spring security
ArrayList<GrantedAuthority> grantedAuthority = new ArrayList<GrantedAuthority>();
grantedAuthority.add(new SimpleGrantedAuthority("ROLE_USER"));
for (Groups group : listGroups) {
grantedAuthority.add(new SimpleGrantedAuthority(group.getId()));
}
_logger.debug("Authority : " + grantedAuthority);
return grantedAuthority;
return loginService.grantAuthority(userInfo);
}
/**
@ -296,10 +120,10 @@ public abstract class AbstractAuthenticationRealm {
* @param message
*/
public boolean insertLoginHistory(UserInfo userInfo, String type, String provider, String code, String message) {
Date loginDate = new Date();
String sessionId = WebContext.genId();
WebContext.setAttribute(WebConstants.CURRENT_USER_SESSION_ID, sessionId);
String ipAddress = WebContext.getRequestIpAddress();
userInfo.setLastLoginTime(DateUtils.formatDateTime(new Date()));
userInfo.setLastLoginIp(WebContext.getRequestIpAddress());
String platform = "";
String browser = "";
String userAgent = WebContext.getRequest().getHeader("User-Agent");
@ -337,43 +161,38 @@ public abstract class AbstractAuthenticationRealm {
}
jdbcTemplate.update(HISTORY_LOGIN_INSERT_STATEMENT,
new Object[] { WebContext.genId(), sessionId, userInfo.getId(), userInfo.getUsername(),
userInfo.getDisplayName(), type, message, code, provider, ipAddress, browser, platform,
"Browser", loginDate },
new int[] { Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,
Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,
Types.VARCHAR, Types.TIMESTAMP });
userInfo.setLastLoginTime(DateUtils.formatDateTime(loginDate));
jdbcTemplate.update(LOGIN_USERINFO_UPDATE_STATEMENT,
new Object[] { loginDate, ipAddress, userInfo.getLoginCount() + 1, userInfo.getId() },
new int[] { Types.TIMESTAMP, Types.VARCHAR, Types.INTEGER, Types.VARCHAR });
loginHistoryService.login(userInfo,sessionId, type, message, code, provider, browser, platform);
loginService.setLastLoginInfo(userInfo);
return true;
}
/**
* logout user and remove RemeberMe token
* @param response
* @return
*/
public boolean logout(HttpServletResponse response) {
if (isAuthenticated()) {
Object sessionIdAttribute = WebContext.getAttribute(WebConstants.CURRENT_USER_SESSION_ID);
UserInfo userInfo = WebContext.getUserInfo();
Date logoutDateTime = new Date();
userInfo.setLastLogoffTime(DateUtils.formatDateTime(new Date()));
if (sessionIdAttribute != null) {
remeberMeService.removeRemeberMe(response);
jdbcTemplate.update(HISTORY_LOGOUT_UPDATE_STATEMENT,
new Object[] { logoutDateTime, sessionIdAttribute.toString() },
new int[] { Types.TIMESTAMP, Types.VARCHAR });
loginHistoryService.logoff(userInfo.getLastLogoffTime(), sessionIdAttribute.toString());
}
jdbcTemplate.update(LOGOUT_USERINFO_UPDATE_STATEMENT, new Object[] { logoutDateTime, userInfo.getId() },
new int[] { Types.TIMESTAMP, Types.VARCHAR });
loginService.setLastLogoffInfo(userInfo);
_logger.debug("Session " + WebContext.getAttribute(WebConstants.CURRENT_USER_SESSION_ID) + ", user "
+ userInfo.getUsername() + " Logout, datetime " + DateUtils.toUtc(logoutDateTime) + " .");
+ userInfo.getUsername() + " Logout, datetime " + userInfo.getLastLogoffTime() + " .");
}
return true;
}
}

View File

@ -18,6 +18,7 @@
package org.maxkey.authn.realm.jdbc;
import org.maxkey.authn.realm.AbstractAuthenticationRealm;
import org.maxkey.constants.ConstantsLoginType;
import org.maxkey.crypto.password.PasswordReciprocal;
import org.maxkey.domain.UserInfo;
import org.maxkey.web.WebContext;
@ -58,7 +59,8 @@ public class DefaultJdbcAuthenticationRealm extends AbstractAuthenticationRealm
passwordMatches = passwordEncoder.matches(password,userInfo.getPassword());
_logger.debug("passwordvalid : " + passwordMatches);
if (!passwordMatches) {
setBadPasswordCount(userInfo);
passwordPolicyValidator.setBadPasswordCount(userInfo);
insertLoginHistory(userInfo, ConstantsLoginType.LOCAL, "", "xe00000004", "password error");
throw new BadCredentialsException(WebContext.getI18nValue("login.error.password"));
}
return passwordMatches;

View File

@ -34,6 +34,7 @@ import org.maxkey.crypto.keystore.KeyStoreLoader;
import org.maxkey.crypto.password.PasswordReciprocal;
import org.maxkey.crypto.password.SM3PasswordEncoder;
import org.maxkey.crypto.password.StandardPasswordEncoder;
import org.maxkey.persistence.db.PasswordPolicyValidator;
import org.maxkey.persistence.redis.RedisConnectionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -58,6 +59,8 @@ import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;
import org.maxkey.persistence.db.LoginService;
import org.maxkey.persistence.db.LoginHistoryService;
@Configuration
@ -126,6 +129,21 @@ public class ApplicationAutoConfiguration implements InitializingBean {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "passwordPolicyValidator")
public PasswordPolicyValidator passwordPolicyValidator(JdbcTemplate jdbcTemplate) {
return new PasswordPolicyValidator(jdbcTemplate);
}
@Bean(name = "loginService")
public LoginService LoginService(JdbcTemplate jdbcTemplate) {
return new LoginService(jdbcTemplate);
}
@Bean(name = "loginHistoryService")
public LoginHistoryService loginHistoryService(JdbcTemplate jdbcTemplate) {
return new LoginHistoryService(jdbcTemplate);
}
/**
* Authentication Password Encoder .
* @return

View File

@ -43,5 +43,7 @@ public final class ConstantsStatus {
public static final int STOP = 12;
public static final int APPROVED = 13;
public static final int QUITED = 14;
}

View File

@ -34,16 +34,19 @@ public class PasswordGen {
public static String CHAR_DEFAULT = CHAR_LOWERCASE + CHAR_NUMBERS + CHAR_UPPERCASE;
private Random random = new Random();
public static int DEFAULT_LENGTH = 8;
private int length;
public PasswordGen() {
length = DEFAULT_LENGTH;
}
public String gen() {
return gen(DEFAULT_LENGTH);
this.length = DEFAULT_LENGTH;
return gen(length);
}
public String gen(int length) {
this.length = length;
return gen(CHAR_DEFAULT, length);
}
@ -61,6 +64,7 @@ public class PasswordGen {
password.append(gen(CHAR_NUMBERS, numbers));
password.append(gen(CHAR_UPPERCASE, upperCase));
password.append(gen(CHAR_SPECIAL, special));
password.append(gen(CHAR_DEFAULT, length - lowerCase - upperCase - numbers -special));
// random generator String by sequence password
return gen(password.toString(), password.length());
}

View File

@ -0,0 +1,43 @@
package org.maxkey.persistence.db;
import java.sql.Types;
import org.maxkey.domain.UserInfo;
import org.maxkey.web.WebContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcTemplate;
public class LoginHistoryService {
private static Logger _logger = LoggerFactory.getLogger(LoginHistoryService.class);
private static final String HISTORY_LOGIN_INSERT_STATEMENT = "INSERT INTO MXK_HISTORY_LOGIN (ID , SESSIONID , UID , USERNAME , DISPLAYNAME , LOGINTYPE , MESSAGE , CODE , PROVIDER , SOURCEIP , BROWSER , PLATFORM , APPLICATION , LOGINURL )VALUES( ? , ? , ? , ? , ?, ? , ? , ?, ? , ? , ?, ? , ? , ?)";
private static final String HISTORY_LOGOUT_UPDATE_STATEMENT = "UPDATE MXK_HISTORY_LOGIN SET LOGOUTTIME = ? WHERE SESSIONID = ?";
protected JdbcTemplate jdbcTemplate;
public LoginHistoryService(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public void login(UserInfo userInfo,String sessionId,
String type, String message, String code, String provider,String browser, String platform) {
jdbcTemplate.update(HISTORY_LOGIN_INSERT_STATEMENT,
new Object[] { WebContext.genId(), sessionId, userInfo.getId(), userInfo.getUsername(),
userInfo.getDisplayName(), type, message, code, provider, userInfo.getLastLoginIp(), browser, platform,
"Browser", userInfo.getLastLoginTime() },
new int[] { Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,
Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,
Types.VARCHAR, Types.TIMESTAMP });
}
public void logoff(String lastLogoffTime,String sessionId) {
_logger.debug(" sessionId " +sessionId +" , lastlogofftime " + lastLogoffTime);
jdbcTemplate.update(HISTORY_LOGOUT_UPDATE_STATEMENT,
new Object[] { lastLogoffTime, sessionId },
new int[] { Types.TIMESTAMP, Types.VARCHAR });
}
}

View File

@ -0,0 +1,184 @@
package org.maxkey.persistence.db;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.maxkey.constants.ConstantsStatus;
import org.maxkey.domain.Groups;
import org.maxkey.domain.UserInfo;
import org.maxkey.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
public class LoginService {
private static Logger _logger = LoggerFactory.getLogger(LoginService.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 LOGOUT_USERINFO_UPDATE_STATEMENT = "UPDATE MXK_USERINFO SET LASTLOGOFFTIME = ? , ONLINE = "
+ UserInfo.ONLINE.OFFLINE + " WHERE ID = ?";
private static final String GROUPS_SELECT_STATEMENT = "SELECT DISTINCT G.ID,G.NAME 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 = ?";
protected JdbcTemplate jdbcTemplate;
public LoginService(){
}
public LoginService(JdbcTemplate jdbcTemplate){
this.jdbcTemplate=jdbcTemplate;
}
public UserInfo loadUserInfo(String username, String password) {
List<UserInfo> listUserInfo = jdbcTemplate.query(DEFAULT_USERINFO_SELECT_STATEMENT, new UserInfoRowMapper(),
username);
UserInfo userInfo = null;
if (listUserInfo != null && listUserInfo.size() > 0) {
userInfo = listUserInfo.get(0);
}
_logger.debug("load UserInfo : " + userInfo);
return userInfo;
}
/**
* 閿佸畾鐢ㄦ埛锛歩slock锛<EFBFBD>1 鐢ㄦ埛瑙i攣 2 鐢ㄦ埛閿佸畾
*
* @param userInfo
*/
public void lockUser(UserInfo userInfo) {
try {
if (userInfo != null && StringUtils.isNotEmpty(userInfo.getId())) {
jdbcTemplate.update(LOCK_USER_UPDATE_STATEMENT,
new Object[] { ConstantsStatus.LOCK, new Date(), userInfo.getId() },
new int[] { Types.VARCHAR, Types.TIMESTAMP, Types.VARCHAR });
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 閿佸畾鐢ㄦ埛锛歩slock锛<EFBFBD>1 鐢ㄦ埛瑙i攣 2 鐢ㄦ埛閿佸畾
*
* @param userInfo
*/
public void unlockUser(UserInfo userInfo) {
try {
if (userInfo != null && StringUtils.isNotEmpty(userInfo.getId())) {
jdbcTemplate.update(UNLOCK_USER_UPDATE_STATEMENT,
new Object[] { ConstantsStatus.ACTIVE, new Date(), userInfo.getId() },
new int[] { Types.VARCHAR, Types.TIMESTAMP, Types.VARCHAR });
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* reset BadPasswordCount And Lockout
*
* @param userInfo
*/
public void resetBadPasswordCountAndLockout(UserInfo userInfo) {
try {
if (userInfo != null && StringUtils.isNotEmpty(userInfo.getId())) {
jdbcTemplate.update(BADPASSWORDCOUNT_RESET_UPDATE_STATEMENT,
new Object[] { 0, ConstantsStatus.ACTIVE, new Date(), userInfo.getId() },
new int[] { Types.INTEGER, Types.INTEGER, Types.TIMESTAMP, Types.VARCHAR });
}
} catch (Exception e) {
e.printStackTrace();
_logger.error(e.getMessage());
}
}
/**
* if login password is error ,BadPasswordCount++ and set bad date
*
* @param userInfo
*/
public void setBadPasswordCount(UserInfo userInfo) {
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 });
}
} catch (Exception e) {
e.printStackTrace();
_logger.error(e.getMessage());
}
}
public List<Groups> queryGroups(UserInfo userInfo) {
List<Groups> listGroups = jdbcTemplate.query(GROUPS_SELECT_STATEMENT, new RowMapper<Groups>() {
public Groups mapRow(ResultSet rs, int rowNum) throws SQLException {
Groups group = new Groups(rs.getString("ID"), rs.getString("NAME"), 0);
return group;
}
}, userInfo.getId());
_logger.debug("list Groups " + listGroups);
return listGroups;
}
/**
* grant Authority by userinfo
*
* @param userInfo
* @return ArrayList<GrantedAuthority>
*/
public ArrayList<GrantedAuthority> grantAuthority(UserInfo userInfo) {
// query roles for user
List<Groups> listGroups = queryGroups(userInfo);
// set role for spring security
ArrayList<GrantedAuthority> grantedAuthority = new ArrayList<GrantedAuthority>();
grantedAuthority.add(new SimpleGrantedAuthority("ROLE_USER"));
for (Groups group : listGroups) {
grantedAuthority.add(new SimpleGrantedAuthority(group.getId()));
}
_logger.debug("Authority : " + grantedAuthority);
return grantedAuthority;
}
public void setLastLoginInfo(UserInfo userInfo) {
jdbcTemplate.update(LOGIN_USERINFO_UPDATE_STATEMENT,
new Object[] { userInfo.getLastLoginTime(), userInfo.getLastLoginIp(), userInfo.getLoginCount() + 1, userInfo.getId() },
new int[] { Types.TIMESTAMP, Types.VARCHAR, Types.INTEGER, Types.VARCHAR });
}
public void setLastLogoffInfo(UserInfo userInfo) {
jdbcTemplate.update(LOGOUT_USERINFO_UPDATE_STATEMENT, new Object[] { userInfo.getLastLogoffTime(), userInfo.getId() },
new int[] { Types.TIMESTAMP, Types.VARCHAR });
}
}

View File

@ -0,0 +1,316 @@
package org.maxkey.persistence.db;
import java.io.InputStreamReader;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Date;
import org.ehcache.UserManagedCache;
import org.ehcache.config.builders.ExpiryPolicyBuilder;
import org.ehcache.config.builders.UserManagedCacheBuilder;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.joda.time.format.DateTimeFormat;
import org.maxkey.constants.ConstantsPasswordSetType;
import org.maxkey.constants.ConstantsProperties;
import org.maxkey.constants.ConstantsStatus;
import org.maxkey.constants.ConstantsTimeInterval;
import org.maxkey.domain.PasswordPolicy;
import org.maxkey.domain.UserInfo;
import org.maxkey.util.StringUtils;
import org.maxkey.web.WebConstants;
import org.maxkey.web.WebContext;
import org.passay.CharacterRule;
import org.passay.DictionaryRule;
import org.passay.EnglishCharacterData;
import org.passay.LengthRule;
import org.passay.PasswordData;
import org.passay.PasswordValidator;
import org.passay.Rule;
import org.passay.RuleResult;
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.security.authentication.BadCredentialsException;
public class PasswordPolicyValidator {
private static Logger _logger = LoggerFactory.getLogger(PasswordPolicyValidator.class);
public static final String topWeakPasswordPropertySource =
"classpath:/org/maxkey/persistence/db/top_weak_password.txt";
protected static final UserManagedCache<String, PasswordPolicy> passwordPolicyStore =
UserManagedCacheBuilder.newUserManagedCacheBuilder(String.class, PasswordPolicy.class)
.withExpiry(
ExpiryPolicyBuilder.timeToLiveExpiration(
java.time.Duration.ofMinutes(ConstantsTimeInterval.ONE_HOUR)
)
)
.build(true);
protected PasswordPolicy passwordPolicy;
ArrayList <Rule> passwordPolicyRuleList;
protected JdbcTemplate jdbcTemplate;
private static final String PASSWORD_POLICY_KEY = "PASSWORD_POLICY_KEY";
private static final String LOCK_USER_UPDATE_STATEMENT = "UPDATE MXK_USERINFO SET ISLOCKED = ? , UNLOCKTIME = ? WHERE ID = ?";
private static final String PASSWORD_POLICY_SELECT_STATEMENT = "SELECT ID,MINLENGTH,MAXLENGTH,LOWERCASE,UPPERCASE,DIGITS,SPECIALCHAR,ATTEMPTS,DURATION,EXPIRATION,USERNAME,SIMPLEPASSWORDS FROM MXK_PASSWORD_POLICY ";
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) {
this.jdbcTemplate = jdbcTemplate;
}
public PasswordPolicy getPasswordPolicy() {
passwordPolicy = passwordPolicyStore.get(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);
passwordPolicyRuleList = new ArrayList<Rule>();
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.getSimplePasswords().length()>0 ) {
try {
ClassPathResource dictFile=
new ClassPathResource(
ConstantsProperties.classPathResource(topWeakPasswordPropertySource));
Dictionary dictionary =new DictionaryBuilder().addReader(new InputStreamReader(dictFile.getInputStream())).build();
passwordPolicyRuleList.add(new DictionaryRule(dictionary));
}catch(Exception e) {
e.printStackTrace();
}
}
}
return passwordPolicy;
}
/**
* validator .
* @param userInfo
* @return boolean
*/
public boolean validator(UserInfo userInfo) {
String password = userInfo.getPassword();
String username = userInfo.getUsername();
if(password.equals("") || password==null){
_logger.debug("password is Empty ");
return false;
}
getPasswordPolicy();
PasswordValidator validator = new PasswordValidator(passwordPolicyRuleList);
RuleResult result = validator.validate(new PasswordData(username,password));
if (result.isValid()) {
System.out.println("Password is valid");
} else {
System.out.println("Invalid password:");
for (String msg : validator.getMessages(result)) {
System.out.println(msg);
}
}
return true;
}
/**
* passwordPolicyValid .
* @param userInfo
* @return boolean
*/
public boolean passwordPolicyValid(UserInfo userInfo) {
getPasswordPolicy();
/*
* check login attempts fail times
*/
if (userInfo.getBadPasswordCount() >= passwordPolicy.getAttempts()) {
_logger.debug("PasswordPolicy : " + passwordPolicy);
_logger.debug("login Attempts is " + userInfo.getBadPasswordCount());
lockUser(userInfo);
throw new BadCredentialsException(
userInfo.getUsername() + " " +
WebContext.getI18nValue("login.error.attempts") + " " +
userInfo.getBadPasswordCount()
);
}
//locked
if(userInfo.getIsLocked()==ConstantsStatus.LOCK) {
throw new BadCredentialsException(
userInfo.getUsername()+ " "+
WebContext.getI18nValue("login.error.locked")
);
}
// inactive
if(userInfo.getStatus()!=ConstantsStatus.ACTIVE) {
throw new BadCredentialsException(
userInfo.getUsername()+ " status "+
userInfo.getStatus() +
WebContext.getI18nValue("login.error.inactive")
);
}
if (userInfo.getPasswordSetType() != ConstantsPasswordSetType.PASSWORD_NORMAL) {
WebContext.getSession().setAttribute(WebConstants.CURRENT_LOGIN_USER_PASSWORD_SET_TYPE,
userInfo.getPasswordSetType());
return true;
} else {
WebContext.getSession().setAttribute(WebConstants.CURRENT_LOGIN_USER_PASSWORD_SET_TYPE,
ConstantsPasswordSetType.PASSWORD_NORMAL);
}
/*
* check password is Expired,Expiration is Expired date ,if Expiration equals 0,not need check
*
*/
if (passwordPolicy.getExpiration() > 0) {
String passwordLastSetTimeString = userInfo.getPasswordLastSetTime().substring(0, 19);
_logger.info("last password set date " + passwordLastSetTimeString);
DateTime currentdateTime = new DateTime();
DateTime changePwdDateTime = DateTime.parse(passwordLastSetTimeString,
DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss"));
Duration duration = new Duration(changePwdDateTime, currentdateTime);
int intDuration = Integer.parseInt(duration.getStandardDays() + "");
_logger.debug("validate duration " + intDuration);
_logger.debug("validate result " + (intDuration <= passwordPolicy.getExpiration()));
if (intDuration > passwordPolicy.getExpiration()) {
WebContext.getSession().setAttribute(WebConstants.CURRENT_LOGIN_USER_PASSWORD_SET_TYPE,
ConstantsPasswordSetType.PASSWORD_EXPIRED);
}
}
return true;
}
/**
* lockUser
*
* @param userInfo
*/
public void lockUser(UserInfo userInfo) {
try {
if (userInfo != null && StringUtils.isNotEmpty(userInfo.getId())) {
jdbcTemplate.update(LOCK_USER_UPDATE_STATEMENT,
new Object[] { ConstantsStatus.LOCK, new Date(), userInfo.getId() },
new int[] { Types.VARCHAR, Types.TIMESTAMP, Types.VARCHAR });
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* unlockUser
*
* @param userInfo
*/
public void unlockUser(UserInfo userInfo) {
try {
if (userInfo != null && StringUtils.isNotEmpty(userInfo.getId())) {
jdbcTemplate.update(UNLOCK_USER_UPDATE_STATEMENT,
new Object[] { ConstantsStatus.ACTIVE, new Date(), userInfo.getId() },
new int[] { Types.VARCHAR, Types.TIMESTAMP, Types.VARCHAR });
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* reset BadPasswordCount And Lockout
*
* @param userInfo
*/
public void resetBadPasswordCountAndLockout(UserInfo userInfo) {
try {
if (userInfo != null && StringUtils.isNotEmpty(userInfo.getId())) {
jdbcTemplate.update(BADPASSWORDCOUNT_RESET_UPDATE_STATEMENT,
new Object[] { 0, ConstantsStatus.ACTIVE, new Date(), userInfo.getId() },
new int[] { Types.INTEGER, Types.INTEGER, Types.TIMESTAMP, Types.VARCHAR });
}
} catch (Exception e) {
e.printStackTrace();
_logger.error(e.getMessage());
}
}
/**
* if login password is error ,BadPasswordCount++ and set bad date
*
* @param userInfo
*/
public void setBadPasswordCount(UserInfo userInfo) {
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 });
}
} catch (Exception e) {
e.printStackTrace();
_logger.error(e.getMessage());
}
}
public void setPasswordPolicy(PasswordPolicy passwordPolicy) {
this.passwordPolicy = passwordPolicy;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -28,10 +28,11 @@ public class PasswordGenTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
PasswordGen gen=new PasswordGen();
System.out.println(gen.gen(2,2,2,1));
for(int i=1;i<100;i++){
System.out.println(gen.gen());
System.out.println(gen.gen(6));
System.out.println(gen.gen(2,2,2,2));
//System.out.println(gen.gen());
//System.out.println(gen.gen(6));
//System.out.println(gen.gen(2,2,2,0));
}
}

View File

@ -0,0 +1,31 @@
package org.maxkey.crypto.password;
import org.maxkey.domain.PasswordPolicy;
import org.maxkey.domain.UserInfo;
import org.maxkey.persistence.db.PasswordPolicyValidator;
public class PasswordPolicyValidatorTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
PasswordPolicy passwordPolicy =new PasswordPolicy();
passwordPolicy.setDigits(3);
passwordPolicy.setMaxLength(16);
passwordPolicy.setMinLength(6);
passwordPolicy.setLowerCase(2);
passwordPolicy.setUpperCase(2);
passwordPolicy.setSpecialChar(1);
passwordPolicy.setUsername(1);
passwordPolicy.setSimplePasswords("admin,1qaz,2wsx,123456,12345678,1234567890");
PasswordPolicyValidator passwordPolicyValidator =new PasswordPolicyValidator();
passwordPolicyValidator.setPasswordPolicy(passwordPolicy);
UserInfo u=new UserInfo();
u.setUsername("admin");
u.setPassword("admin无");
passwordPolicyValidator.validator(u);
}
}

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--DOCTYPE log4j:configuration SYSTEM "log4j.dtd" -->
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"
status="INFO" monitorInterval="300"
>
<appenders>
<Console name="consolePrint" target="SYSTEM_OUT">
<PatternLayout pattern="%d{YYYY-MM-dd HH:mm:ss,SSS} %-5level [%t] %logger{36}:%L - %msg%n" />
</Console>
<!-- 输出到文件按天或者超过128MB分割 每天进行归档yyyy-MM-dd -->
<RollingFile name="RollingFile" fileName="logs/maxkey.log" filePattern="logs/$${date:yyyyMMdd}/maxkey-%d{yyyy-MM-dd}-%i.log.gz">
<!-- 需要记录的级别 -->
<!-- <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY" /> -->
<PatternLayout pattern="%d{YYYY-MM-dd HH:mm:ss,SSS} %-5level [%t] %logger{36}:%L - %msg%n" />
<Policies>
<OnStartupTriggeringPolicy />
<TimeBasedTriggeringPolicy />
<SizeBasedTriggeringPolicy size="128 MB" />
</Policies>
<DefaultRolloverStrategy max="100"/>
</RollingFile>
</appenders>
<loggers>
<Logger name="org.springframework" level="INFO"></Logger>
<Logger name="org.apache.logging" level="INFO"></Logger>
<Logger name="org.maxkey" level="DEBUG"></Logger>
<root level="INFO">
<appender-ref ref="consolePrint" />
<appender-ref ref="RollingFile" />
</root>
</loggers>
</log4j:configuration>

View File

@ -27,6 +27,7 @@ import org.maxkey.domain.UserInfo;
import org.maxkey.identity.kafka.KafkaIdentityAction;
import org.maxkey.identity.kafka.KafkaIdentityTopic;
import org.maxkey.identity.kafka.KafkaProvisioningService;
import org.maxkey.persistence.db.PasswordPolicyValidator;
import org.maxkey.persistence.mapper.UserInfoMapper;
import org.maxkey.util.DateUtils;
import org.maxkey.util.StringUtils;
@ -49,6 +50,9 @@ public class UserInfoService extends JpaBaseService<UserInfo> {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
PasswordPolicyValidator passwordPolicyValidator;
@Autowired
KafkaProvisioningService kafkaProvisioningService;
@ -153,8 +157,13 @@ public class UserInfoService extends JpaBaseService<UserInfo> {
}
return userInfo;
}
public boolean changePassword(UserInfo userInfo) {
try {
passwordPolicyValidator.validator(userInfo);
if(WebContext.getUserInfo() != null) {
userInfo.setModifiedBy(WebContext.getUserInfo().getId());
@ -277,4 +286,8 @@ public class UserInfoService extends JpaBaseService<UserInfo> {
return getMapper().updateProfile(userInfo);
}
public void setPasswordPolicyValidator(PasswordPolicyValidator passwordPolicyValidator) {
this.passwordPolicyValidator = passwordPolicyValidator;
}
}

View File

@ -118,7 +118,7 @@ public class SafeController {
_logger.debug("decipherable new : "+ReciprocalUtils.encode(PasswordReciprocal.getInstance().rawPassword(userInfo.getUsername(), newPassword)));
if(newPassword.equals(confirmPassword)){
if(oldPassword==null ||
passwordEncoder.matches(PasswordReciprocal.getInstance().rawPassword(userInfo.getUsername(),oldPassword), userInfo.getPassword())){
passwordEncoder.matches(oldPassword, userInfo.getPassword())){
userInfo.setPassword(newPassword);
userInfoService.changePassword(userInfo);
//TODO syncProvisioningService.changePassword(userInfo);