springcloudgateway进行feign调用校验用户的实现方案


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,并在浏览器中访问,结果如下:
在这里插入图片描述

四、总结

  1. 异步转同步
    Feign是同步调用,而Gateway基于Reactor异步模型。必须通过CompletableFuture.supplyAsync将阻塞调用提交到线程池,再用Mono.fromFuture转为响应式流。

  2. 线程池隔离
    CompletableFuture.supplyAsync默认使用ForkJoinPool,高并发时建议指定独立线程池:

    private static final ExecutorService asyncPool = Executors.newFixedThreadPool(100);
    // 使用时:
    CompletableFuture.supplyAsync(() -> userFeignClients.verifyUser(), asyncPool)
    
  3. 超时控制
    在Feign客户端添加超时配置:

    feign:
      client:
        config:
          default:
            connectTimeout: 2000
            readTimeout: 5000
    
  4. 服务降级
    使用fallback处理熔断,防止认证服务不可用导致网关雪崩。

  5. 信息传递
    如需将用户信息传给下游服务,可追加请求头:

    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
网络异常启用熔断降级,拒绝访问

通过以上实现,网关可在不影响异步性能的前提下,安全调用远程服务完成鉴权,同时保障系统的弹性和稳定性。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值