在实际项目使用中,必须要考虑服务的安全性,当服务部署到互联网以后,就要考虑服务被恶意请求和暴力攻击的情况,通过intercept和redis针对url+ip在一定时间内访问的次数来将ip禁用,可以根据自己的需求进行相应的修改,来打打自己的目的;
代码展示
首先工程为springboot框架搭建,不再详细叙述。直接上核心代码。
首先创建一个自定义的拦截器类,也是最核心的代码;
1、引入依赖
<!--springboot redis依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
2、配置文件yml编写
spring:
redis:
database: 3 # Redis数据库索引(默认为0)
host: 192.168.152.173 # Redis服务器地址
port: 6379 # Redis服务器连接端口
password: # Redis服务器连接密码(默认为空)
timeout: 2000 # 连接超时时间(毫秒)
jedis:
pool:
max-active: 200 # 连接池最大连接数(使用负值表示没有限制)
max-idle: 20 # 连接池中的最大空闲连接
min-idle: 0 # 连接池中的最小空闲连接
max-wait: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制)
3、拦截器类:
import com.example.zsn.utils.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @Description:
* @Author: zhouzhou
* 2022/6/23 10:39
*/
@Slf4j
public class IpUrlLimitInterceptor implements HandlerInterceptor {
@Resource
private RedisUtil redisUtil;
private static final String LOCK_IP_URL_KEY="lock_ip_";
private static final String IP_URL_REQ_TIME="ip_url_times_";
private static final long LIMIT_TIMES=5;
private static final int IP_LOCK_TIME=60;
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
String remoteAddr = httpServletRequest.getRemoteAddr();
String requestURI = httpServletRequest.getRequestURI();
log.info("request请求地址uri={},ip={}", requestURI, remoteAddr);
if (ipIsLock(remoteAddr)){
log.info("ip访问被禁止={}",remoteAddr);
return false;
}
if(!addRequestTime(remoteAddr,requestURI)){
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
/**
* @Description: 判断ip是否被禁用
* @author: shuyu.wang
* @date: 2019-10-12 13:08
* @param ip
* @return java.lang.Boolean
*/
private Boolean ipIsLock(String ip){
if(redisUtil.hasKey(LOCK_IP_URL_KEY+ip)){
return true;
}
return false;
}
/**
* @Description: 记录请求次数
* @author: shuyu.wang
* @date: 2019-10-12 17:18
* @param ip
* @param uri
* @return java.lang.Boolean
*/
private Boolean addRequestTime(String ip,String uri){
String key=IP_URL_REQ_TIME+ip+uri;
if (redisUtil.hasKey(key)){
long time=redisUtil.incr(key,(long)1);
if (time>=LIMIT_TIMES){
redisUtil.getLock(LOCK_IP_URL_KEY+ip,ip,IP_LOCK_TIME);
return false;
}
}else {
redisUtil.getLock(key,(long)1,1);
}
return true;
}
}
代码中redis的使用的是分布式锁的形式,这样可以最大程度保证线程安全和功能的实现效果。代码中设置的是1S内同一个接口通过同一个ip访问5次,就将该ip禁用1个小时,根据自己项目需求可以自己适当修改,实现自己想要的功能;
拦截器配置类
import com.example.zsn.interceptor.IpUrlLimitInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @Description:
* @Author: zhouzhou
* 2022/6/23 10:43
*/
@Configuration
@Slf4j
public class InterceptorConfig implements WebMvcConfigurer {
@Bean
IpUrlLimitInterceptor getIpUrlLimitInterceptor(){
return new IpUrlLimitInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(getIpUrlLimitInterceptor()).addPathPatterns("/**");
}
}
redis 配置:
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* @Description:
* @Author: zhouzhou
* 2022/6/23 16:20
*/
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport{
/**
* RedisTemplate相关配置
* 使redis支持插入对象
*
* @param factory
* @return 方法缓存 Methods the cache
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 配置连接工厂
template.setConnectionFactory(factory);
// 设置key的序列化器
template.setKeySerializer(new StringRedisSerializer());
// 设置value的序列化器
//使用Jackson 2,将对象序列化为JSON
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//json转对象类,不设置默认的会将json转成hashmap
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setValueSerializer(jackson2JsonRedisSerializer);
return template;
}
}
redis 工具类
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Collections;
/**
* @Description:
* @Author: zhouzhou
* 2022/6/23 10:41
*/
@Slf4j
@Component
public class RedisUtil {
private static final Long SUCCESS = 1L;
@Resource
private RedisTemplate<String, Object> redisTemplate;
/**
* 获取锁
* @param lockKey
* @param value
* @param expireTime:单位-秒
* @return
*/
public boolean getLock(String lockKey, Object value, int expireTime) {
try {
log.info("添加分布式锁key={},expireTime={}",lockKey,expireTime);
String script = "if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then return redis.call('expire',KEYS[1],ARGV[2]) else return 0 end";
RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
Object result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), value, expireTime);
if (SUCCESS.equals(result)) {
return true;
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 释放锁
* @param lockKey
* @param value
* @return
*/
public boolean releaseLock(String lockKey, String value) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
Object result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), value);
if (SUCCESS.equals(result)) {
return true;
}
return false;
}
/**
* 是否存在key
*
* @param key
* @return
*/
public Boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}
/**
* 增加(自增长), 负数则为自减
*
* @param key
* @return
*/
public Long incr(String key, long increment) {
return redisTemplate.opsForValue().increment(key, increment);
}
}
自己随便写个测试类
浏览器多请求一下。
问题记录
1.java.lang.IllegalStateException
在返回值方面,会经常报java.lang.IllegalStateException
RedisScript<String> redisScript = new DefaultRedisScript<>(script, String.class);
用String类型时候,经常会报类型转换异常。我在代码中使用的Long类型接收该类型,在命令行中我们也看到命令行结果返回的是数字0或者1,保险起见我们也可以用Object对象来接收结果集。
2.ERR value is not an integer or out of range
分析,可能是值的类型不对或长度太长了?
1、修改value 的长度以后错误仍然存在。
2、在分析是不是返回值的类型不对,改为Integer后,又抛出另一个错误
就是redis 值序列化的问题,由于个人能力的问题,对部分源码有点未理解,所以没有做到全方位的解读这些异常。这个是莫名其妙解决了。
感谢博主,让我解决了第一个问题
https://blog.csdn.net/weixin_38405253/article/details/124854363