目录
Spring Cloud Gateway是Spring Cloud生态中的新一代API网关,基于Spring 5、Spring Boot 2.0和Project Reactor等技术构建,旨在为微服务架构提供统一的API路由管理、过滤器功能(如熔断、限流)及动态路由能力。
使用Spring Cloud Gateway可以非常方便的进行API路由管理,实现过滤功能,也可进行用户验证,权限校验。在Spring Cloud Gateway的GlobalFilter中使用Feign调用进行用户校验是一个常见的需求(如统一鉴权),但需要注意异步调用、依赖注入和错误处理问题。
在gateway中进行用户验证鉴权,如果直接在Filter中使用Feign调用进行用户校验,会出现java.lang.IllegalStateException,因为Feign是同步调用,而Gateway基于Reactor异步模型,故有如下图所示的错误信息:

以下是在gateway中使用Feign调用进行用户校验的具体实现方案和注意事项:
一、创建Gateway网关模块
1. 添加依赖 (pom.xml)
确保网关模块已引入OpenFeign和Spring Cloud LoadBalancer(Gateway默认使用Reactive环境)以及nacos依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
<version>3.1.1</version>
</dependency>
<!-- nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2021.0.1.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2021.0.1.0</version>
</dependency>
2. 启用Feign客户端
在Gateway启动类上类启用FeignClients:
@SpringBootApplication
@EnableFeignClients // 关键注解 启用FeignClients
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
}
3. 定义Feign客户端接口
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
@FeignClient(name = "user", fallback = UserClientFallback.class)
public interface UserFeignClients {
@GetMapping("/verifyUser")
ResponseEntity<Boolean> verifyUser();
}
4.定义降级策略
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
@Component
public class UserClientFallback implements UserFeignClients {
@Override
public ResponseEntity<Boolean> verifyUser() {
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body(false);
}
}
5.配置HttpMessageConverters
在GatewayConfig配置类中添加HttpMessageConverters Bean
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
@Configuration
public class GatewayConfig {
@Bean
public HttpMessageConverters messageConverters() {
List<HttpMessageConverter<?>> converters = new ArrayList<>();
converters.add(new MappingJackson2HttpMessageConverter());
return new HttpMessageConverters(converters);
}
}
6.路由配置
在配置文件中配置路由,开启熔断器。
server:
port: 8080
spring:
application:
name: gateway
cloud:
nacos:
config:
server-addr: localhost:8848
file-extension: yaml
discovery:
server-addr: localhost:8848
gateway:
# 路由配置项,对应 RouteDefinition 数组
routes:
- id: user
uri: lb://user
predicates:
- Path=/user/**
filters:
- StripPrefix=0
- id: order
uri: lb://order
predicates:
- Path=/order/**
filters:
- StripPrefix=0
# 开启熔断器
feign:
circuitbreaker:
enabled: true
7.实现GlobalFilter
创建MyFilter类实现GlobalFilter,在filter中进行调用验证用户
import com.demo.feigh.UserFeignClients;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class MyFilter implements GlobalFilter {
@Autowired
private UserFeignClients userFeignClients;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ResponseEntity<Boolean> responseEntity = userFeignClients.verifyUser();
if (responseEntity.getStatusCode().is2xxSuccessful() && Boolean.TRUE.equals(responseEntity.getBody())) {
return chain.filter(exchange); // 校验通过
}
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
}
8.创建User和Order模块
user和order模块的创建此处略过,只展示controller中的伪代码,具体业务可自行实现。
UserController:
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
@GetMapping("/verifyUser")
public ResponseEntity<Boolean> verifyUser(){
return new ResponseEntity<>(true, HttpStatus.OK);
}
}
OrderController:
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderController {
@GetMapping("order")
public String Orders(){
return "orders";
}
}
二、方法一:配置负载均衡客户端,手动实现负载均衡算法
在GatewayConfig配置类中添加以下代码
@Bean
public BlockingLoadBalancerClient blockingLoadBalancerClient(ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerClientFactory,
DiscoveryClient discoveryClient) {
return new BlockingLoadBalancerClient(loadBalancerClientFactory) {
@Override
public <T> ServiceInstance choose(String serviceId, Request<T> request) {
List<ServiceInstance> instanceList = discoveryClient.getInstances(serviceId);
return loadBalancerInstance(instanceList);
}
};
}
private static ServiceInstance loadBalancerInstance(List<ServiceInstance> instanceList) {
if (instanceList == null || instanceList.isEmpty()) {
return null;
}
if (instanceList.size() == 1) {
return instanceList.get(0);
}
// 随机负载
Random random = new Random();
int index = random.nextInt(instanceList.size());
return instanceList.get(index);
}
三、 方法二:异步调用
在GlobalFilter的实现类MyFilter中进行异步调用验证用户。
关键:使用Mono.fromFuture将Feign同步调用转为异步
import com.demo.feigh.UserFeignClients;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.concurrent.CompletableFuture;
@Component
public class MyFilter implements GlobalFilter {
@Autowired
private UserFeignClients userFeignClients;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// ResponseEntity<Boolean> responseEntity = userFeignClients.verifyUser();
// if (responseEntity.getStatusCode().is2xxSuccessful() && Boolean.TRUE.equals(responseEntity.getBody())) {
// return chain.filter(exchange); // 校验通过
// }
// ServerHttpResponse response = exchange.getResponse();
// response.setStatusCode(HttpStatus.UNAUTHORIZED);
// return response.setComplete();
return Mono.fromFuture(
CompletableFuture.supplyAsync(() -> userFeignClients.verifyUser())
)
.flatMap(response -> {
if (response.getStatusCode().is2xxSuccessful() && Boolean.TRUE.equals(response.getBody())) {
return chain.filter(exchange); // 校验通过
}
return unauthorizedResponse(exchange, "Invalid user");
})
.onErrorResume(e ->
unauthorizedResponse(exchange, "user service error: " + e.getMessage())
);
}
private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String message) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
exchange.getResponse().getHeaders().add("Content-Type", "application/json");
return exchange.getResponse().writeWith(
Mono.just(exchange.getResponse()
.bufferFactory().wrap(message.toString().getBytes())
)
);
}
}
启动gateway、user、order,并在浏览器中访问,结果如下:

四、总结
-
异步转同步
Feign是同步调用,而Gateway基于Reactor异步模型。必须通过CompletableFuture.supplyAsync将阻塞调用提交到线程池,再用Mono.fromFuture转为响应式流。 -
线程池隔离
CompletableFuture.supplyAsync默认使用ForkJoinPool,高并发时建议指定独立线程池:private static final ExecutorService asyncPool = Executors.newFixedThreadPool(100); // 使用时: CompletableFuture.supplyAsync(() -> userFeignClients.verifyUser(), asyncPool) -
超时控制
在Feign客户端添加超时配置:feign: client: config: default: connectTimeout: 2000 readTimeout: 5000 -
服务降级
使用fallback处理熔断,防止认证服务不可用导致网关雪崩。 -
信息传递
如需将用户信息传给下游服务,可追加请求头:exchange.getRequest().mutate().header("user-id", userId).build();
优化建议
- 缓存机制:对已验证Token做短期缓存(如redis或者本地缓存Caffeine),减少Feign调用频次
- JWT本地校验:如果使用JWT,网关可本地验签,无需远程调用
- 权限信息预加载:将用户权限缓存在网关,避免每次请求都查库
异常场景处理
| 场景 | 处理方式 |
|---|---|
| token缺失或校验失败 | 返回401 UNAUTHORIZED |
| Feign调用超时 | 返回503 SERVICE_UNAVAILABLE |
| 用户服务返回false | 返回403 FORBIDDEN |
| 网络异常 | 启用熔断降级,拒绝访问 |
本地缓存Caffeine),减少Feign调用频次
- JWT本地校验:如果使用JWT,网关可本地验签,无需远程调用
- 权限信息预加载:将用户权限缓存在网关,避免每次请求都查库
异常场景处理
| 场景 | 处理方式 |
|---|---|
| token缺失或校验失败 | 返回401 UNAUTHORIZED |
| Feign调用超时 | 返回503 SERVICE_UNAVAILABLE |
| 用户服务返回false | 返回403 FORBIDDEN |
| 网络异常 | 启用熔断降级,拒绝访问 |
通过以上实现,网关可在不影响异步性能的前提下,安全调用远程服务完成鉴权,同时保障系统的弹性和稳定性。
1414

被折叠的 条评论
为什么被折叠?



