限制ip访问次数

公司要写一个新的项目,需要自己搭建架子,之前全是在别人搭建好的架子下做的开发,没接碰过关于接口安全和token认证还有权限的问题,所以现在需要考虑到这些问题。
当时想采用SpringBoot+SpringSecurity+JWT做token和权限的,但是想了想,还是觉得用自己的办法写写试试再说。
首先我加入了超时时间+限制访问次数,超过访问次数禁用此ip。禁用ip我用的是redis中的hash来存的(也可以用全局的ConcurrentHashMap),ip作为key,禁用的时间为value,通过ip获取禁用时间判断是否禁用。需要注意的是,如果你的项目是接口式开发就不需要考虑放行静态文件,因为像js、css、img等这些是忽略访问次数的。

getIpAddress() 是获取ip的方法。

package hsyt.utils;

import org.apache.commons.collections.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Description: Ip相关工具类
 * @Title: IpUtils
 * @Package hsyt.utils
 * @Author: **
 * @Copyright 版权归**所有
 * @CreateTime: 2021/4/20 15:21
 */
@Component
public class IpUtils {

    private static Logger LOGGER = LoggerFactory.getLogger(IpUtils.class);

    /**
     * redis操作
     */
    private static RedisTemplate redisTemplate;

    /**
     * @Description: set方法注入静态属性
     * @param redisTemplate
     * @return void
     * @Author zangdy0289@hsyuntai.com
     * @CreateTime 2021/4/20 17:22
     */
    @Autowired
    public void setRedisTemplate(RedisTemplate redisTemplate) {
        IpUtils.redisTemplate = redisTemplate;
    }


    /**
     * @param ip         需要校验的ip
     * @param insideTime 在多长时间内计算次数  毫秒级别
     * @param count      当前ip访问超过该次数时被限制
     * @param lockTime   限制ip的时长  毫秒级别
     * @return boolean
     * @Description: 限制一段时间内ip访问次数
     * @Author **
     * @CreateTime 2021/4/20 16:40
     */
   public static boolean restrictIpTest(String ip, Integer insideTime, Integer count, Long lockTime) {
        // 查看ip是否被封(被封ip存到redis中)
        BoundHashOperations<String, String, Long> ipBlockedMap = redisTemplate.boundHashOps("ipBlockedMap");
        // ip访问次数集合
        BoundHashOperations<String, String, List<Long>> ipCountMap = redisTemplate.boundHashOps("ipCountMap");
        // 当前的毫秒值
        long cTMillis = System.currentTimeMillis();
        if (ipBlockedMap.hasKey(ip)) {
            // 解封的毫秒值
            Long timeLong = ipBlockedMap.get(ip);
            // 未解封
            if (cTMillis < timeLong) {
                return false;
            }
            // 解封后移除
            ipBlockedMap.delete(ip);
            ipCountMap.delete(ip);
            LOGGER.info(ip + "已解除被封状态,当前时间为:" + cTMillis);
        }
        // ip对应的请求时间戳集合
        List<Long> tmList = ipCountMap.get(ip);
        if (CollectionUtils.isNotEmpty(tmList)) {
            for (int i = 0; i < tmList.size(); i++) {
                Long nTMillis = tmList.get(i);
                // 不在限制时间内
                if (cTMillis > nTMillis + insideTime) {
                    tmList.remove(i);
                    i--;
                }
            }
            if (tmList.size() <= count) {
                tmList.add(cTMillis);
            } else {
                // 将ip封 lockTime 毫秒
                ipBlockedMap.put(ip, cTMillis + lockTime);
                LOGGER.info(ip + "已被封 " + lockTime + " 毫秒,当前时间为:" + cTMillis);
                return false;
            }
        } else {
            // 为空,创建
            if (tmList == null) {
                synchronized (TestUtil.class) {
                    // 需要重新再获取一遍,要不tmList可能已经取到null值了
                    tmList = ipCountMap.get(ip);
                    // 确实没取到值再 new
                    if (tmList == null) {
                        tmList = new ArrayList<>();
                        tmList.add(cTMillis);
                        ipCountMap.put(ip, tmList);
                    }
                }
            }
        }
        return true;
    }
    
    /**
     * 获取用户真实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 ip字符串
     */
    public static String getIpAddress(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        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();
        }
        return ip;
    }
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值