博客被惡意刷留言了,致使短期內沒法正常訪問,並讓我博客的留言板看起來比較糟,本覺得攻擊者只是一時興起,看我有開放接口,就攻擊測試一下,可次日博客又受到了攻擊,第三天仍是如此,看樣子是被盯上了,更可惡的是還利用留言回覆自動發郵件功能,留下一個公司的郵箱地址,致使經過個人博客做死的給那個公司郵箱回覆郵件(好像是狂神公司的郵箱地址,實在是抱歉)。哎,林子大了什麼鳥都有,也見慣不怪,畢竟能作這種事的人心理是有點問題的,估計是小時候有過什麼陰影,致使人格扭曲了那麼一點,說實話,挺可憐這種人的,悲哀!java
博客中留言功能是沒有作限制的,初衷就是讓全部人都可以在這個平臺暢所欲言,不作限制,可這也給了一些人可乘之機,例如上面那位惡意刷留言的,那咱們要如何作防禦呢,既要作到不受限制的留言,又不能讓人惡意刷留言,沒錯,就是攔截器了,我們能夠指定某個接口在指定的時間內訪問的次數受到限制。以一個Springboot工程爲例,我們來說述一下如何攔截。redis
使用idea搭建Springboot工程spring
yml配置文件:apache
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依賴:json
<!-- 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>
複製代碼
咱們自定義一個註解,能夠將這個註解做用在要攔截接口的方法上,當在接口的方法上加了這個註解後,就能夠按照指定的規則進行攔截,以下:markdown
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface AccessLimit {
int seconds();
int maxCount();
}
複製代碼
這裏註解類上的三個註解稱爲元註解,其分別表明的含義以下:session
在自定義註解類中,定義了兩個方法,seconds()、maxCount(),表示指定時間內請求的次數app
@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;
}
}
複製代碼
@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);
}
}
複製代碼
編寫controller進行測試ide
@RestController
public class TestController {
@GetMapping("test")
@AccessLimit(seconds = 15, maxCount = 3) //15秒內 容許請求3次
public String testAccessLimit() {
return "success";
}
}
複製代碼
爲了方便顯示,對返回結果進行處理:spring-boot
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,能夠看到正常請求和頻繁刷新請求的結果:
到這裏就差很少了,最後放一下整個工程的目錄結構:
這裏仍是得感謝攻擊我博客的那我的,讓我對攔截功能更熟悉了
打不倒你的,只會讓你更強大!