当前位置: 首页 > news >正文

Spring Cloud 以Gateway实现限流(自定义返回内容)

前言


    Spring Cloud Gateway自带RequestRateLimiterGatewayFilterFactory限流方案,可基于Redis和RedisRateLimiter实现默认算法为令牌桶的请求限流。作为自带的该限流方案,其可与Spring生态的其它各项组件无缝集成,并且自身实现也相对完善/好用,因此在没有特殊/复杂需求的情况下,该方案是实现基础限流的首选。
 
 

依赖


    在pom.xml文件中添加以下依赖。

<!--  Spring Boot Redis响应式起步依赖:用于实现请求限流  -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<!--  Spring Cloud网关起步依赖  -->
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

    继承&改造UsernamePasswordAuthenticationToken类以创建免密授权的鉴权类,该类会置null密码以逃脱密码校验。此外该类还可限定免密授权的处理器不会作用在其它授权模式上,因此其功能虽然完全可以使用UsernamePasswordAuthenticationToken代替,但其存在依然是不可省略的。

/*** @Author: 说淑人* @Date: 2025/5/8 21:45* @Description: 免密鉴权令牌类*/
public static class PasswordLessAuthenticationToken extends UsernamePasswordAuthenticationToken {private static final long serialVersionUID = -2798549339574220892L;public PasswordLessAuthenticationToken(Object principal) {// ---- credentials即为密码,为null表示不进行校验。super(principal, null);setAuthenticated(false);}public PasswordLessAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {super(principal, null, authorities);}}

    实现AuthenticationProvider接口以创建免密授权的实际处理器。

/*** @Author: 说淑人* @Date: 2025/5/8 19:38* @Description: 免密鉴权供应者类*/
@Component
public class PasswordLessAuthenticationProvider implements AuthenticationProvider {private final UserDetailsService userDetailsService;public PasswordLessAuthenticationProvider(UserDetailsService userDetailsService) {this.userDetailsService = userDetailsService;}@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {// ---- 获取用户名,并调用我们实现的userDetailsService.loadUserByUsername(username)String username = (String) authentication.getPrincipal();UserDetails user = userDetailsService.loadUserByUsername(username);// ---- 如果用户不存在,按框架逻辑抛出原样异常以统一格式。if (user == null) {throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");}return new PasswordLessAuthenticationToken(user, user.getAuthorities());}@Overridepublic boolean supports(Class<?> authentication) {// ---- 该方法用于限定PasswordLessAuthenticationProvider只对// PasswordLessAuthenticationToken生效,这个可以避免// PasswordLessAuthenticationProvider作用于其它授权模式,而这会导致混乱。return PasswordLessAuthenticationToken.class.isAssignableFrom(authentication);}}

 
 

配置


    在application.yml文件中添加以下Redis与Gateway配置。

# ---- Spring Config
spring:# ---- Redis Configredis:host: 127.0.0.1port: 6379# ---- Gateway Configgateway:routes:# ---- 转发的服务。- id: world-biz-manageuri: lb://world-biz-managepredicates:- Path=/api/manage/**# ---- 添加RequestRateLimiter过滤器实现限流。因为过滤器是注册在具体服# 务下的,因此也只会对当前服务进行限流。filters:- name: RequestRateLimiterargs:# ---- 键解析器:定义限流数据键的生成规则,常用的生成规则有基于用# 户/IP/接口,而这里使用IP进行区分,即各IP限流数据是独立的。key-resolver: "#{@ipKeyResolver}"# ---- 每秒生成15个令牌,因此在令牌桶无令牌的情况下,一个IP每秒# 最多请求15次。redis-rate-limiter.replenishRate: 15# ---- 令牌桶中最多保存30个令牌,可支持单个IP最多30个请求/秒的流# 量高发。redis-rate-limiter.burstCapacity: 30# ---- 一次请求消耗一个令牌。redis-rate-limiter.requestedTokens: 1

    创建WebConfig(名称自定)类,用于内部创建/注册IP键解析器实例。

package com.ssr.world.frame.gateway.tool.config.web;import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Mono;/*** @Author: 说淑人* @Date: 2025/4/22 22:39* @Description: 网络配置类*/
@Configuration
public class WebConfig {/*** IP键解析器** @return IP键解析器*/@Bean// ---- 你没看错,没有public。KeyResolver ipKeyResolver() {// ---- 以IP地址作为限流键的区分标志,即每个IP都有自己独立的令牌桶。return exchange -> Mono.just(// ---- 此处也可以获取其它参数作为限流键的区分表示,例如:// 头信息中携带的用户ID// 请求的接口// ---- 将上述参数混合使用也是不错的做法。exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());}}

 
 

启动&测试


# ---- 为了方便测试,我们暂时修改令牌的生成/存储上限为1/1。
redis-rate-limiter.replenishRate: 1
redis-rate-limiter.burstCapacity: 1

