Gateway微服务网关
1. Gateway微服务网关概述
核心功能
- 请求路由:将请求转发到对应的微服务
- 请求转发:作为所有微服务的统一入口
- 身份校验:统一进行身份认证和权限验证
- 负载均衡:配合服务发现实现负载均衡
- 限流熔断:保护后端微服务
2. 项目集成Gateway配置
2.1 创建Gateway服务模块步骤
- 创建独立模块:作为网关服务
- 新建启动类 :添加
@SpringBootApplication注解 - 引入依赖:添加Spring Cloud Gateway相关依赖
- 配置路由规则:定义路由转发规则
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 自定义过滤器实现
其实GatewayFilter和GlobalFilter这两种过滤器的方法签名完全一致:
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:秘钥文件
其中AuthProperties和JwtProperties所需的属性要在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 下游微服务拦截器
-
在common模块中定义给下游微服务使用的拦截器:
- 创建微服务拦截器
- 拦截请求获取用户信息
- 保存到ThreadLocal后放行
javapackage 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:
javapackage 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(); } } -
SpringMVC配置类配置登录拦截器:
javapackage 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()); } } -
将拦截器添加到自动装配名单中:
aplorg.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 最佳实践
- 网关集中认证:避免每个微服务重复校验
- 请求头传递:使用请求头传递用户信息
- ThreadLocal管理:确保线程安全并及时清理
- Feign自动传播:实现微服务间无缝传递