微服务网关:别再让接口 “各自为战”!Gateway 实战 + 鉴权全攻略

兄弟们,先灵魂拷问:

微服务拆了 5 个服务后,前端小姐姐是不是天天追着你要 "统一接口地址"?

每个服务都写一遍 token 验证,是不是感觉自己在 "复制粘贴式搬砖"?

线上排查问题,想拦个请求看参数,是不是得改 N 个服务的代码?

如果你中了两条以上 ------ 别慌,你缺的不是咖啡,是一个 "微服务管家":网关

一、先搞懂:网关到底是个啥?

别被 "架构名词" 吓到,网关其实就是个 "中间商",干 3 件接地气的事:

  1. 看门:谁能进(鉴权)、谁不能进(拦截非法请求),比如没 token 直接打回;
  1. 指路:用户访问/api/user,自动转发到用户服务,不用记 N 个服务地址;
  1. 打杂:给请求加个 header、改个路径、记录个日志,不用每个服务自己干。

简单说:网关 = 小区保安 + 快递中转站 + 公司行政,让微服务们专心 "搞业务"。

二、网关技术方案:该 pick 谁?

市面上的网关不少,别瞎跟风,看场景选:

方案 特点 适合场景
Kong/APISIX 基于 Nginx,性能强,运维友好 多语言架构、高并发场景
Spring Cloud Gateway 纯 Java,和 Spring 生态无缝衔接 Java 后端主导的微服务
Zuul 老牌网关,性能一般 老项目维护(新项目别用)

咱们后端搬砖人,大多是 Spring 生态,所以重点聊 Spring Cloud Gateway(以下简称 Gateway)。

三、为啥 Gateway 把 Zuul 按在地上摩擦?

老玩家可能记得 Zuul,但现在基本被 Gateway 取代,原因很真实:

  1. 性能差太多:Zuul 1.x 是同步阻塞(请求来了等半天),Gateway 基于 Netty 异步非阻塞,吞吐量直接翻倍;
  1. 功能太拉胯:Zuul 想加个动态路由、自定义过滤,得改代码重启;Gateway 支持配置化,改 yaml 就行;
  1. Zuul 2.x 跳票坑:Zuul 2.x 本来想补异步的坑,结果跳票 N 年,等它出来,Gateway 已经占领半壁江山了...

总结:选 Gateway,就是选 "不加班"------ 毕竟谁也不想因为 Zuul 的性能问题,半夜起来改代码。

四、实战:10 分钟搭一个 Gateway 服务

光说不练假把式,咱们用 Spring Boot + Gateway + Nacos(注册中心)搭一个,步骤超简单:

1. 加依赖(别踩坑!)

注意:Gateway 不能加spring-boot-starter-web依赖,会冲突!

xml 复制代码
<dependencies>
    <!-- Gateway核心依赖 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <!-- 注册中心(Nacos为例,Eureka/Consul也一样) -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
</dependencies>

2. 写配置(application.yml)

核心是 "路由规则":用户访问啥路径,转发到哪个服务?

