Skip to content

Gateway 组件

1.1 认识网关

什么是网关?

网关是网络的关口,负责数据在不同网络间传输时的路由、转发和安全校验。它充当微服务架构中的统一入口,所有外部请求都需要先经过网关才能访问后端微服务。

网关的作用

  • 安全控制:进行身份认证和权限校验
  • 请求路由:根据规则将请求转发到对应的微服务
  • 负载均衡:在多个服务实例间分配请求
  • 限流熔断:保护后端服务不被过多请求压垮

SpringCloud 网关方案

  • Netflix Zuul:早期实现,目前已淘汰
  • SpringCloud Gateway:基于 WebFlux 的响应式网关,性能更优

1.2 快速入门

创建网关微服务

1. 创建项目模块

在 mall 项目下创建 hj-gateway 模块

2. 添加依赖

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">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.hj</groupId>
        <artifactId>mall</artifactId>
        <version>1.0.0</version>
    </parent>

    <artifactId>hj-gateway</artifactId>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencies>
        <!-- 公共模块 -->
        <dependency>
            <groupId>com.hj</groupId>
            <artifactId>hj-common</artifactId>
            <version>1.0.0</version>
        </dependency>

        <!-- SpringCloud Gateway -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

        <!-- Nacos 服务发现 -->
        <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>

        <!-- Spring Web MVC -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</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>

3. 创建启动类

java
package com.hj.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);
    }
}

4. 配置路由规则

yaml
server:
  port: 8080

