公司要写一个新的项目,需要自己搭建架子,之前全是在别人搭建好的架子下做的开发,没接碰过关于接口安全和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;
}
}