Index: ssts-web/src/main/webapp/WEB-INF/spring/applicationContext-disinfectsystem-service.xml =================================================================== diff -u -r40302 -r40542 --- ssts-web/src/main/webapp/WEB-INF/spring/applicationContext-disinfectsystem-service.xml (.../applicationContext-disinfectsystem-service.xml) (revision 40302) +++ ssts-web/src/main/webapp/WEB-INF/spring/applicationContext-disinfectsystem-service.xml (.../applicationContext-disinfectsystem-service.xml) (revision 40542) @@ -3250,4 +3250,14 @@ class="com.forgon.wechat.service.UserInfoOfProjectManagerImpl"> + + + + + + + + + \ No newline at end of file Index: forgon-core/src/main/java/com/forgon/security/model/IpLoginLockRecord.java =================================================================== diff -u --- forgon-core/src/main/java/com/forgon/security/model/IpLoginLockRecord.java (revision 0) +++ forgon-core/src/main/java/com/forgon/security/model/IpLoginLockRecord.java (revision 40542) @@ -0,0 +1,83 @@ +package com.forgon.security.model; + +import java.util.Date; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Index; +import javax.persistence.Table; + +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.annotations.DynamicInsert; +import org.hibernate.annotations.DynamicUpdate; + +/** + * IP登录锁定记录 + * 同一IP多次登录失败后,锁定IP,不允许登录 + * GZSZYY-119【登录管理】新增多个登录功能改进(IP登录失败锁定次数,验证码刷新规则修改) + */ +@Entity +@DynamicInsert(false) +@DynamicUpdate(true) +@Table(name = "IpLoginLockRecord", indexes = {@Index(columnList = "ipAddress", name = "ip_lck_ip_index")}) +@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) +public class IpLoginLockRecord { + + /** + * id + */ + private Long id; + + /** + * ip地址 + */ + private String ipAddress; + + /** + * 登录失败次数 + */ + private Integer failCount = 0; + + /** + * 锁定截止时间 + */ + private Date lockEndDateTime; + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getIpAddress() { + return ipAddress; + } + + public void setIpAddress(String ipAddress) { + this.ipAddress = ipAddress; + } + + public Integer getFailCount() { + return failCount; + } + + public void setFailCount(Integer failCount) { + this.failCount = failCount; + } + + public Date getLockEndDateTime() { + return lockEndDateTime; + } + + public void setLockEndDateTime(Date lockEndDateTime) { + this.lockEndDateTime = lockEndDateTime; + } + +} Index: forgon-core/src/main/java/com/forgon/security/service/IpLoginLockRecordManagerImpl.java =================================================================== diff -u --- forgon-core/src/main/java/com/forgon/security/service/IpLoginLockRecordManagerImpl.java (revision 0) +++ forgon-core/src/main/java/com/forgon/security/service/IpLoginLockRecordManagerImpl.java (revision 40542) @@ -0,0 +1,100 @@ +package com.forgon.security.service; + +import java.util.Date; + +import javax.servlet.http.HttpServletRequest; + +import net.sf.json.JSONObject; + +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; + +import com.forgon.runwithtrans.model.RunWithTransNewTask; +import com.forgon.runwithtrans.service.RunWithTransNewManager; +import com.forgon.security.model.IpLoginLockRecord; +import com.forgon.tools.hibernate.BasePoManagerImpl; +import com.forgon.tools.util.ConfigUtils; +import com.forgon.tools.util.ForgonDateUtils; + +public class IpLoginLockRecordManagerImpl extends BasePoManagerImpl implements IpLoginLockRecordManager { + + private static Logger logger = Logger.getLogger(IpLoginLockRecordManagerImpl.class); + + @Autowired + private RunWithTransNewManager runWithTransNewManager; + + @Override + public void isLockedIP(HttpServletRequest request) { + + if(request == null){ + return; + } + + String ip = request.getRemoteAddr(); + if(StringUtils.isBlank(ip)){ + return; + } + //连续登录失败的次数,达到这个次数后,账号会被锁定。如果没配置,则缺省值为6,即连续6次失败,会被锁定。(连续2次及以下登录失败只提示用户名或密码错误,不提示可以尝登录失败次数) + int loginFailuresCountOfIP = 0; + //锁定的时间,单位为分钟。如果没配置,则缺省值为180,即3个小时。 + int lockTimeInMinutesOfIP = 0; + String loginSecurirtyConfig = ConfigUtils.getSystemSetConfigByName("loginSecurirtyConfig"); + if(StringUtils.isNotBlank(loginSecurirtyConfig)){ + try { + JSONObject json = JSONObject.fromObject(loginSecurirtyConfig); + loginFailuresCountOfIP = json.optInt("loginFailuresCountOfIP", 0); + lockTimeInMinutesOfIP = json.optInt("lockTimeInMinutesOfIP", 0); + } catch (Exception e) { + } + } + if(loginFailuresCountOfIP == 0 || lockTimeInMinutesOfIP == 0){ + //不开启IP登录失败锁定的配置项 + return; + } + IpLoginLockRecord ipLoginLockRecord = this.getIpLoginLockRecordByIp(ip); + if(ipLoginLockRecord == null){ + return; + } + if(ipLoginLockRecord.getLockEndDateTime() == null){ + return; + } + + if(ipLoginLockRecord.getLockEndDateTime().after(new Date())){ + String messageAfterLocked = "您已连续多次身份验证失败,请%s后再进行尝试!"; + String logonFailNoticeMessage = String.format(messageAfterLocked, ForgonDateUtils.safelyFormatDate(ipLoginLockRecord.getLockEndDateTime(), ForgonDateUtils.SIMPLEDATEFORMAT_YYYYMMDDHHMM, "")); + request.getSession().setAttribute("message", logonFailNoticeMessage); + logger.error(logonFailNoticeMessage + "(IP登录失败次数限制)"); + throw new RuntimeException(logonFailNoticeMessage); + } + } + + @Override + public void clearIpLoginFailCount(String ip) { + if(StringUtils.isEmpty(ip)){ + return; + } + objectDao.excuteSQL(String.format("delete from %s where ipAddress = '%s'", IpLoginLockRecord.class.getSimpleName(), ip)); + } + + @Override + public IpLoginLockRecord getIpLoginLockRecordByIp(String ip) { + if(StringUtils.isBlank(ip)){ + return null; + } + + return this.getFirst("ipAddress", ip); + } + + @Override + public void recordIpLoginFailRecordWithTransNewManager(IpLoginLockRecord ipLoginLockRecord) { + runWithTransNewManager.runWith_TRANS_NEW(new RunWithTransNewTask() { + @Override + public void runTask() { + objectDao.saveOrUpdate(ipLoginLockRecord); + } + + }); + } + +} Index: ssts-web/src/main/java/com/forgon/disinfectsystem/security/userdetails/DaoUserDetailSSTSImpl.java =================================================================== diff -u -r38081 -r40542 --- ssts-web/src/main/java/com/forgon/disinfectsystem/security/userdetails/DaoUserDetailSSTSImpl.java (.../DaoUserDetailSSTSImpl.java) (revision 38081) +++ ssts-web/src/main/java/com/forgon/disinfectsystem/security/userdetails/DaoUserDetailSSTSImpl.java (.../DaoUserDetailSSTSImpl.java) (revision 40542) @@ -43,10 +43,12 @@ import com.forgon.disinfectsystem.common.CssdUtils; import com.forgon.disinfectsystem.entity.basedatamanager.supplyroomconfig.SupplyRoomConfig; import com.forgon.disinfectsystem.schedule.service.ScheduleInformationManager; +import com.forgon.security.model.IpLoginLockRecord; import com.forgon.security.model.Role; import com.forgon.security.model.User; import com.forgon.security.model.UserAttribute; import com.forgon.security.model.UserLogonRecord; +import com.forgon.security.service.IpLoginLockRecordManager; import com.forgon.security.service.RoleManager; import com.forgon.security.service.SSOAuthenticationService; import com.forgon.security.service.UserManager; @@ -85,6 +87,8 @@ private OrgUnitManager orgUnitManager; @Resource private ScheduleInformationManager scheduleInformationManager; + @Resource + private IpLoginLockRecordManager ipLoginLockRecordManager; /** * 单点登录service,此处不能用@Resource或@Autowired注解,因为这个bean所在的project不是任何项目都会依赖的,没有依赖时会报ClassNotFound @@ -146,6 +150,11 @@ if(requestAttributes != null){ request = ((ServletRequestAttributes)requestAttributes).getRequest(); } + //判断当前登录IP是否被锁定(GZSZYY-119【登录管理】新增多个登录功能改进(IP登录失败锁定次数,验证码刷新规则修改)) + if(request != null){ + ipLoginLockRecordManager.isLockedIP(request); + } + // oracle用户名区分大小写,广东省中医院的用户名的英文均为大写,采用的是oracle数据库, // 因此不再转换为小写的用户名 //username = username.toLowerCase(); @@ -194,6 +203,8 @@ } //插入登录记录 userManager.insertUserLogonRecord(userLogonRecord); + //记录ip登录失败记录(GZSZYY-119【登录管理】新增多个登录功能改进(IP登录失败锁定次数,验证码刷新规则修改)) + recordLoginFailIp(request); throw new UsernameNotFoundException("user not found in database"); }else{ //如果帐号存在,则删除session会话的消息 @@ -206,8 +217,10 @@ logger.error("User with login: " + username + " is disabled "); if(request != null){ - request.getSession().setAttribute("message", "帐号" + username + "状态为停用."); + request.getSession().setAttribute("message", messageCommon); } + //记录ip登录失败记录(GZSZYY-119【登录管理】新增多个登录功能改进(IP登录失败锁定次数,验证码刷新规则修改)) + recordLoginFailIp(request); throw new DisabledException("该用户已被停用!"); } @@ -232,8 +245,8 @@ } //连续登录失败**次后开始提醒 int seriesLogonFailBeginNoticeTimes = 3; - String messageCanTryLogon = "您还可以尝试登录%s次,请再次检查用户名、密码是否正确!"; - String messageAfterLocked = "您已连续多次身份验证失败,请%s后再进行尝试!"; + String messageCanTryLogon = username + "还可以尝试登录%s次,请再次检查用户名、密码是否正确!"; + String messageAfterLocked = username + "已连续多次身份验证失败,请%s后再进行尝试!"; Date now = new Date(); //锁定的截止时间 Date lockEndDate = currentLoginedUser.getLockEndDate(); @@ -244,12 +257,15 @@ userLogonRecord.setLogonName(username); userLogonRecord.setPassword(j_password); messageAfterLocked = String.format(messageAfterLocked, ForgonDateUtils.safelyFormatDate(lockEndDate, ForgonDateUtils.SIMPLEDATEFORMAT_YYYYMMDDHHMM, "")); + logger.info(messageAfterLocked); if(request != null){ - request.getSession().setAttribute("message", messageAfterLocked); + request.getSession().setAttribute("message", messageCommon); userLogonRecord.setIp(request.getRemoteAddr()); } //插入登录记录 userManager.insertUserLogonRecord(userLogonRecord); + //如果开启IP限制,优先提示IP锁定信息 + recordLoginFailIp(request); throw new RuntimeException(messageAfterLocked); }else{ //截止时间过了,则清除该用户的锁定截止时间字段的值 @@ -269,6 +285,9 @@ String j_passwordAfterRsaDecrypt = RSAEncrypt.decrypt(j_password); j_passwordRsaMd5 = CoderEncryption.encryptMD5ForSpringSecurity(j_passwordAfterRsaDecrypt); } catch (Exception e) { + logger.info("对密码进行MD5加密时出错!" + e.getMessage()); + //记录ip登录失败记录(GZSZYY-119【登录管理】新增多个登录功能改进(IP登录失败锁定次数,验证码刷新规则修改)) + recordLoginFailIp(request); throw new RuntimeException("对密码进行MD5加密时出错!" + e.getMessage()); } UserLogonRecord userLogonRecord = new UserLogonRecord(); @@ -290,7 +309,7 @@ //判断剩余可尝试次数是否大于0(即连续登录失败次数是否超过5次时) int canTryLogonTimes = allowLogonFailTimes - (seriesLogonFailTimes + 1); if(canTryLogonTimes > 0){ - logonFailNoticeMessage = String.format(messageCanTryLogon, canTryLogonTimes); + logger.info(String.format(messageCanTryLogon, canTryLogonTimes)); userLogonRecord.setSucc(UserLogonRecord.SUCC_FALSE); }else if(canTryLogonTimes == 0){ userLogonRecord.setSucc(UserLogonRecord.SUCC_FALSE); @@ -299,7 +318,7 @@ nextLockEndDate = cal1.getTime(); currentLoginedUser.setLockEndDate(nextLockEndDate); userManager.modifyUserLockEndDateWithTransNewManager(currentLoginedUser); - logonFailNoticeMessage = String.format(messageAfterLocked, ForgonDateUtils.safelyFormatDate(nextLockEndDate, ForgonDateUtils.SIMPLEDATEFORMAT_YYYYMMDDHHMM, "")); + logger.info(String.format(messageAfterLocked, ForgonDateUtils.safelyFormatDate(nextLockEndDate, ForgonDateUtils.SIMPLEDATEFORMAT_YYYYMMDDHHMM, ""))); }else{ //正常不会进到这个else(因为连续登录失败次数超过5次时时,会进入前面194行的lockEndDate判断的分支里) } @@ -312,6 +331,8 @@ } //插入登录记录 userManager.insertUserLogonRecord(userLogonRecord); + //记录ip登录失败记录(GZSZYY-119【登录管理】新增多个登录功能改进(ip登录失败锁定次数,验证码刷新规则修改)) + recordLoginFailIp(request); throw new RuntimeException(logonFailNoticeMessage); }else if(StringUtils.equals(password, j_passwordRsaMd5)){ password = j_passwordMd5; @@ -400,6 +421,8 @@ //登录成功后,修改用户的最后在线时间FSSZYY-37 userManager.updateUserLastOnlineTime(currentLoginedUser.getId(), new Date(), false); + //登录成功后,清除IP登录失败次数GZSZYY-119 + ipLoginLockRecordManager.clearIpLoginFailCount(request == null ? null : request.getRemoteAddr()); UserDetails acegiUser = new UserContainsSessionUser( authenticationUserName,password, @@ -410,6 +433,77 @@ } /** + * 登录失败后,记录IP锁定记录,及返回提示信息 + * @param request 请求 + */ + private void recordLoginFailIp(HttpServletRequest request) { + + if(request == null){ + return; + } + String ip = request.getRemoteAddr(); + if(StringUtils.isBlank(ip)){ + return; + } + //连续登录失败的次数,达到这个次数后,账号会被锁定。(连续2次及以下登录失败只提示用户名或密码错误,不提示可以尝登录失败次数) + int loginFailuresCountOfIP = 0; + //锁定的时间,单位为分钟。 + int lockTimeInMinutesOfIP = 0; + String loginSecurirtyConfig = ConfigUtils.getSystemSetConfigByName("loginSecurirtyConfig"); + if(StringUtils.isNotBlank(loginSecurirtyConfig)){ + try { + JSONObject json = JSONObject.fromObject(loginSecurirtyConfig); + loginFailuresCountOfIP = json.optInt("loginFailuresCountOfIP", 0); + lockTimeInMinutesOfIP = json.optInt("lockTimeInMinutesOfIP", 0); + } catch (Exception e) {} + } + if(loginFailuresCountOfIP == 0 || lockTimeInMinutesOfIP == 0){ + //不开启IP登录失败锁定的配置项 + return; + } + + //连续登录失败3次后开始提醒 + int seriesLogonFailBeginNoticeTimes = loginFailuresCountOfIP > 3 ? 3 : loginFailuresCountOfIP / 2; + String messageCanTryLogon = "您还可以尝试登录%s次,请再次检查用户名、密码是否正确!"; + String messageAfterLocked = "您已连续多次身份验证失败,请%s后再进行尝试!"; + IpLoginLockRecord ipLoginLockRecord = ipLoginLockRecordManager.getIpLoginLockRecordByIp(ip); + if(ipLoginLockRecord == null){ + ipLoginLockRecord = new IpLoginLockRecord(); + ipLoginLockRecord.setIpAddress(ip); + ipLoginLockRecord.setFailCount(1); + }else{ + ipLoginLockRecord.setFailCount(ipLoginLockRecord.getFailCount()+1); + } + + //过了锁定截止时间后,自动解锁后的第一次登录就登录失败时,需要重新计算登录失败时间 + if(ipLoginLockRecord.getLockEndDateTime() != null && !ipLoginLockRecord.getLockEndDateTime().after(new Date())){ + ipLoginLockRecord.setFailCount(1); + ipLoginLockRecord.setLockEndDateTime(null); + } + + //登录失败提示信息 + String logonFailNoticeMessage = ""; + if(ipLoginLockRecord.getFailCount() < seriesLogonFailBeginNoticeTimes){ + logonFailNoticeMessage = "用户名或者密码错误,请重新输入!"; + }else if(ipLoginLockRecord.getFailCount() < loginFailuresCountOfIP){ + logonFailNoticeMessage = String.format(messageCanTryLogon, loginFailuresCountOfIP - ipLoginLockRecord.getFailCount()); + }else{ + Calendar cal1 = Calendar.getInstance(); + cal1.add(Calendar.MINUTE, lockTimeInMinutesOfIP); + ipLoginLockRecord.setLockEndDateTime(cal1.getTime()); + logonFailNoticeMessage = String.format(messageAfterLocked, ForgonDateUtils.safelyFormatDate(ipLoginLockRecord.getLockEndDateTime(), ForgonDateUtils.SIMPLEDATEFORMAT_YYYYMMDDHHMM, "")); + } + //保存IP锁定记录 + ipLoginLockRecordManager.recordIpLoginFailRecordWithTransNewManager(ipLoginLockRecord); + if(StringUtils.isBlank(logonFailNoticeMessage)){ + logonFailNoticeMessage = "用户名或者密码错误,请重新输入!"; + } + logger.error(String.format("%s,loginFailuresCountOfIP = %s,lockTimeInMinutesOfIP = %s ", logonFailNoticeMessage, loginFailuresCountOfIP, lockTimeInMinutesOfIP)); + request.getSession().setAttribute("message", logonFailNoticeMessage); + throw new RuntimeException(logonFailNoticeMessage); + } + + /** * 获取当前登录科室的角色GYEY-765 * @param currentLoginedUser * @return Index: forgon-core/src/main/java/com/forgon/security/service/IpLoginLockRecordManager.java =================================================================== diff -u --- forgon-core/src/main/java/com/forgon/security/service/IpLoginLockRecordManager.java (revision 0) +++ forgon-core/src/main/java/com/forgon/security/service/IpLoginLockRecordManager.java (revision 40542) @@ -0,0 +1,38 @@ +package com.forgon.security.service; + +import javax.servlet.http.HttpServletRequest; + +import com.forgon.security.model.IpLoginLockRecord; +import com.forgon.tools.hibernate.BasePoManager; + +/** + * IP登录锁定记录的manager(GZSZYY-119) + */ +public interface IpLoginLockRecordManager extends BasePoManager { + + /** + * 判断IP是否被锁定,不允许登录 + * @param request 请求 + */ + public void isLockedIP(HttpServletRequest request); + + /** + * 清除登录成功的ip的登录失败次数 + * @param ip ip地址 + */ + public void clearIpLoginFailCount(String ip); + + /** + * 根据ip查询IP锁定记录 + * @param ip ip地址 + * @return IP锁定记录 + */ + public IpLoginLockRecord getIpLoginLockRecordByIp(String ip); + + /** + * 记录Ip登录失败记录 + * @param ipLoginLockRecord IP锁定记录 + */ + public void recordIpLoginFailRecordWithTransNewManager(IpLoginLockRecord ipLoginLockRecord); + +} Index: ssts-web/src/main/webapp/disinfectsystem/config/gzszyy/config.js =================================================================== diff -u -r39843 -r40542 --- ssts-web/src/main/webapp/disinfectsystem/config/gzszyy/config.js (.../config.js) (revision 39843) +++ ssts-web/src/main/webapp/disinfectsystem/config/gzszyy/config.js (.../config.js) (revision 40542) @@ -176,5 +176,7 @@ //启用设备维护分组管理功能 enableGroupManagementOfDeviceMaintenanceFunction:true, //关闭自定义表单功能 -closeFormDefinition:false +closeFormDefinition:false, +//登录安全配置 +loginSecurirtyConfig: {"loginFailuresCountOfIP":999, "lockTimeInMinutesOfIP":1} } \ No newline at end of file Index: ssts-web/src/main/webapp/disinfectsystem/config/gzszyy/menu/menuconfigure.json =================================================================== diff -u -r40383 -r40542 --- ssts-web/src/main/webapp/disinfectsystem/config/gzszyy/menu/menuconfigure.json (.../menuconfigure.json) (revision 40383) +++ ssts-web/src/main/webapp/disinfectsystem/config/gzszyy/menu/menuconfigure.json (.../menuconfigure.json) (revision 40542) @@ -2660,6 +2660,17 @@ } }, { + "requestName": "用户列表dwr接口", + "URI": "/dwr/call/plaincall/UserTableManager.findOrgUserTableList.dwr", + "parameter": { }, + "operationIds": [ + "System_User_Select" + ], + "systemSetConfig": { + "moduleWhiteList": "userManage" + } + }, + { "requestName": "权限管理", "URI": "/systemmanage/roleList.jsp", "parameter": { }, @@ -2669,6 +2680,15 @@ "systemSetConfig": { } }, { + "requestName": "权限管理角色列表dwr接口", + "URI": "/dwr/call/plaincall/RoleTableManager.findAll.dwr", + "parameter": { }, + "operationIds": [ + "System_Role_Select" + ], + "systemSetConfig": { } + }, + { "requestName": "日志管理", "URI": "/log/loggrid.jsp", "parameter": { },