springboot接口服务,防刷、防止请求攻击,aop实现
本文使用aop的方式防止spring boot的接口服务被网络攻击
pom.xml 中加入 aop 依赖
<dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-aop</artifactid> </dependency>
aop自定义注解类
package org.jeecg.common.aspect.annotation; import java.lang.annotation.*; /** * 用于防刷限流的注解 * 默认是5秒内只能调用一次 */ @target({ elementtype.method }) @retention(retentionpolicy.runtime) @documented public @interface ratelimit { /** 限流的key */ string key() default "limit:"; /** 周期,单位是秒 */ int cycle() default 5; /** 请求次数 */ int count() default 1; /** 默认提示信息 */ string msg() default "请勿重复点击"; }
aop切面业务类
package org.jeecg.common.aspect; import org.aspectj.lang.proceedingjoinpoint; import org.aspectj.lang.annotation.around; import org.aspectj.lang.annotation.aspect; import org.aspectj.lang.annotation.pointcut; import org.aspectj.lang.reflect.methodsignature; import org.jeecg.common.aspect.annotation.ratelimit; import org.springframework.aop.aspectj.methodinvocationproceedingjoinpoint; import org.springframework.data.redis.core.redistemplate; import org.springframework.stereotype.component; import org.springframework.web.context.request.requestcontextholder; import org.springframework.web.context.request.servletrequestattributes; import javax.annotation.resource; import javax.servlet.http.httpservletrequest; import java.lang.reflect.method; import java.util.concurrent.timeunit; /** * 切面类:实现限流校验 */ @aspect @component public class accesslimitaspect { @resource private redistemplate<string, integer> redistemplate; /** * 这里我们使用注解的形式 * 当然,我们也可以通过切点表达式直接指定需要拦截的package,需要拦截的class 以及 method */ @pointcut("@annotation(org.jeecg.common.aspect.annotation.ratelimit)") public void limitpointcut() { } /** * 环绕通知 */ @around("limitpointcut()") public object around(proceedingjoinpoint pjp) throws throwable { // 获取被注解的方法 methodinvocationproceedingjoinpoint mjp = (methodinvocationproceedingjoinpoint) pjp; methodsignature signature = (methodsignature) mjp.getsignature(); method method = signature.getmethod(); // 获取方法上的注解 ratelimit ratelimit = method.getannotation(ratelimit.class); if (ratelimit == null) { // 如果没有注解,则继续调用,不做任何处理 return pjp.proceed(); } /** * 代码走到这里,说明有 ratelimit 注解,那么就需要做限流校验了 * 1、这里可以使用redis的api做计数校验 * 2、这里也可以使用lua脚本做计数校验,都可以 */ //获取request对象 servletrequestattributes attributes = (servletrequestattributes) requestcontextholder.getrequestattributes(); httpservletrequest request = attributes.getrequest(); // 获取请求ip地址 string ip = getipaddr(request); // 请求url路径 string uri = request.getrequesturi(); //存到redis中的key string key = "ratelimit:" + ip + ":" + uri; // 缓存中存在key,在限定访问周期内已经调用过当前接口 if (redistemplate.haskey(key)) { // 访问次数自增1 redistemplate.opsforvalue().increment(key, 1); // 超出访问次数限制 if (redistemplate.opsforvalue().get(key) > ratelimit.count()) { throw new runtimeexception(ratelimit.msg()); } // 未超出访问次数限制,不进行任何操作,返回true } else { // 第一次设置数据,过期时间为注解确定的访问周期 redistemplate.opsforvalue().set(key, 1, ratelimit.cycle(), timeunit.seconds); } return pjp.proceed(); } //获取请求的归属ip地址 private string getipaddr(httpservletrequest request) { string ipaddress = null; try { ipaddress = request.getheader("x-forwarded-for"); if (ipaddress == null || ipaddress.length() == 0 || "unknown".equalsignorecase(ipaddress)) { ipaddress = request.getheader("proxy-client-ip"); } if (ipaddress == null || ipaddress.length() == 0 || "unknown".equalsignorecase(ipaddress)) { ipaddress = request.getheader("wl-proxy-client-ip"); } if (ipaddress == null || ipaddress.length() == 0 || "unknown".equalsignorecase(ipaddress)) { ipaddress = request.getremoteaddr(); } // 对于通过多个代理的情况,第一个ip为客户端真实ip,多个ip按照','分割 if (ipaddress != null && ipaddress.length() > 15) { // = 15 if (ipaddress.indexof(",") > 0) { ipaddress = ipaddress.substring(0, ipaddress.indexof(",")); } } } catch (exception e) { ipaddress = ""; } return ipaddress; } }
测试
package org.jeecg.modules.api.controller; import org.jeecg.common.api.vo.result; import org.jeecg.common.aspect.annotation.ratelimit; import org.springframework.web.bind.annotation.getmapping; import org.springframework.web.bind.annotation.requestmapping; import org.springframework.web.bind.annotation.restcontroller; /** * 测试接口 * @author wujiangbo * @date 2022-08-23 18:50 */ @restcontroller @requestmapping("/test") public class testcontroller { //4秒内只能访问2次 @ratelimit(key= "testlimit", count = 2, cycle = 4, msg = "大哥、慢点刷请求!") @getmapping("/test001") public result<?> rate() { system.out.println("请求成功"); return result.ok("请求成功!"); } }
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论