前言:
博客被恶意刷留言了,导致短时间内无法正常访问,并让我博客的留言板看起来比较糟,本以为攻击者只是一时兴起,看我有开放接口,就攻击测试一下,可第二天博客又受到了攻击,第三天还是如此,看样子是被盯上了,更可恶的是还利用留言回复自动发邮件功能,留下一个公司的邮箱地址,导致通过我的博客作死的给那个公司邮箱回复邮件(好像是狂神公司的邮箱地址,实在是抱歉)。哎,林子大了什么鸟都有,也见惯不怪,毕竟能做这种事的人心理是有点问题的,估计是小时候有过什么阴影,导致人格扭曲了那么一点,说实话,挺可怜这种人的,悲哀!
博客中留言功能是没有做限制的,初衷就是让所有人都能够在这个平台畅所欲言,不做限制,可这也给了一些人可乘之机,例如上面那位恶意刷留言的,那我们要如何做防护呢,既要做到不受限制的留言,又不能让人恶意刷留言,没错,就是拦截器了,咱们可以指定某个接口在指定的时间内访问的次数受到限制。以一个Springboot工程为例,咱们来讲述一下如何拦截。
【0】创建Springboot工程
使用idea搭建Springboot工程
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");
Response result = new Response<>();
result.setCode("9999");
result.setMsg("操作过于频繁");
Object obj = JSONObject.toJSON(result);
response.getWriter().write(JSONObject.toJSONString(obj));
return false;
}
}
return true;
}
}
【3】将拦截器注册到容器中
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private SessionInterceptor sessionInterceptor;
@Bean
public SessionInterceptor tokenVerifyInterceptor() {
return new SessionInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(sessionInterceptor);
registry.addInterceptor(tokenVerifyInterceptor()).addPathPatterns("/**");
WebMvcConfigurer.super.addInterceptors(registry);
}
}
【4】测试
编写controller进行测试
@RestController
public class TestController {
@GetMapping("test")
@AccessLimit(seconds = 15, maxCount = 3) //15秒内 允许请求3次
public String testAccessLimit() {
return "success";
}
}
为了方便显示,对返回结果进行处理:
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;
}
}
访问:http://localhost:8080/test,可以看到正常请求和频繁刷新请求的结果:
到这里就差不多了,最后放一下整个工程的目录结构:
这里还是得感谢攻击我博客的那个人,让我对拦截功能更熟悉了
打不倒你的,只会让你更强大!
评论