spring:
  application:
    name: gateway
  cloud:
    nacos:
      server-addr: 192.168.12.168:8848
    gateway:
      routes:
        - id: item
          uri: lb://item-service
          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/**

5. 测试验证

启动网关和相关微服务,通过网关地址访问:

http://localhost:8080/items/page?pageNo=1&pageSize=1

1.3 路由过滤

路由配置结构

路由配置包含四个核心属性:

  • id:路由规则唯一标识
  • uri:目标服务地址,lb:// 表示负载均衡
  • predicates:路由断言,定义匹配条件
  • filters:路由过滤器,处理请求和响应

常用路由断言

断言类型说明示例
Path路径匹配- Path=/api/**
After在指定时间后生效- After=2023-01-20T17:42:47.789-07:00[America/Denver]
Before在指定时间前生效- Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai]
Between在时间区间内生效- Between=startTime,endTime
Cookie必须包含指定 Cookie- Cookie=sessionId, .*
Header必须包含指定 Header- Header=X-Request-Id, \d+
Method请求方法匹配- Method=GET,POST
Query必须包含指定参数- Query=token
RemoteAddrIP 地址匹配- RemoteAddr=192.168.1.1/24

内置过滤器示例

yaml
spring:
  cloud:
    gateway:
      routes:
        - id: item
          uri: lb://item-service
          predicates:
            - Path=/items/**,/search/**
          filters:
            - AddRequestHeader=Authorization,Bearer token123
            - AddRequestParameter=source,gateway

2.1 鉴权思路分析

微服务架构下的认证挑战

  • 每个微服务都需要重复实现登录校验
  • JWT 秘钥需要在多个服务中保存,存在安全风险
  • 代码重复,维护困难

网关统一认证方案

将登录校验前置到网关层:

  • 网关统一处理身份认证
  • 只有网关和用户服务持有 JWT 秘钥
  • 减少代码重复,提高安全性

2.2 网关过滤器

Gateway 工作原理

  1. 请求进入网关的 HandlerMapping
  2. 匹配到对应的路由规则 (Route)
  3. 交给 WebHandler 处理,加载过滤器链
  4. 按顺序执行过滤器的 pre 逻辑
  5. 路由到微服务
  6. 倒序执行过滤器的 post 逻辑
  7. 返回响应结果

过滤器类型

  • GatewayFilter:路由过滤器,作用于特定路由
  • GlobalFilter:全局过滤器,作用于所有路由

2.3 自定义过滤器

自定义 GlobalFilter

java
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
    private final JwtTool jwtTool;
    private final AuthProperties authProperties;
    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1. 获取请求信息
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getPath().toString();

        // 2. 检查是否在排除路径中
        if (isExcludePath(path)) {
            return chain.filter(exchange);
        }

        // 3. 获取并验证Token
        String token = getTokenFromHeader(request);
        try {
            Long userId = jwtTool.parseToken(token);
            // 4. 将用户信息添加到请求头
            ServerHttpRequest newRequest = request.mutate()
                    .header("user-info", userId.toString())
                    .build();
            return chain.filter(exchange.mutate().request(newRequest).build());
        } catch (Exception e) {
            // 5. Token验证失败,返回401
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }
    }

    private boolean isExcludePath(String path) {
        return authProperties.getExcludePaths().stream()
                .anyMatch(pattern -> antPathMatcher.match(pattern, path));
    }

    private String getTokenFromHeader(ServerHttpRequest request) {
        List<String> headers = request.getHeaders().get("authorization");
        return headers != null && !headers.isEmpty() ? headers.get(0) : null;
    }

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

配置类

java
@Data
@ConfigurationProperties(prefix = "hj.auth")
public class AuthProperties {
    private List<String> excludePaths = new ArrayList<>();
}
yaml
hj:
  auth:
    excludePaths:
      - /search/**
      - /users/login
      - /items/**

2.4 JWT 工具集成

添加 JWT 相关配置

yaml
hj:
  jwt:
    location: classpath:mall.jks
    alias: mall
    password: mall123
    tokenTTL: 30m

JWT 工具类

java
@Component
@EnableConfigurationProperties(JwtProperties.class)
public class JwtTool {
    private final JwtProperties properties;
    private final JwtParser jwtParser;

    public JwtTool(JwtProperties properties) {
        this.properties = properties;
        // 初始化JWT解析器
        this.jwtParser = Jwts.parserBuilder()
                .setSigningKey(getPublicKey())
                .build();
    }

    public Long parseToken(String token) {
        if (token == null || token.trim().isEmpty()) {
            throw new JwtException("Token不能为空");
        }

        try {
            Claims claims = jwtParser.parseClaimsJws(token).getBody();
            return claims.get("userid", Long.class);
        } catch (Exception e) {
            throw new JwtException("Token解析失败", e);
        }
    }

    private PublicKey getPublicKey() {
        // 从秘钥库加载公钥
        // 具体实现略...
    }
}

2.5 用户信息传递

微服务拦截器

java
public class UserInfoInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request,
                           HttpServletResponse response,
                           Object handler) {
        // 1. 从请求头获取用户信息
        String userInfo = request.getHeader("user-info");

        // 2. 保存到ThreadLocal
        if (StringUtils.hasText(userInfo)) {
            UserContext.setUser(Long.valueOf(userInfo));
        }

        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request,
                              HttpServletResponse response,
                              Object handler, Exception ex) {
        // 清理ThreadLocal
        UserContext.removeUser();
    }
}

自动配置

java
@Configuration
public class MvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new UserInfoInterceptor());
    }
}

META-INF/spring.factories 中注册:

properties
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.hj.common.config.MvcConfig

2.6 OpenFeign 用户传递

Feign 请求拦截器

java
public class DefaultFeignConfig {
    @Bean
    public RequestInterceptor userInfoRequestInterceptor() {
        return template -> {
            Long userId = UserContext.getUser();
            if (userId != null) {
                template.header("user-info", userId.toString());
            }
        };
    }
}

购物车业务逻辑

java
@Service
@RequiredArgsConstructor
public class CartServiceImpl implements ICartService {
    @Override
    public List<CartVO> queryMyCarts() {
        // 从ThreadLocal获取当前用户ID
        Long userId = UserContext.getUser();
        if (userId == null) {
            throw new RuntimeException("用户未登录");
        }

        // 查询该用户的购物车
        return cartMapper.listCarts(userId);
    }
}