这两天在调查“ 输错 密 码 n 次后 锁 定 这 个 账户” 的功 能 ,日方客户想这样实现。
我先看文档,没找到相关内容。转变思路,在 IM 画面中查找,找到了很类似的功能,具体 情况是: IM 画面中提供的是 这样 的功能:用 户输错 密 码 的次数,会被自 动递 增 记录 到 帐户 管理 页 面(注意:假 设该 用 户 后来又正确登 录 了一下, 则 前面的 错误 登 录 次数会被清零),管理 员 可以据此决定是否 锁 定 该 用 户 的 帐户 。
只要管理 员锁 定了某 帐户 , 该 用 户 即使以正确的用 户 名 / 密 码尝试 登 陆 ,也无法 进 入, 页 面提示: 该帐户 已被 锁 定,需要 联 系管理 员进 行解 锁 。
但是,这个调查结果并不完全符合用户的需求,客户想要实现的是设置个数字,当最终用户输入密码错误次数达到这个数字时,就自动锁定该帐户,而不是让客户这个管理员自己去一个个地看,不是人为去锁定(解锁要求是人为解锁)。
没办法,还要继续。
今天上午一过来,尝试从新的思路入手。
调查用户登录时的处理逻辑,最终在开发环境的实例项目的 web.xml 文件中找到我想要的配置项:
<servlet-mapping> <servlet-name>UserCertificationServlet</servlet-name> <url-pattern>/user.login</url-pattern> </servlet-mapping>
根据这个找到对应的 Servlet :
<servlet> <servlet-name>UserCertificationServlet</servlet-name> <servlet-class>jp.co.intra_mart.foundation.security.servlet.UserCertificationServlet</servlet-class> </servlet>
这个 servlet 是位于 IM 的 jar 包中的,反编译,看源代码。
想到新的思路,修改此处配置:
<servlet> <servlet-name>UserCertificationServlet</servlet-name> <servlet-class>employee.password.UserCertificationServlet</servlet-class> </servlet>
让用户登录时的处理逻辑不再指向原先 jar 包中的 servlet ,而是指向我的 servlet 。
我将原先的 UserCertificationServlet 拷贝过来,设置断点,不断地跑页面登录,将这段逻辑大概搞清楚后,然后加上自己的处理逻辑,主要就是用户登录错误此时达到或者超过设置的数字时,则将该用户锁定,需要管理员解锁。
经过 N 次尝试,经过 N-1 次失败,最终将其搞定了。
我追加的主要一段逻辑代码如下:
if ( "user" .equals(loginInfo.getLoginType())) {
AccountManager accountManager = new AccountManager(loginInfo.getLoginGroup());
Account account = accountManager.getAccount(loginInfo.getUser());
String strLockCount = PropertiesUtil.getMessage ( "lockCount" );
int intLockCount = 0;
if (strLockCount != null && ! "" .equals(strLockCount.trim())) {
intLockCount = Integer.parseInt (strLockCount);
}
// このユーザのログイン失敗回数 >=ロック設定回数
if (account != null && account.getLoginFailureCount() >= intLockCount) {
// 日付を設定した場合、アカウントはロックされる。
account.setLockDate(new Date());
// アカウントを更新します。
accountManager.updateAccount(account);
// ロックフラッグ
loginCertification = -3;
}
}
分析: debug 发现,源代码中, loginCertification 为 3时表示登录成功,为 0时表示登录失败,为 -3时表示帐户已被锁定。
引入的两个包主要是:
import jp.co.intra_mart.foundation.security.account.Account;
import jp.co.intra_mart.foundation.security.account.AccountManager;
其中,关键的两点就是:
account.setLockDate(new Date()); 和 accountManager.updateAccount(account);
前者主要是利用现在的时间作为参数传进去将帐户 account 锁定(意思就是立即锁定该帐户);
后者主要是将对该帐户的锁定信息更新一下(特别注意后面这一点,只有 update 后才会在 group 管理界面上真正自动将该用户锁定)。
我将以前写过的配置文件处理类拷贝过来,这样,最终实现了客户的需求:在 properties属性文件中进行设置,当最终用户登录次数达到或者超过这个数字时,就会自动将这个帐户锁定,需要管理员进行解锁操作。
但是,我自己也小结了下: 可能也可以使用 Listeren 来实现,但是昨天下午也大概看过这方面的 API ,没看到什么对应的方法,也没什么思路。
感觉现在的这种实现还是有点隐患的: 因 为 原先用 户 登 录 ( 还 有 group 登 录 )都是交 给 jp.co.intra_mart.foundation.security.servlet.UserCertificationServlet 处 理 的, 这 次 为 了 实现对 用 户帐户进 行 锁 定, 我 人 为 修改了 web.xml 中配置 项 UserCertificationServlet 的指向 Servlet 文件。
尽管个人已 经进 行了多次 测试 ,但 还 是有点担心 。
20090123追加:
部门首席技术专家改进本例,使用了Filter,学习一下。
总体概述:
web.xml中的对应配置:
<!-- アカウントロック --> <filter> <filter-name>LoginFailureCountCheckFilter</filter-name> <filter-class>**.**.LoginFailureCountCheckFilter</filter-class> <init-param> <description>The login type of the user that to check.</description> <param-name>loginType</param-name> <param-value>user</param-value> </init-param> <init-param> <description>The maximum allowable login failure count.</description> <param-name>maxFailureCount</param-name> <param-value>2</param-value> </init-param> </filter> <!-- アカウントロック --> <filter-mapping> <filter-name>LoginFailureCountCheckFilter</filter-name> <servlet-name>UserCertificationServlet</servlet-name> </filter-mapping> <servlet> <servlet-name>UserCertificationServlet</servlet-name> <servlet-class>jp.co.intra_mart.foundation.security.servlet.UserCertificationServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>UserCertificationServlet</servlet-name> <url-pattern>/user.login</url-pattern> </servlet-mapping>
Filter处理类LoginFailureCountCheckFilter.java
/**
*
*/
package ***.***.web.filter;
import java.io.IOException;
import java.util.Date;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import jp.co.intra_mart.common.platform.log.Logger;
import jp.co.intra_mart.foundation.security.account.Account;
import jp.co.intra_mart.foundation.security.account.AccountManager;
import jp.co.intra_mart.foundation.security.certification.LoginRequestInfo;
import jp.co.intra_mart.foundation.security.exception.AccessSecurityException;
import jp.co.intra_mart.system.security.LoginInfoSerializer;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
/**
* アカウントロック。
*
* @author **, **
*/
public class LoginFailureCountCheckFilter implements Filter {
private static final Logger LOG = Logger.getLogger(LoginFailureCountCheckFilter.class);
private static final boolean DEBUG = LOG.isDebugEnabled();
/**
* The login type of the user that to check.
*/
private String loginType;
/**
* The maximum allowable login failure count.
*/
private int maxFailureCount;
/**
* {@inheritDoc}
*/
public void init(FilterConfig config) throws ServletException {
this.maxFailureCount = NumberUtils.toInt(config.getInitParameter("maxFailureCount"));
this.loginType = StringUtils.trim(config.getInitParameter("loginType"));
}
/**
* {@inheritDoc}
*/
public void destroy() {
}
/**
* {@inheritDoc}
*/
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException,
IOException {
// Execute the servlet "jp.co.intra_mart.foundation.security.servlet.UserCertificationServlet" first,
// as it increase the failure count while login failure.
chain.doFilter(request, response);
// Check failure count.
doCheck(request, response);
}
/**
* Do check failure count.
*
* @param request
* the servlet request
* @param response
* the servlet response
*/
private void doCheck(ServletRequest request, ServletResponse response) {
String imLoginInfo = request.getParameter("im_login_info");
String username = request.getParameter("im_user");
LoginRequestInfo loginInfo = LoginInfoSerializer.load(imLoginInfo);
try {
checkFailureCount(loginInfo, username);
} catch (AccessSecurityException e) {
LOG.error(e.getMessage(), e);
}
}
/**
* Check the failure count of <code>loginInfo</code>, if it max than
* {@link #maxFailureCount}, lock the user.
*
* @param loginInfo
* the login info
* @param username
* the username
* @throws AccessSecurityException
* if access the account manager failed
*/
private void checkFailureCount(LoginRequestInfo loginInfo, String username) throws AccessSecurityException {
if (StringUtils.equals(this.loginType, loginInfo.getLoginType())) {
AccountManager accountManager = new AccountManager(loginInfo.getLoginGroup());
Account account = accountManager.getAccount(username);
if (DEBUG) {
LOG.debug("The login failure count of user {} is {}.", account.getUserId(), account
.getLoginFailureCount());
}
// このユーザのログイン失敗回数>=ロック設定回数
if (account != null && !account.isLock() && account.getLoginFailureCount() > this.maxFailureCount) {
if (DEBUG) {
LOG.debug("Locking user {}as the login failure count {} is greater than the max failure count {}.",
account.getUserId(), account.getLoginFailureCount(), this.maxFailureCount);
}
// 日付を設定した場合、アカウントはロックされる。
account.setLockDate(new Date());
// アカウントを更新します。
accountManager.updateAccount(account);
}
}
}
}
写了一个过滤器来达到指定类型的用户在登录失败次数到达指定数值时,锁定该用户。
/user.login 这个页面是由Servlet:UserCertificationServlet? (jp.co.intra_mart.foundation.security.servlet.UserCertificationServlet? ) 处理的,给它加一个Filter,使得在servlet执行完成后追加执行失败次数检查,一旦满足条件(用户类型满足设定的类型,该用户当前尚未被锁定 且,该用户的登录失败次数超过指定的最大次数)就将该用户的锁定日期设置为当前日期。用户即被锁定,该用户再尝试登录将得到错误提示: