Gateway微服务网关

Gateway微服务网关

1. Gateway微服务网关概述

核心功能

  • 请求路由:将请求转发到对应的微服务
  • 请求转发:作为所有微服务的统一入口
  • 身份校验:统一进行身份认证和权限验证
  • 负载均衡:配合服务发现实现负载均衡
  • 限流熔断:保护后端微服务

2. 项目集成Gateway配置

2.1 创建Gateway服务模块步骤

  1. 创建独立模块:作为网关服务
  2. 新建启动类 :添加@SpringBootApplication注解
  3. 引入依赖:添加Spring Cloud Gateway相关依赖
  4. 配置路由规则:定义路由转发规则

2.2 核心依赖配置

hm-gateway模块的pom.xml文件中引入依赖:

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>hmall</artifactId>
        <groupId>com.heima</groupId>
        <version>1.0.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>hm-gateway</artifactId>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>
    <dependencies>
        <!--common-->
        <dependency>
            <groupId>com.heima</groupId>
            <artifactId>hm-common</artifactId>
            <version>1.0.0</version>
        </dependency>
        <!--网关-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!--nacos discovery-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--负载均衡-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
    </dependencies>
    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

2.3 启动类示例

代码如下:

java 复制代码
package com.hmall.gateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}

2.3 路由规则配置示例

接下来,在gateway模块的resources目录新建一个application.yaml文件,内容如下:

yaml 复制代码
server:
  port: 8080
spring:
  application:
    name: gateway
  cloud:
    nacos:
      server-addr: 192.168.150.101:8848
    gateway:
      routes:
        - id: item # 路由规则id,自定义,唯一
          uri: lb://item-service # 路由的目标服务,lb代表负载均衡,会从注册中心拉取服务列表
          predicates: # 路由断言,判断当前请求是否符合当前规则,符合则路由到目标服务
            - Path=/items/**,/search/** # 这里是以请求路径作为判断规则
        - id: cart
          uri: lb://cart-service
          predicates:
            - Path=/carts/**
        - id: user
          uri: lb://user-service
          predicates:
            - Path=/users/**,/addresses/**
        - id: trade
          uri: lb://trade-service
          predicates:
            - Path=/orders/**
        - id: pay
          uri: lb://pay-service
          predicates:
            - Path=/pay-orders/**

3. 网关过滤器

3.1 Gateway工作原理

apl 复制代码
客户端请求 → Gateway网关 → 路由匹配 → 过滤器链 → 转发到微服务

如图中所示,最终请求转发是有一个名为NettyRoutingFilter 的过滤器来执行的,而且这个过滤器是整个过滤器链中顺序最靠后的一个。如果我们能够定义一个过滤器,在其中实现登录校验逻辑,并且将过滤器执行顺序定义到NettyRoutingFilter之前,符合需求:AddRequestHeaderGatewayFilterFacotry

3.2 过滤器分类

如何实现一个网关过滤器呢?

网关过滤器链中的过滤器有两种:

过滤器类型 作用范围 配置性
GatewayFilter 指定的路由Route 可配置
GlobalFilter 所有路由 不可配置

Gateway中内置了很多的GatewayFilter,详情可以参考官方文档:

https://docs.spring.io/spring-cloud-gateway/docs/3.1.7/reference/html/#gatewayfilter-factories

3.3 内置过滤器示例

AddRequestHeaderGatewayFilterFactory

  • 功能:添加请求头传递到下游服务
  • 配置:
yaml 复制代码
spring:
  cloud:
    gateway:
      routes:
      - id: test_route
        uri: lb://test-service
        predicates:
          -Path=/test/**
        filters:
          - AddRequestHeader=truth, anyone long-press like button will be rich # 请求头的key,value

在请求头中添加truth:anyone long-press like button will be rich的键值对信息。

如果想要让过滤器作用于所有的路由,则可以这样配置:

yaml 复制代码
server:
  port: 8080
spring:
  application:
    name: gateway
  cloud:
    nacos:
      server-addr: 192.168.150.101:8848
    gateway:
      routes:
        - id: item
          uri: lb://item-service
          predicates:
            - Path=/items/**,/search/**
        - id: user
          uri: lb://user-service
          predicates:
            - Path=/users/**,/addresses/**
        - id: cart
          uri: lb://cart-service
          predicates:
            - Path=/carts/**
        - id: trade
          uri: lb://trade-service
          predicates:
            - Path=/orders/**
        - id: pay
          uri: lb://pay-service
          predicates:
            - Path=/pay-orders/**
      default-filters:
        - AddRequestHeader=truth, anyone long-press like button will be rich

default-filters下的过滤器可以作用于所有路由

3.4 自定义过滤器实现

其实GatewayFilterGlobalFilter这两种过滤器的方法签名完全一致:

java 复制代码
/**
 * 处理请求并将其传递给下一个过滤器
 * @param exchange 当前请求的上下文,其中包含request、response等各种数据
 * @param chain 过滤器链,基于它向下传递请求
 * @return 根据返回值标记当前请求是否被完成或拦截,chain.filter(exchange)就放行了。
 */
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
3.4.1 自定义GatewayFilter

