前言:
博客被恶意刷留言了,导致短时间内无法正常访问,并让我博客的留言板看起来比较糟,本以为攻击者只是一时兴起,看我有开放接口,就攻击测试一下,可第二天博客又受到了攻击,第三天还是如此,看样子是被盯上了哎,林子大了什么鸟都有,也见惯不怪,只好对评论功能做一些改进了
博客中留言功能是没有做限制的,初衷就是让所有人都能够在这个平台畅所欲言,不做限制,可这也给了一些人可乘之机,例如上面那位恶意刷留言的,那我们要如何做防护呢,既要做到不受限制的留言,又不能让人恶意刷留言,没错,就是拦截器了,咱们可以指定某个接口在指定的时间内访问的次数受到限制。
【0】配置yml文件、导入依赖
yml配置文件:
spring:
redis:
host: 127.0.0.1
port: 6379
database: 0
timeout: 1800000
lettuce:
pool:
max-active: 20
max-wait: -1
max-idle: 5
min-idle: 0
主要的pom依赖:
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- spring2.X集成redis所需common-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>
<!-- json解析-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.28</version>
</dependency>
【1】自定义一个注解
我们自定义一个注解,可以将这个注解作用在要拦截接口的方法上,当在接口的方法上加了这个注解后,就可以按照指定的规则进行拦截,如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface AccessLimit {
int seconds();
int maxCount();
}
这里注解类上的三个注解称为元注解,其分别代表的含义如下:
- @Documented:注解信息会被添加到Java文档中
- @Retention:注解的生命周期,表示注解会被保留到什么阶段,可以选择编译阶段、类加载阶段,或运行阶段
- @Target:注解作用的位置,ElementType.METHOD表示该注解仅能作用于方法上
在自定义注解类中,定义了两个方法,seconds()、maxCount(),表示指定时间内请求的次数
- seconds():表示指定的时间内
- maxCount():表示请求的次数
【2】自定义一个拦截器
- 通过自定义的注解传递指定时间和请求次数
- 使用redis记录请求次数
- 当访问次数达到指定上限的时候进行限制
@Component
public class SessionInterceptor implements HandlerInterceptor {
@Autowired
private RedisTemplate redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 判断请求是否属于方法的请求
if (handler instanceof HandlerMethod) {
HandlerMethod hm = (HandlerMethod) handler;
// 获取方法中的注解,看是否有该注解
AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);
if (null == accessLimit) {
return true;
}
// 指定时间内
int seconds = accessLimit.seconds();
// 允许请求次数
int maxCount = accessLimit.maxCount();
String ip=request.getRemoteAddr();
String key = request.getServletPath() + ":" + ip ;
// 从redis中获取用户访问的次数
Integer count = (Integer) redisTemplate.opsForValue().get(key);
System.out.println(count);
if (null == count || -1 == count) {
// 第一次访问
redisTemplate.opsForValue().set(key, 1,seconds, TimeUnit.SECONDS);
return true;
}
if (count < maxCount) {
// count加1
count = count+1;
redisTemplate.opsForValue().set(key, count,0);
return true;
}
// 超出访问次数
if (count >= maxCount) {
// response 返回 json 请求过于频繁请稍后再试
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
//可以自定义一个错误类用来传给前端,也可以直接传一句话hh
//Response result = new Response<>();
//result.setCode("9999");
//result.setMsg("操作过于频繁");
//Object obj = JSONObject.toJSON(result);
//response.getWriter().write(JSONObject.toJSONString(obj));
response.getWriter().write("Operations are too frequent,QwQ ~~~");
return false;
}
}
return true;
}
}
【3】将拦截器注册到容器中
【4】可以对返回结果进行处理,也可以不用
Serializable是一个对象序列化的接口,一个类只有实现了Serializable接口,它的对象才是可序列化的。因此如果要序列化某些类的对象,这些类就必须实现Serializable接口。而实际上,Serializable是一个空接口,没有什么具体内容,它的目的只是简单的标识一个类的对象可以被序列化。
public class Response<T> implements Serializable {
private static final long serialVersionUID = -4505655308965878999L;
//请求成功返回码为:0000
private static final String successCode = "0000";
//返回数据
private T data;
//返回码
private String code;
//返回描述
private String msg;
public Response(){
this.code = successCode;
this.msg = "请求成功";
}
public Response(String code,String msg){
this();
this.code = code;
this.msg = msg;
}
public Response(String code,String msg,T data){
this();
this.code = code;
this.msg = msg;
this.data = data;
}
public Response(T data){
this();
this.data = data;
}
public static long getSerialVersionUID() {
return serialVersionUID;
}
public static String getSuccessCode() {
return successCode;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
【5】在对应方法上添加注解
//新增评论
@PostMapping("/comments")
@AccessLimit(seconds = 1, maxCount = 30) //1秒内 允许请求30次
public String post(Comment comment, HttpSession session, Model model) {
}
发布评论过于频繁时会被限制
到这里就差不多了,这里还是得感谢攻击我博客的那个人,让我对拦截功能更熟悉了
打不倒你的,只会让你更强大!
Comments