限制接口的访问次数

情景:发送手机验证码或者邮箱验证码时限制规则:一分钟只可以发一次,一天内也有次数限制。以防止恶意访问,降低服务器压力。

解决思路:获取用户ip地址,判断此ip是否首次访问,如果是首次访问,在redis创建minKey,dayKey.并设置minKey过期60s,dayKey为86400s,也就是24H。首次访问则次数加一。超过1次或一天超过限制次数时,禁止访问。

1.获取用户真实ip地址。

/**
 * 自定义访问对象工具类
 * 获取对象的IP地址等信息 
 */
public class GetIpAddressUtil {
    /**
     * 获取用户真实IP地址,不使用request.getRemoteAddr();的原因是有可能用户使用了代理软件方式避免真实IP地址,
     * 
     * 可是,如果通过了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP值,究竟哪个才是真正的用户端的真实IP呢?
     * 答案是取X-Forwarded-For中第一个非unknown的有效IP字符串。
     * 
     * 如:X-Forwarded-For:192.168.1.110, 192.168.1.120, 192.168.1.130,
     * 192.168.1.100
     * 用户真实IP为: 192.168.1.110
     * @param request
     * @return
     */
    public static String getIpInfo(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("Proxy-Client-IP");
            }
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("WL-Proxy-Client-IP");
            }
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("HTTP_CLIENT_IP");
            }
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("HTTP_X_FORWARDED_FOR");
            }
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getRemoteAddr();
            }
        } else if (ip.length() > 15) {
            String[] ips = ip.split(",");
            for (int index = 0; index < ips.length; index++) {
                String strIp = (String) ips[index];
                if (!("unknown".equalsIgnoreCase(strIp))) {
                    ip = strIp;
                    break;
                }
            }
        }
        return ip;
    }
}

2.Redis配置

public class MyJedisPool {
    private final static Logger logger = LoggerFactory.getLogger(MyJedisPool.class);
    private static JedisPool jedisPool = null;
    //redis服务配置
      public static final String Redis_IP = "192.168.xx.xx";
      public static final int Redis_PORT =6379;
      public static final String Redis_jedisUser ="";//密码
      public static final int ExpireSeconds = 3600;
    //静态代码初始化池配置
    static {
        try{
            //创建jedis池配置实例
            JedisPoolConfig config = new JedisPoolConfig();
            //设置池配置项值
            config.setMaxTotal(20);
            config.setMaxIdle(10);
            config.setMaxWaitMillis(3000);
            config.setTestOnBorrow(true);
            config.setTestOnReturn(false);
            //根据配置实例化jedis池
            //jedisPool = new JedisPool(config,Redis_IP,Redis_PORT,1000*2,Redis_jedisUser);
            jedisPool = new JedisPool(config,SystemFileConfig.get("Redis_IP"),
                    Integer.parseInt(SystemFileConfig.get("Redis_PORT")),
                    1000*2,
                    SystemFileConfig.get("Redis_jedisUser"),
                    Integer.parseInt(SystemFileConfig.get("Redis_database")));     
        }catch (Exception e) {
            logger.info("redis连接池异常",e);
        }
    } 
    /**获得jedis对象*/
    public static Jedis getJedisObject(){
        return jedisPool.getResource();
    }
    
    /**归还jedis对象*/
    public static void returnJedisOjbect(Jedis jedis){
        if (jedis != null) {
            jedis.close();
        }
    }
}

3.操作redis完成次数限制

String userIpAddr = GetIpAddressUtil.getIpInfo(request);
Jedis jedis = MyJedisPool.getJedisObject();//获取jedis链接对象
boolean flag = true;               
flag = limitSendCount(userIpAddr,jedis);
System.out.println(flag); //根据flag判断是否超出限制
if (jedis != null) {
    jedis.close();  //用完关闭jedis资源
}                        
//判断是否超出限制
 public boolean limitSendCount(String ip, Jedis jedis) {     
            String value = jedis.get(ip+"MinLimte");
            String dayValue = jedis.get(ip+"DayLimte");
            if(value==null){
                jedis.set(ip+"MinLimte", "1");
                jedis.expire(ip+"MinLimte", 60);//设置过期时间60秒
                if(dayValue==null) {
                    jedis.set(ip+"DayLimte", "1");
                    jedis.expire(ip+"DayLimte", 86400);//设置过期时间24hours
                }else {
                    jedis.incr(ip+"DayLimte");  //加一次
                    int parseIntDay = Integer.parseInt(dayValue);
                    if(parseIntDay>20){
                        System.out.println("访问受限!!!!");
                        return false;
                    }
                }
                return true;
            }else{
                int parseInt = Integer.parseInt(value);
                int parseIntDay = Integer.parseInt(dayValue);
                60秒内访问超过1次,或者一天超过20次,就禁止访问
                if(parseInt>=1 || parseIntDay>20){
                    System.out.println("访问受限!!!!");
                    return false;
                }
                jedis.incr(ip+"MinLimte");
                jedis.incr(ip+"DayLimte");
            }            
            return true;
        } 

4.以上后端验证次数已完成。如果前后不分离,前端需要在点击按钮后禁止点击,按钮显示倒计时直至为0s方可再次点击时,前端js如下:

//发送成功后60秒内不可再次发送
var id = 60;
//定时执行
var timeing = setInterval(function() {
    id = id - 1;
    $('#sendVcode').html(id + 's');
    }, 1000);
//延迟执行
window.setTimeout(function() { //结束定时,恢复按钮可用 clearInterval(timeing); $('#sendVcode').html('点击发送Send').removeAttr("disabled"); $("#sendVcode").removeClass("sendclass2");//设置自己的按钮样式 $("#sendVcode").addClass("sendclass1"); }, 60000);

 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

todo 注解防刷

posted @ 2020-06-22 14:04  DaDa~  阅读(4238)  评论(0编辑  收藏  举报