自定义类继承AbstractGatewayFilterFactory并编写过滤器代码:

java 复制代码
package com.hmall.gateway.filter;

import lombok.Data;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.OrderedGatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.List;

@Component
public class PrintAnyGatewayFilterFactory extends AbstractGatewayFilterFactory<PrintAnyGatewayFilterFactory.Config> {
    @Override
    public GatewayFilter apply(Config config) {
        return new OrderedGatewayFilter(new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                // 1.获取请求
                ServerHttpRequest request = exchange.getRequest();
                // 2.编写过滤器逻辑
                String oneLine = config.getOneLine();
                String twoLine = config.getTwoLine();
                String threeLine = config.getThreeLine();
                System.out.println("oneLine = " + oneLine);
                System.out.println("twoLine = " + twoLine);
                System.out.println("threeLine = " + threeLine);
                System.out.println("过滤器执行~~");
                // 3.放行
                return chain.filter(exchange);
            }
        }, 100);
    }

    @Data
    public static class Config{
        private String oneLine;
        private String twoLine;
        private String threeLine;
    }

    public PrintAnyGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return List.of("oneLine", "twoLine", "threeLine");
    }
}

项目配置

yaml 复制代码
spring:
  cloud:
    gateway:
      default-filters:
            - PrintAny=1,2,3 # 此处直接以自定义的GatewayFilterFactory类名称前缀类声明过滤器

还有一种用法,无需按照这个顺序,就是手动指定参数名:

yaml 复制代码
spring:
  cloud:
    gateway:
      default-filters:
            - name: PrintAny
              args: # 手动指定参数名,无需按照参数顺序
                oneLine: 1
                twoLine: 2
                threeLine: 3
3.4.2 自定义GlobalFilter

自定义类实现GlobalFilter并编写过滤器代码:

java 复制代码
package com.hmall.gateway.filter;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

//@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 模拟登录校验逻辑
        ServerHttpRequest request = exchange.getRequest();
        HttpHeaders headers = request.getHeaders();
        System.out.println("headers = " + headers);
        // 放行
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

4. 用户信息传递方案

4.1 网关层用户信息处理流程

4.1.1 JWT工具集成

在网关微服务中进行JWT令牌校验

登录校验需要用到JWT,而且JWT的加密需要秘钥和加密工具。

  • AuthProperties:配置登录校验需要拦截的路径,因为不是所有的路径都需要登录才能访问
java 复制代码
package com.hmall.gateway.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.List;

@Data
@Component
@ConfigurationProperties(prefix = "hm.auth")
public class AuthProperties {
    private List<String> includePaths;
    private List<String> excludePaths;
}
  • JwtProperties:定义与JWT工具有关的属性,比如秘钥文件位置
java 复制代码
package com.hmall.gateway.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.io.Resource;

import java.time.Duration;

@Data
@ConfigurationProperties(prefix = "hm.jwt")
public class JwtProperties {
    private Resource location;
    private String password;
    private String alias;
    private Duration tokenTTL = Duration.ofMinutes(10);
}
  • SecurityConfig:工具的自动装配
java 复制代码
package com.hmall.gateway.config;

import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.rsa.crypto.KeyStoreKeyFactory;

import java.security.KeyPair;

@Configuration
@EnableConfigurationProperties(JwtProperties.class) # 启用 Spring Boot 的配置属性功能
public class SecurityConfig {

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Bean
    public KeyPair keyPair(JwtProperties properties){
        // 获取秘钥工厂
        KeyStoreKeyFactory keyStoreKeyFactory =
                new KeyStoreKeyFactory(
                        properties.getLocation(),
                        properties.getPassword().toCharArray());
        //读取钥匙对
        return keyStoreKeyFactory.getKeyPair(
                properties.getAlias(),
                properties.getPassword().toCharArray());
    }
}
  • JwtTool:JWT工具,其中包含了校验和解析token的功能
java 复制代码
package com.hmall.gateway.util;

import cn.hutool.core.exceptions.ValidateException;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTValidator;
import cn.hutool.jwt.signers.JWTSigner;
import cn.hutool.jwt.signers.JWTSignerUtil;
import com.hmall.common.exception.UnauthorizedException;
import org.springframework.stereotype.Component;

import java.security.KeyPair;
import java.time.Duration;
import java.util.Date;

@Component
public class JwtTool {
    private final JWTSigner jwtSigner;

