javaweb项目实现连续3次输错密码后禁止登录

摘要:主要通过sql(oracle)实现连续X次输错密码后,禁止登录。Y小时或隔天后可以登录。

在javaweb项目的登录模块中经常会有连续X次输错密码后禁止登录的需求。这个功能可以通过多种方法来实现。本文只介绍以sql为主的方法,以供参考。

这是从实际项目中扒出来的代码,对一些变量名进行了处理,但是文中将包含全部核心代码。使用框架为struts2,ibatis。

需求:连续输入错误密码5次后,账号进入锁定状态,不可登录。锁定状态将于1小时后,或者下一个自然日(0点)解除。

具体方法描述。

1.建一张表来记录用户id,错误登录次数,上次登录时间。(也可以在user表上添加这三个字段,这样会省去一个初始化的麻烦)
2.登录失败后,进行校验:若已经错误登录5次,返回特定编码以告知前台显示错误信息。若次数不足5次,错误登录次数+1,并返回正常错误信息。若距离上一次错误登录时间已经过了1小时或者到了第二天,错误次数置为1。
3.若密码正确,并可以正常登录,将错误次数置为0。若处于锁定中,返回指定错误信息。

代码

建表语句

create table log_pwd_err(login_id number primary key, err_num number default 0, last_date date default sysdate);

java代码

    //参数的设置
    private static int maxTime = 5; //连续5次后
    private static double hour = 1; //一小时内不可登录。

    /**
     * 登录错误5次,一小时后不可登录
     * @param loginId 登录id
     * @param password 密码
     * @param isJiaMi 对密码进行加密处理
     * @return
     * @throws DataAccessException
     */
    public Map<String, Object> validateByLoginIdLoginErr(String loginId, String password,
            boolean isJiaMi) throws DataAccessException {
        Map<String, Object> resultMap = new HashMap(2);

        User user = getByLoginId(loginId);    //一个通过登录id获取用户实体类的方法,文中无源码
        String encodePass = "";
        if (isJiaMi && null != isJiaMi) {
            encodePass = encrypt.encode(password);  //对密码进行加密处理,文中无源码
        }

        if(null != user){    //如果登录id确实存在
            if(user.getPassword().equals(encodePass)){ //并且密码输入正确
                if(this.checkWhenPwdOk(user)){    //校验该用户是否可以登录
                    this.setErrZero(user);    //如果可以确实的登录,则对表中错误登录次数置零
                    resultMap.put("user", user);
                    return resultMap;
                }else {
                    resultMap.put("extMsg", "errMax");    //前台错误信息展现由上层代码实现。
                    return resultMap;
                }   
            }else {
                if(this.checkErrNum(user)){  //密码错误时的一系列操作
                    //返回true时,错误次数超过了5次,需要返回特定的错误信息
                    resultMap.put("extMsg", "errMax");
                    return resultMap;
                }else {
                    //错误次数没有超过5次,返回一个空map,交由上层处理
                    return resultMap;
                }
            }
        }
        return resultMap;
    }

    private void setErrZero(User user){
        //将指定id的错误次数置零
        //client是操作数据库的方法,可以认为是SqlMapClientBuilder.buildSqlMapClient(Resources.getResourceAsReader("config/SqlMap.xml"));  这种东西。
        this.client.update("login.err.updateForOk", user);
    }

    /**
     * 即使密码正确,也不能登录
     * @param user
     * @return false 处于锁定中,无法登陆  true 可以正常登录
     */
    private boolean checkWhenPwdOk(User user){
        Map<String, String> daoMap = new HashMap();
        daoMap.put("loginId", user.getLoginId() + "");
        daoMap.put("maxTime", maxTime + "");
        daoMap.put("hour", hour + "");
        String currentNumStr = (String) this.client.queryForObject("login.err.checkWhenPwdOk", daoMap);
        if(currentNumStr == null || currentNumStr.length() == 0){
            return true;    //err表中无数据,可以登录成功
        }
        if(Integer.valueOf(currentNumStr) >= maxTime){    //超过5次
            return false;
        }else {
            return true;
        }
    }

    //密码错误时的方法
    private boolean checkErrNum(User user){
        Map<String, String> daoMap = new HashMap();
        daoMap.put("loginId", user.getLoginId() + "");
        daoMap.put("maxTime", maxTime + "");
        daoMap.put("hour", hour + "");
        String currentNumStr = (String) this.client.queryForObject("login.err.getCurrentNum", daoMap);

        if(currentNumStr == null || currentNumStr.length() == 0){
            //错误信息表与用户表并不是同步的,如果是新建的用户,err表中将没有对应数据,需要插入一条新数据
            this.client.insert("login.err.insertUser", daoMap);
            currentNumStr = "0";
        }

        int currentNum = Integer.valueOf(currentNumStr);
        if(currentNum >= maxTime){
            //若次数超过了,将不会修改登陆时间。那样做会导致一小时的校验错误
            return true;
        }
        //执行+1或=1的操作
        this.client.update("login.err.updateForErr", daoMap);

        return false;
    }

sql文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN" "http://www.ibatis.com/dtd/sql-map-2.dtd">

<sqlMap namespace="login.err">

    <select id="getCurrentNum" resultClass="java.lang.String">
select decode(trunc(t1.last_date), trunc(sysdate), 
              case when (sysdate - t1.last_date) * 24 > to_number(#hour#) and t1.err_num >= to_number(#maxTime#)
                     then 1 <!-- 如果达到5次并且又过了1小时,原则上应该返回1 -->
              else t1.err_num end, 
              1) "flag" <!-- 过了一天还错也返回1 -->
  from log_pwd_err t1
 where t1.login_id = to_number(#loginId#)

    </select>

    <insert id="insertUser">
        insert into log_pwd_err(login_id, err_num, last_date) values (#userId#, 1, sysdate)
    </insert>

    <update id="updateForOk">
        update log_pwd_err t1
           set t1.err_num = 0,
               t1.last_date = sysdate
         where t1.login_id = to_number(#loginId#)
           and t1.err_num != 0
    </update>

    <update id="updateForErr">
        update log_pwd_err t1
           set t1.err_num = decode(trunc(t1.last_date), trunc(sysdate), 
                              case when (sysdate - t1.last_date) * 24 > to_number(#hour#) 
                                            and t1.err_num >= to_number(#maxTime#)
                                     then 1
                              else t1.err_num + 1 end, 
                              1),
               t1.last_date = sysdate
         where t1.login_id = to_number(#loginId#)
    </update>

    <select id="checkWhenPwdOk" resultClass="java.lang.String">
        select case
                 when trunc(t1.last_date) != trunc(sysdate) then
                  0
                 when to_number(#hour#) / 24 > sysdate - t1.last_date and t1.err_num >= to_number(#maxTime#) then
                  t1.err_num
                 else
                  0
               end "flag"
          from log_pwd_err t1
         where t1.login_id = to_number(#loginId#)
    </select>

</sqlMap>

总结:使用sql去完成这种需求最大的好处是逻辑清晰,集中,安全,并且不会出现bug。整个功能连写带测试加部署只花了2小时,比写这篇文章都短……缺点就是会对数据库造成一点点额外的压力。

猜你喜欢

转载自blog.csdn.net/nayi_224/article/details/80221888