ip限流
1.限流的思路
使用redis 功能,了解Redis中的key(IP+URL),从而记录了某个IP访问的某个接口,value存的是访问的次数,加上一个过期时间就是在注解赋值的值。
- 限流的思路就是去监控这个ip,使用拦截器来进行拦截,
- 通过 ip的作为key,访问次数为value的方式对某一用户的某一请求进行唯一的标识。
- 每次访问的时候判断key 是否存在,是否count超过了限制的访问次数、
- 若超出访问的限制,则应response放回msg:请求过于频繁,给前端予以展示。
自定义注解
@interface 就是要做自定义注解了
@Retention(RetentionPolicy.RUNTIME) 运行时使用
@Target(可以查看是放在那个上面的,是方法上面还是类上面)
int seconds();
int maxCount();
boolean needLogin() default true;
2.拦截器
使用的是handlerInterceptor
HandlerInterceptor
-
preHandle 方法是处理拦截器拦截使用的,在controller处理之前就开始进行调用了,springmvc中夫人Interceptor是链式调用的,所有的prehandle 方法都会在controller方法调用之前调用。 prehandle 方法,在请求发生前执行的。
-
posthandle 返回true 调用的前提:prehandle 返回TRUE
调用前提:prehandle 返回TRUE
调用时间:controller方法处理完。
执行顺序:链式Intercepter情况下,按照声明的顺序倒着执行。
-
afterCompletion
调用的前提:prehandle返回TRUE
调用时间:DispatcherServlet进行视图渲染之后多用于清理资源。
首先是拦截器
小于最大的操作,是不是加1操作
首次进入
此时访问次数小于最大的次数
此时访问的次数大于最大的次数,
之后就开始写controller+6
使用自定义注解
@GetMapping
@AccessLimit(seconds = 3,maxCount = 10)
public String accessLimit( ){
拿到限流类的注解的参数,拿到方法的注解,就可以使用的最大的次数。
拦截路径:
全面的使用注解:InteceptorConfig WebMvcConfigurer 增加一个拦截器
配置Redis 6379
反向代理的情况下使用的request.getRemoteAddr( ),获取的Ip地址就是Nginx所在服务器的ip地址,而不知客户端的ip。
利用X-forwarded-for 伪造客户端
在web开发中,获取客户端ip地址,为了防止刷票,需要限制每个IP只能投票一次。
获取客户端IP
在Java中获取客户端最直接的方式使用request.getRemoteAddr( ) 这种
HTTP协议是基于TCP协议,由于request,getRemoteAddr( )获取到的就是TCP层直接连接的客户端的IP,对于web服务器来说直接连接的服务器实际上为Nginx,也就是tcp层拿不到真实客户端的IP
为了解决上面的问题,很多的HTTP代理会在HTTP协议头中添加X-forward-for,用来跟踪请求的来源。
X-Forwarded-For:client1,proxy1,proxy2
包含多个IP地址,每个值通过逗号+空格分开,最左边(client1)是最原始客户端的地址,中间如果有多层代理,每一层代理都会将连接他的客户端IP,追加在X-Forwarded-For右边
获取真实的IP地址的话,首先从HTTP头中获取X-Forwarded-For,如果X-Forwarded-For 存在就按逗号分隔取最左边的第一个IP地址,不存在直接通过request.getRemoteAdrr();
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-
D2i1HViI-1650442761725)(ip%E9%99%90%E6%B5%81%E5%8A%9F%E8%83%BD%E7%9A%84%E5%AE%9E%E7%8E%B0/image-20220417202935603.png)]
伪造X-Forwarded-For
因为在客户端和服务端进行通信的时候,我们需要进行三次握手
X-Forwarded-For 介绍
X-Forwarded-For 是一个 HTTP 扩展头部,用来表示HTTP请求端真实 IP,HTTP/1.1 协议并没有对它的定义,但现如今X-Forwarded-For已被各大 HTTP 代理、负载均衡等转发服务广泛使用。
一般的客户端发送HTTP请求是没有X-Forwarded-For,当请求到达第一个代理服务器时,代理服务器上会加上X-Forwarded-For,并将值设为客户端的IP地址(也就是左边的第一个值),后面如果还有很多代理,会将IP追加到X-Forwarded-For最右边,最终请求到达web应用服务器,应用通过X-Forwarded-For头取左边第一个IP为真实IP,如果客户端在发起请求的时候,请求头上带上一个伪造的X-Forwarded-For,由于后续的每层代理只会追加不会覆盖,那么最终到达应用服务器时,获取左边的第一个IP地址将会是客户端伪造的IP。也就是Java代码getClientIp()方法很有可能是伪造的IP地址,如果投票系统使用这样进行限制的话,很容易被刷票。
伪造X-Forwarded-For,使用postman来进行伪造就可以了
如果让Nginx支持X-Forwarded-For
proxy_ser_header X-Forwarded-For $proxy_add_x_forwarded_for
如果用户在请求时候伪造的话,那么会出现上面案例的client1前面,出现伪造的ip:
X-Forwarded-For: 伪造ip1, 伪造ip2, client_ip, proxy1_ip, proxy2_ip
防范:
- 直接在对外的Nginx反向代理的服务器上配置
既然我们能够直接获得真实的客户端 IP,那么我们为什么还要获得 X-Forwarded-For 呢?原因在于如果配置了多层的代理,那么这个 X-Real-IP 将会是上一层代理的真实 IP。
我们直接获得 X-Forwarded-For 将会有 IP 被伪造的风险,而使用 X-Real-IP 将会无法获得真实的 IP 地址。我们将两者的优势进行结合,便可防止客户端 IP 伪造。
proxy_set_header X-Real-IP $remote_addr
proxy_set_header X-Forwarded-For $remote_addr
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
...
}
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes) ra;
HttpServletRequest request = sra.getRequest();
//客户端真实IP
String ip = request.getHeader("X-Real-IP");
r e m o t e a d d r 替 代 上 面 的 remote_addr替代上面的 remoteaddr替代上面的proxy_add_x_forwarded_for,KaTeX parse error: Double subscript at position 12: proxy_add_x_̲forwarded_for会在…remote_addr是获取的是直接TCP连接的客户端IP。(类似于在JAVA中的request.getRemoteAddr(),是无法伪造的。即使客户端伪造也会被覆盖掉,而不是追加。
如果有多层代理,那么只要直接对外访问的Nginx上配置X-Forwarded-For为$remote_addr,内部的还是跟之前一样,不然内部的Nginx又会覆盖掉客户端的真实IP
- tomcat的源码的方法
遍历头中的IP地址,从右向左遍历,遍历可以通过正则表达式去除内网IP和代理服务器本身的IP,拿到的第一个非移除会使一个可信任的ip
RemoteIpValve
可以替换Servlet API中request.getRemoteAddr()
方法的实现,让request.getRemoteAddr()
方法从X-Forwarded-For
头中获取IP地址。也就是在业务代码中不需要再自己实现类似于上面的getClientIp()
方法来从X-Forwarded-For
中获取IP,而是直接使用request.getRemoteAddr()
方法。想要使用RemoteIpValve
,仅需要在Tomcat配置文件server.xml中Host元素内末尾加上:
<Valve className="org.apache.catalina.valves.RemoteIpValve" ... />
RemoteIpValve
有一套防止伪造X-Forwarded-For
的机制,实现思路:遍历X-Forwarded-For
头中的IP地址,和方法一不同的是,不是直接取左边第一个IP,而是从右向左遍历。遍历时可以根据正则表达式剔除掉内网IP和已知的代理服务器本身的IP(例如192.168开头的IP),那么拿到的第一个非剔除IP就会是一个可信任的客户端IP。这种方法的巧妙之处在于,即使伪造X-Forwarded-For
,那么请求到达应用服务器时,伪造的IP也会在X-Forwarded-For
值的左边,真实的IP为放到右边的某个位置,从右向左遍历就可以避免取到这些伪造的IP地址。
.168开头的IP),那么拿到的第一个非剔除IP就会是一个可信任的客户端IP。这种方法的巧妙之处在于,即使伪造X-Forwarded-For
,那么请求到达应用服务器时,伪造的IP也会在X-Forwarded-For
值的左边,真实的IP为放到右边的某个位置,从右向左遍历就可以避免取到这些伪造的IP地址。