    public JwtTool(KeyPair keyPair) {
        this.jwtSigner = JWTSignerUtil.createSigner("rs256", keyPair);
    }

    /**
     * 创建 access-token
     *
     * @param userId 用户信息
     * @return access-token
     */
    public String createToken(Long userId, Duration ttl) {
        // 1.生成jws
        return JWT.create()
                .setPayload("user", userId)
                .setExpiresAt(new Date(System.currentTimeMillis() + ttl.toMillis()))
                .setSigner(jwtSigner)
                .sign();
    }

    /**
     * 解析token
     *
     * @param token token
     * @return 解析刷新token得到的用户信息
     */
    public Long parseToken(String token) {
        // 1.校验token是否为空
        if (token == null) {
            throw new UnauthorizedException("未登录");
        }
        // 2.校验并解析jwt
        JWT jwt;
        try {
            jwt = JWT.of(token).setSigner(jwtSigner);
        } catch (Exception e) {
            throw new UnauthorizedException("无效的token", e);
        }
        // 2.校验jwt是否有效
        if (!jwt.verify()) {
            // 验证失败
            throw new UnauthorizedException("无效的token");
        }
        // 3.校验是否过期
        try {
            JWTValidator.of(jwt).validateDate();
        } catch (ValidateException e) {
            throw new UnauthorizedException("token已经过期");
        }
        // 4.数据格式校验
        Object userPayload = jwt.getPayload("user");
        if (userPayload == null) {
            // 数据为空
            throw new UnauthorizedException("无效的token");
        }

        // 5.数据解析
        try {
           return Long.valueOf(userPayload.toString());
        } catch (RuntimeException e) {
            // 数据格式有误
            throw new UnauthorizedException("无效的token");
        }
    }
}
  • hmall.jks:秘钥文件

其中AuthPropertiesJwtProperties所需的属性要在application.yaml中配置:

yaml 复制代码
hm:
  jwt:
    location: classpath:hmall.jks # 秘钥地址
    alias: hmall # 秘钥别名
    password: hmall123 # 秘钥文件密码
    tokenTTL: 30m # 登录有效期
  auth:
    excludePaths: # 无需登录校验的路径
      - /search/**
      - /users/login
      - /items/**
4.1.2 登录校验过滤器

解析令牌获取用户信息

定义一个登录校验的过滤器,并编写保存用户信息到请求头并转发到下游微服务的代码。

java 复制代码
package com.hmall.gateway.filter;

import com.hmall.gateway.config.AuthProperties;
import com.hmall.gateway.util.JwtTool;
import lombok.RequiredArgsConstructor;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.List;

@Component
@RequiredArgsConstructor
public class AuthGlobalFilter implements GlobalFilter {

    private final JwtTool jwtTool;

    private final AuthProperties authProperties;

    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1、获取request
        ServerHttpRequest request = exchange.getRequest();
        // 2、判断是否需要拦截,若不需要,则放行
        if (isExclude(request.getPath().toString())){
            return chain.filter(exchange);
        }
        // 3、获取token
        String token = null;
        List<String> headers = request.getHeaders().get("authorization");
        if (headers != null && !headers.isEmpty()){
            token = headers.get(0);
        }
        // 4、校验并解析token
        Long userId;
        try {
            userId = jwtTool.parseToken(token);
        } catch (Exception e) {
            // 解析token无效,拦截
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }

        // 5、传递用户信息给微服务
        String userInfo = userId.toString();
        ServerWebExchange swe = exchange.mutate()
                .request(builder -> builder.header("user-info", userInfo))
                .build();
        // 6、放行
        return chain.filter(swe);
    }

    private boolean isExclude(String antPath) {
        for (String excludePath : authProperties.getExcludePaths()) {
            if (antPathMatcher.match(excludePath, antPath)){
                return true;
            }
        }
        return false;
    }
}
4.1.3 下游微服务拦截器
  1. 在common模块中定义给下游微服务使用的拦截器:

    • 创建微服务拦截器
    • 拦截请求获取用户信息
    • 保存到ThreadLocal后放行
    java 复制代码
    package com.hmall.common.interceptor;
    
    
    import cn.hutool.core.util.StrUtil;
    import com.hmall.common.utils.UserContext;
    import org.springframework.web.servlet.HandlerInterceptor;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    /**
     * @author Peter Pang
     */
    public class UserInfoInterceptor implements HandlerInterceptor {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            // 1、获取请求头中的用户信息
            String userInfo = request.getHeader("user-info");
            // 2、判断用户信息是否为空,若不为空,则保存用户信息
            if (StrUtil.isNotBlank(userInfo)){
                UserContext.setUser(Long.valueOf(userInfo));
            }
            // 3、放行
            return true;
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            // 请求处理完成,清除用户信息
            UserContext.removeUser();
        }
    }

    工具类UserContext保存用户信息到ThreadLocal:

    java 复制代码
    package com.hmall.common.utils;
    
    public class UserContext {
        private static final ThreadLocal<Long> tl = new ThreadLocal<>();
    
        /**
         * 保存当前登录用户信息到ThreadLocal
         * @param userId 用户id
         */
        public static void setUser(Long userId) {
            tl.set(userId);
        }
    
        /**
         * 获取当前登录用户信息
         * @return 用户id
         */
        public static Long getUser() {
            return tl.get();
        }
    
        /**
         * 移除当前登录用户信息
         */
        public static void removeUser(){
            tl.remove();
        }
    }
  2. SpringMVC配置类配置登录拦截器:

    java 复制代码
    package com.hmall.common.config;
    
    import com.hmall.common.interceptor.UserInfoInterceptor;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.DispatcherServlet;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    /**
     * @author Peter Pang
     */
    @Configuration
    @ConditionalOnClass(DispatcherServlet.class)
    public class MvcConfig implements WebMvcConfigurer {
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new UserInfoInterceptor());
        }
    }
  3. 将拦截器添加到自动装配名单中:

    apl 复制代码
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
      com.hmall.common.config.MyBatisConfig,\
      com.hmall.common.config.MvcConfig,\ # 添加全限定类名
      com.hmall.common.config.JsonConfig

    需要注意,这个配置类默认是不会生效的,因为它所在的包是com.hmall.common.config,与其它微服务的扫描包不一致,无法被扫描到,因此无法生效。

    基于SpringBoot的自动装配原理,我们要将其添加到resources目录下的META-INF/spring.factories文件中。

