Spring Cloud Gateway通過全局過濾器實現接口防刷
環境:Spring Boot2.7.12 + Spring Cloud2021.0.7
1 概念
通過某種機制實現對系統中的某些接口在規定的時間段內只能讓某個具體的客戶端訪問指定次數,超出次數,就不讓訪問了。等待指定的時間到期后又能繼續訪問接口;這里需要注意的是是控制到每一個具體的接口上,所以必須確定兩個要素:
- 客戶端是誰
- 訪問的接口
2 實現原理
可以通過2種方式實現:
- 通過網關
可以控制到所有的服務 - 通過AOP
該方案只能針對具體的每一個服務,代碼重復,如果通過
本篇文章我們通過網關實現,那接下來就是考慮上該如何去記錄當前客戶端訪問的具體接口在指定的時間內已經訪問了多少次了?通過兩種方式:
- JVM層
該種實現方式,你需要自己實現時效性的檢查,實現麻煩 - 通過Redis
Redis本身就可以對Key設置時效性,所以非常的方便。本文通過Redis實現。
通過 Redis 記錄訪問請求的次數,每次訪問都進行遞減,如果次數小于0就返回錯誤信息,當到了指定的時效則Redis會對過期的key進行自動刪除。
3 代碼實現
Redis配置
spring:
redis:
host: localhost
port: 6379
password: 123123
database: 8
lettuce:
pool:
maxActive: 8
maxIdle: 100
minIdle: 10
maxWait: -1定義全局過濾器
@Component
public class BrushProofFilter implements GlobalFilter, Ordered {
private final ReactiveStringRedisTemplate reactiveStringRedisTemplate ;
public BrushProofFilter(ReactiveStringRedisTemplate reactiveStringRedisTemplate) {
this.reactiveStringRedisTemplate = reactiveStringRedisTemplate ;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 獲取客戶端的請求ip
InetAddress address = exchange.getRequest().getRemoteAddress().getAddress();
// 獲取請求的URI
String path = exchange.getRequest().getPath().toString() ;
// 將其組合為Redis中的Key
String key = ("ratelimiter:" + address + ":" + path) ;
// 通過拋出異常的方式終止序列,然后通過自定義的WebExceptionHandler處理異常信息
return this.reactiveStringRedisTemplate.opsForValue()
// 這里固定設置每30s,訪問10次
.setIfAbsent(key, "10", Duration.ofSeconds(30))
.flatMap(exist -> {
return this.reactiveStringRedisTemplate.opsForValue().decrement(key) ;
})
.doOnNext(num -> {
if (num < 0) {
throw new BrushProofException("你訪問太快了") ;
}
})
.then(chain.filter(exchange)) ;
}
@Override
public int getOrder() {
return -2 ;
}
}自定義異常
public class BrushProofException extends RuntimeException {
private static final long serialVersionUID = 1L;
public BrushProofException(String message) {
super(message) ;
}
}自定義異常處理句柄
@Component
public class RatelimiterWebExceptionHandler implements WebExceptionHandler {
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
if (ex instanceof RatelimiterException re) {
ServerHttpResponse response = exchange.getResponse() ;
response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR) ;
response.getHeaders().add("Content-Type", "text/html;charset=utf8") ;
// 直接輸出了,異常不進行傳遞
return response.writeWith(Mono.just(response.bufferFactory().wrap(("你訪問太快了").getBytes()))) ;
}
// 如果是其它異常,則將異常繼續向下傳遞
return Mono.error(ex) ;
}
}訪問測試
圖片
因為我這里沒有這個接口,所以返回的是降級接口,也算是正常
當超過10次后:
Redis
圖片
以客戶端請求ip + path作為key
圖片




