yaml 复制代码
spring:
  application:
    name: gateway-service # 网关服务名
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848 # Nacos地址
    gateway:
      routes:
        # 路由1:转发用户服务
        - id: user-service-route # 唯一标识(随便起)
          uri: lb://user-service # 转发到Nacos中的user-service(lb=负载均衡)
          predicates: # 匹配规则(断言)
            - Path=/api/user/** # 只要访问/api/user/开头的路径,就走这个路由
          filters: # 过滤规则
            - RewritePath=/api/user/(?<segment>.*), /user/${segment} # 路径重写:/api/user/1 → /user/1
        # 路由2:转发订单服务(同理)
        - id: order-service-route
          uri: lb://order-service
          predicates:
            - Path=/api/order/**
          filters:
            - RewritePath=/api/order/(?<segment>.*), /order/${segment}

3. 加启动类

就加个@EnableDiscoveryClient,没啥花活:

less 复制代码
@SpringBootApplication
@EnableDiscoveryClient // 注册到Nacos
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}

启动后,访问http://localhost:8080/api/user/1,会自动转发到user-service的/user/1接口 ------ 成了!

五、路由断言:网关的 "指路牌"

刚才配置里的predicates就是 "断言",简单说:满足条件的请求,才走这个路由

常用断言整理好了,直接抄:

断言类型 例子 意思
Path(路径) Path=/api/user/** 匹配 /api/user/ 开头的路径
After(时间) After=2024-05-01T00:00:00+08:00[Asia/Shanghai] 2024 年 5 月 1 日后才生效
Query(参数) Query=token 请求必须带 token 参数(如?token=xxx)
RemoteAddr(IP) RemoteAddr=192.168.1.0/24 只允许 192.168.1.x 网段的请求

可以多个断言组合,比如:Path=/api/user/** && Query=token------ 既匹配路径,又得带 token 参数。

六、网关过滤:给请求 "加 buff" 或 "拦坑"

过滤分两种:内置过滤器 (现成的)和自定义过滤器(自己写),都是为了处理请求。

1. 内置过滤器:拿来就用

Gateway 自带很多过滤器,不用写代码,配置就行:

过滤器名 例子 作用
AddRequestHeader AddRequestHeader=X-User-Id, 123 给请求加个 X-User-Id 头,值为 123
SetStatus SetStatus=404 把响应状态码改成 404
RewritePath 前面实战用过,路径重写 隐藏真实接口路径
RequestRateLimiter 限流用(后续单独讲) 防止接口被刷爆

如图所示:

  1. 客户端请求进入网关后由HandlerMapping对请求做判断,找到与当前请求匹配的路由规则(Route ),然后将请求交给WebHandler去处理。
  2. WebHandler则会加载当前路由下需要执行的过滤器链(Filter chain ),然后按照顺序逐一执行过滤器(后面称为**Filter**)。
  3. 图中Filter被虚线分为左右两部分,是因为Filter内部的逻辑分为prepost两部分,分别会在请求路由到微服务之前之后被执行。
  4. 只有所有Filterpre逻辑都依次顺序执行通过后,请求才会被路由到微服务。
  5. 微服务返回结果后,再倒序执行Filterpost逻辑。
  6. 最终把响应结果返回。

在网关过滤器链中,有两种过滤器:

  • GatewayFilter :路由过滤器,作用范围比较灵活,可以是任意指定的路由Route.
  • GlobalFilter:全局过滤器,作用范围是所有路由,不可配置。通常用于自定义过滤器实现拦截功能的

💡FilteringWebHandler在处理请求时,会将GlobalFilter装饰为GatewayFilter,然后放到过滤器链中,排序以后依次执行。

2. 自定义过滤器:满足特殊需求

过滤器分两类,别搞混:

(1)GatewayFilter:路由级过滤(只对某个路由生效)

比如给 "用户服务路由" 加个过滤器,打印请求日志:

typescript 复制代码
// 1. 写过滤器
@Component
public class LogGatewayFilter implements GatewayFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 打印请求路径
        System.out.println("请求路径:" + exchange.getRequest().getPath());
        // 继续往下走(不放行就卡这了)
        return chain.filter(exchange);
    }
    // 过滤器顺序(数字越小越先执行)
    @Override
    public int getOrder() {
        return 0;
    }
}
// 2. 配置路由时引用
spring:
  cloud:
    gateway:
      routes:
        - id: user-service-route
          uri: lb://user-service
          predicates:
            - Path=/api/user/**
          filters:
            - name: LogGatewayFilter # 引用自定义的过滤器

(2)GlobalFilter:全局过滤(所有请求都生效)

比如全局打印请求时间,不用在每个路由配置:

java 复制代码
@Component
public class GlobalLogFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        long start = System.currentTimeMillis();
        // 执行完后续逻辑后,打印耗时
        return chain.filter(exchange)
                .doFinally(signal -> {
                    long end = System.currentTimeMillis();
                    System.out.println("请求耗时:" + (end - start) + "ms");
                });
    }
    @Override
    public int getOrder() {
        return -1; // 全局过滤器,先执行
    }
}

七、鉴权:网关的 "门禁卡",必须搞懂!

最核心的场景来了:所有请求必须带合法 token,否则不让进

为啥在网关鉴权?------ 总不能每个服务都写一遍 token 验证吧?网关统一拦,效率高!

鉴权逻辑:3 步走

  1. 从请求头里拿 token(比如Authorization: Bearer xxx);
  1. 验证 token 是否合法(调用认证服务,或用 JWT 解密);
  1. 合法就放行,不合法返回 401(未授权)。

实战:自定义全局鉴权过滤器

用 JWT 为例(无状态,适合微服务):

java 复制代码
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
    // 假设这是JWT工具类(实际项目用现成的,比如jjwt)
    private final JwtUtil jwtUtil;
    @Autowired
    public AuthGlobalFilter(JwtUtil jwtUtil) {
        this.jwtUtil = jwtUtil;
    }
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1. 跳过白名单(比如登录接口,不用鉴权)
        String path = exchange.getRequest().getPath().value();
        if ("/api/auth/login".equals(path)) {
            return chain.filter(exchange);
        }
        // 2. 拿token
        String token = exchange.getRequest().getHeaders().getFirst("Authorization");
        if (token == null || !token.startsWith("Bearer ")) {
            return handleUnauthorized(exchange); // 没token,返回401
        }
        token = token.substring(7); // 去掉"Bearer "前缀
        // 3. 验证token
        try {
            Claims claims = jwtUtil.parseToken(token); // JWT解密
            // 把用户信息放入上下文,后续服务可以直接拿
            exchange.getAttributes().put("userId", claims.get("userId"));
        } catch (Exception e) {
            return handleUnauthorized(exchange); // token无效,返回401
        }
        // 4. 放行
        return chain.filter(exchange);
    }
    // 处理未授权:返回401
    private Mono<Void> handleUnauthorized(ServerWebExchange exchange) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
        String json = "{"code":401,"msg":"未授权,请先登录"}";
        DataBuffer buffer = response.bufferFactory().wrap(json.getBytes(StandardCharsets.UTF_8));
        return response.writeWith(Mono.just(buffer));
    }
    @Override
    public int getOrder() {
        return -2; // 比全局日志过滤器先执行
    }
}

这样一来,所有非白名单请求,都会先过鉴权 ------ 搞定!

总结:网关学好,搬砖没烦恼

  1. 网关是微服务的 "管家",负责路由、过滤、鉴权;
  1. 选 Gateway 不选 Zuul,因为性能好、生态近;
  1. 路由靠断言,过滤分内置 / 自定义,鉴权用全局过滤器;
  1. 实战代码直接抄,注意别踩 "加 web 依赖" 的坑。

最后问一句:你在网关实战中遇到过啥奇葩问题?比如动态路由不生效、鉴权丢头?评论区聊聊,一起避坑~

相关推荐
叫我阿柒啊13 小时前
从Java全栈到前端框架:一场真实的技术面试实录
java·spring boot·redis·typescript·vue3·jwt·前后端分离
这里有鱼汤13 小时前
从0开始:如何用miniQMT跑起最小的实盘策略
后端·python
武子康13 小时前
Java-114 深入浅出 MySQL 开源分布式中间件 ShardingSphere 深度解读
java·数据库·分布式·mysql·中间件·性能优化·开源
Li_yizYa14 小时前
JVM:内存区域划分、类加载的过程、垃圾回收机制
java·jvm
Goboy14 小时前
有人敲门,开水开了,电话响了,孩子哭了,你先顾谁?
后端·面试·架构
失散1314 小时前
并发编程——06 JUC并发同步工具类的应用实战
java·架构·并发编程
2301_7813925214 小时前
从spring MVC角度理解HTTP协议及Request-Response模式
java·spring·mvc
程序员江鸟14 小时前
Java面试实战系列【JVM篇】- JVM内存结构与运行时数据区详解(共享区域)
java·jvm·面试
努力也学不会java14 小时前
【设计模式】三大原则 单一职责原则、开放-封闭原则、依赖倒转原则
java·设计模式·依赖倒置原则·开闭原则·单一职责原则