4.2 OpenFeign微服务间用户信息传递

4.2.1 问题分析
  • OpenFeign发起的请求不会自动携带网关传递的用户信息
  • 需要手动传递用户信息到请求头
4.2.2 解决方案

在api模块配置Feign后置拦截器:

com.hmall.api.config.DefaultFeignConfig中添加一个Bean。

java 复制代码
package com.hmall.api.config;

import com.hmall.common.utils.UserContext;
import feign.Logger;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Bean;

public class DefaultFeginConfig {
    @Bean
    public Logger.Level feignLogLevel(){
        return Logger.Level.FULL;
    }

    @Bean
    public RequestInterceptor userInfoRequestInterceptor(){
        return new RequestInterceptor() {
            @Override
            public void apply(RequestTemplate requestTemplate) {
                // 获取用户信息
                Long userId = UserContext.getUser();
                if (userId == null){
                    return;
                }
                // 将用户信息传递给下一个微服务
                requestTemplate.header("user-info", userId.toString());
            }
        };
    }
}
4.2.3 自动装配配置

配置@EnableFeignClients启用自定义Feign配置

java 复制代码
@EnableFeignClients(basePackages = "com.hmall.api.client", defaultConfiguration = DefaultFeginConfig.class)

defaultConfiguration = DefaultFeginConfig.class启用自定义Feign配置

5. 核心要点总结

5.1 网关层职责

  • 统一入口和路由转发
  • 身份认证和权限校验
  • 用户信息解析和传递

5.2 信息传递链路

复制代码
网关(JWT校验) → 添加用户信息到请求头 → 下游服务(拦截器读取) → ThreadLocal存储

5.3 微服务间调用

  • 通过Feign拦截器自动传递用户信息
  • 保证用户上下文在调用链中的一致性

5.4 最佳实践

  1. 网关集中认证:避免每个微服务重复校验
  2. 请求头传递:使用请求头传递用户信息
  3. ThreadLocal管理:确保线程安全并及时清理
  4. Feign自动传播:实现微服务间无缝传递
相关推荐
川Princess2 小时前
【面试经验】百度Agent架构研发工程师一面
面试·职场和发展·架构·agent
Tezign_space3 小时前
技术方案|构建品牌KOS内容中台:三种架构模式与AI赋能实践
人工智能·架构·数字化转型·小红书·kos·内容营销·内容科技
e***95644 小时前
springboot项目架构
spring boot·后端·架构
平凡之大路5 小时前
【企业架构】TOGAF架构标准规范-实现治理
架构·togaf
g***86696 小时前
springcloud-eureka与gateway简易搭建
spring cloud·eureka·gateway
q***65696 小时前
Nginx反向代理出现502 Bad Gateway问题的解决方案
运维·nginx·gateway
喵了几个咪6 小时前
Golang微服务框架kratos实现Socket.IO服务
开发语言·微服务·golang
鹏北海6 小时前
从“版本号打架”到 30 秒内提醒用户刷新:一个微前端团队的实践
前端·面试·架构
喜欢你,还有大家7 小时前
DaemonSet && service && ingress的
linux·架构·kubernetes