一、场景
为了系统安全,有时候需要做一些限制用户的操作。比如用户恶意刷接口,或者系统卡顿时候频繁点击,轻则造成系统崩溃,重则可能造成数据丢失。不得不引起重视。
解决方案:使用redis和拦截器来记录并控制用户行为
二、拦截器
ResponseParams为本地的一个异常处理对象,自己可以使用自己的对象
java
package simple.cloud.config.myInterceptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import net.sf.json.JSONArray;
import simple.cloud.components.common.params.ResponseParams;
import simple.cloud.components.common.utils.MyUtils;
@Component
@CrossOrigin("*")
public class MyInterceptor implements HandlerInterceptor {
@Resource
private RedisTemplate<String, Object> redisTemplate;
/**
* 1、过滤封禁IP
* 2、过滤恶意IP
*/
@Override
@CrossOrigin("*")
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if (handler.getClass().isAssignableFrom(HandlerMethod.class)) { // 判断此class对象所表示的类或接口与指定的class参数所表示的类或接口是否相同
//1、校验IP合法
String ip = MyUtils.getIpAddress(request);
String limitKey = request.getServletPath() + MyUtils.getIpAddress(request);
Object blackIp = redisTemplate.opsForValue().get(ip);
if (MyUtils.isNotNull(blackIp)) {
respouseOut(response, ResponseParams.error("禁止使用!"));
return false;
}
//2、校验重复点击
// HandlerMethod封装方法定义相关的信息,如类、方法、参数等
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
// 获取方法中是否包含注解
MyLimit methodAnnotation = method.getAnnotation(MyLimit .class);
// 获取类中是否包含注解
MyLimit classAnnotation = method.getDeclaringClass().getAnnotation(MyLimit .class);
// 如果方法上有注解 就优先选择方法上的参数
MyLimit myLimit = methodAnnotation != null ? methodAnnotation : classAnnotation;
if (requestLimit != null) {
if (checkLimit(limitKey, myLimit )) {
respouseOut(response, ResponseParams.error("您点击的太频繁啦!"));
//将被封禁的IP加入缓存,下次访问时不必校验其点击情况
redisTemplate.opsForValue().set(ip , 1, 30, TimeUnit.SECONDS);
return false;
}
}
}
return true;
}
/**
* 回写给客户端
*/
private void respouseOut(HttpServletResponse response, ResponseParams resp) throws IOException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json;charaset=utf-8");
PrintWriter out = null;
String json = com.alibaba.fastjson.JSONObject.toJSON(resp).toString();
out = response.getWriter();
out.append(json);
}
/**
* 判断请求是否受限
*/
private boolean checkLimit(String limitKey, RequestLimit requestLimit) {
//使用IP+请求地址作为key
//从缓存中获取,当前这个请求访问了几次
Integer redisCount = (Integer) redisTemplate.opsForValue().get(limitKey);
if (redisCount == null) {
//初始次数
redisTemplate.opsForValue().set(limitKey, 1, requestLimit.second(), TimeUnit.SECONDS);
} else {
//若在指定时间内超过了访问次数限制,则返回true
if (redisCount.intValue() >= requestLimit.maxCount()) {
//将其设置超过限制次数,并延迟30秒,30秒之后可以重新访问
redisTemplate.opsForValue().set(limitKey, requestLimit.maxCount()+1, 30, TimeUnit.SECONDS);
return true;
}
//次数自增
redisTemplate.opsForValue().increment(limitKey, 1);
}
return false;
}
}
MyLimit为一个注解,可以添加在指定controller上,来精确限制用户行为
java
@Documented
@Inherited
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLimit {
int second() default 1;
int maxCount() default 15;
}
三、添加拦截器
值得注意的是需要添加crosFiter方法,来解决拦截器跨越问题
java
@Resource
private MyInterceptor myInterceptor;
/**
* 让cors高于拦截器的权限
* 解决拦截器CROS问题
* @return
*/
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("*");
config.setAllowCredentials(true);
config.addAllowedMethod("*");
config.addAllowedHeader("*");
UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
configSource.registerCorsConfiguration("/**", config);
return new CorsFilter(configSource);
}
/**
* 添加我的默认的拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myInterceptor).addPathPatterns("/**");
}
四、结束
需要完整代码的可以留言讨论