    使用Postman进行连续快速地多次调用后返回以下内容,表示请求被限流:

![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/c1b593acd672465bb9cb74f1dd24bda1.png
    Redis数据结构如下所示。注意!由于限流数据键每秒都会更新/删除,所以想看到的话需要频繁请求接口以保持限流状态。
在这里插入图片描述
 
 

自定义回应


    Spring Cloud Gateway通过返回429的异常状态来表示限流异常/情况,但显然这种行为并无法兼容进异常的统一返回格式,因此此处会展示自定义限流异常回应信息的完整流程…这通过自定义限流过滤器的方式实现:
    创建WebRequestRateLimiterGatewayFilterFactory类并重写apply(Config config)方法以自定义限流回应的内容/格式。

import com.alibaba.fastjson2.JSONObject;
import com.ssr.world.tool.toft.model.bo.result.ResultBox;
import com.ssr.world.tool.toft.model.eo.web.WebResultEnum;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;@Component
public class WebRequestRateLimiterGatewayFilterFactory extends RequestRateLimiterGatewayFilterFactory {// ---- defaultKeyResolver会因为多实例而出现无法注入的情况,可通过// 在ipKeyResolver()方法上添加@Primary注解的方式处理。public WebRequestRateLimiterGatewayFilterFactory(RateLimiter defaultRateLimiter, KeyResolver defaultKeyResolver) {super(defaultRateLimiter, defaultKeyResolver);}@Overridepublic GatewayFilter apply(Config config) {// ---- 获取健解析器/速率限制器。KeyResolver resolver = config.getKeyResolver() != null ? config.getKeyResolver() : getDefaultKeyResolver();RateLimiter<Object> limiter = config.getRateLimiter() != null ? config.getRateLimiter() : getDefaultRateLimiter();return (exchange, chain) -> resolver.resolve(exchange).flatMap(key -> {String routeId = config.getRouteId();if (routeId == null) {Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);assert route != null;routeId = route.getId();}return limiter.isAllowed(routeId, key).flatMap(response -> {// ---- 继承所有的头信息。for (Map.Entry<String, String> header : response.getHeaders().entrySet()) {exchange.getResponse().getHeaders().add(header.getKey(), header.getValue());}// ---- 如果未被限流,直接返回。if (response.isAllowed()) {return chain.filter(exchange);}// ---- 如果被限流了,重置回应体。ServerHttpResponse httpResponse = exchange.getResponse();// ---- 重设状态/内容类型/内容长度(皆可选)。httpResponse.setStatusCode(HttpStatus.OK);httpResponse.getHeaders().setContentType(MediaType.APPLICATION_JSON_UTF8);httpResponse.getHeaders().setContentLength(REQUEST_TOO_QUICKLY_BYTES.length);// ---- 重设回应体。Map<String, Object> map = new HashMap<>();map.put("code", "自定义错误码");map.put("message", "请求频率过高,请稍后重试...");map.put("data", "自定义数据");map.put("success", false);// ---- 回应内容是固定的,因此可以bytes直接在static中预加载,这能一定程度提升性能。byte[] bytes = JSONObject.toJSONString(map).getBytes(StandardCharsets.UTF_8);DataBuffer buffer = httpResponse.bufferFactory().wrap(bytes);return httpResponse.writeWith(Mono.just(buffer));});});}}

    修改application.yml文件中指定的限流过滤器为自定义的限流过滤器。

filters:# ---- 将原本的RequestRateLimiter改为自定义的WebRequestRateLimiter,即略去名称尾部的GatewayFilterFactory部分。- name: WebRequestRateLimiterargs:key-resolver: "#{@ipKeyResolver}"redis-rate-limiter.replenishRate: 1redis-rate-limiter.burstCapacity: 1redis-rate-limiter.requestedTokens: 1

    启动测试。
在这里插入图片描述
在这里插入图片描述

相关文章:

  • DVWA靶场保姆级通关教程--06不安全验证机制
  • 安全核查基线-1.LPD服务
  • 构筑芯片行业的“安全硅甲”
  • 教育+AI:个性化学习能否颠覆传统课堂?
  • 游戏引擎学习第266天:添加顶部时钟概览视图。
  • CSS实现图片垂直居中方法
  • 利用GPT实现油猴脚本—网页滚动(优化版)
  • CSS flex:1
  • C23 与 MISRA C:2025:嵌入式 C 语言的进化之路
  • 计算机视觉与深度学习 | 视觉+激光雷达+惯惯性SLAM算法汇总(原理,公式,代码)
  • JDK8 HashMap红黑树退化为链表的机制解析
  • 为人类文明建一座“永不遗忘”的数字博物馆:Funes 技术解析
  • 【计算机视觉】Car-Plate-Detection-OpenCV-TesseractOCR:车牌检测与识别
  • 在 MyBatis 中实现控制台输出 SQL 参数
  • java学习笔记
  • AI客服问答自动生成文章(基于deepseek实现)
  • ABB电机保护单元通过Profibus DP主站转Modbus TCP网关实现上位机通讯
  • Vulnhub Lazysysadmin靶机攻击实战(一)
  • 硬链接与软连接
  • 如何从极狐GitLab 容器镜像库中删除容器镜像?
  • 碧桂园:砸锅卖铁保交房、持续推进保主体,尽快让公司恢复正常经营
  • 习近平会见古巴国家主席迪亚斯-卡内尔
  • 欧洲承诺投资6亿欧元吸引外国科学家
  • “三德子”赵亮直播间卖“德子土鸡”,外包装商标实为“德子土”
  • 调节负面情绪可以缓解慢性疼痛
  • 戴维·珀杜宣誓就任美国驻华大使