微服务的网关配置
1. 网关路由
1.1 网关
1.1.1 存在问题
单体架构时我们只需要完成一次用户登录、身份校验,就可以在所有业务中获取到用户信息。而微服务拆分后,每个微服务都独立部署,这就存在一些问题:每个微服务都需要编写身份校验、用户信息获取的接口,非常麻烦。
用户身份校验最好放在一个统一的地方,例如:网关。
1.1.2 认识网关
顾明思议,网关就是网 络的关 口。数据在网络间传输,从一个网络传输到另一网络时就需要经过网关来做数据的路由和转发以及数据安全的校验。
实现 java 微服务网关的技术:
- Netflix Zuul:早期实现,目前已经淘汰
- Spring Cloud Gateway:基于 Spring 的 WebFlux 技术,完全支持响应式编程,吞吐能力更强
Spring Cloud Gateway 使用参考官网
使用 Spring Cloud Gateway 实现网关,如下图:
前端请求网关根据请求路径路由到微服务,网关从 nacos 获取微服务实例地址将请求转发到具体的微服务实例上。
生产环境中网关也是集群部署,在网关前边通过 nginx 进行负载均衡,如下图:
1.2 实现网关路由
1.2.1 配置
创建网关工程;
引入依赖
xml
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<!-- common:通用工具服务,有则添加 -->
<dependency>
<groupId>com.hmall</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>
创建启动类
java
package com.hmall.gateway;
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
修改配置文件
yaml
server:
port: 8080
spring:
application:
name: gateway
cloud:
nacos:
server-addr: 192.168.101.68:8848
gateway:
routes:
- id: item # 以 item-service 举例,根据实际修改
uri: lb://item-service
predicates:
- Path=/items/**
路由规则 routes 包括四个属性,定义语法如下:
id
:路由的唯一标示predicates
:路由断言,Predicates 是用于判断请求是否满足特定条件的组件。filters
:路由过滤条件,稍后讲解。uri
:路由目标地址,lb://
代表负载均衡,从注册中心获取目标微服务的实例列表,并且负载均衡选择一个访问。-
:用于表示数组
1.2.2 配置项
predicates
属性,也就是路由断言。Spring Cloud Gateway 中支持的断言类型有很多:
名称 | 说明 |
---|---|
Cookie | 请求必须包含某些 cookie |
Header | 请求必须包含某些 header |
Host | 请求必须是访问某个host(域名) |
Method | 请求方式必须是指定方式 |
Path | 请求路径必须符合指定规则 |
weight | 权重处理 |
常用的类型就是 Header、Path,Header 的使用如 - Header=X-Request-Id, \d+
,说明请求头中包含属性 X-Request-Id
,且对应的值为数字。
2. 网关鉴权
2.1 认识网关鉴权
单体架构时我们只需要完成一次用户登录、身份校验,就可以在所有业务中获取到用户信息。而微服务拆分后,每个微服务都独立部署,不再共享数据。也就意味着每个微服务都需要做身份校验,这显然不可取。
我们的登录是基于 JWT 来实现的,校验 JWT 的算法复杂,而且需要用到密钥。如果每个微服务都去做身份校验,这就存在着两大问题:
- 每个微服务都需要知道 JWT 的密钥,不安全。
- 每个微服务重复编写身份校验代码、权限校验代码,代码重复不易维护。
网关鉴权是指在网关对请求进行身份验证的过程。这个过程确保只有经过授权的用户或设备才能访问特定的服务或资源。
流程如下:
- 用户登录成功生成 token 并存储在前端
- 前端携带 token 访问网关
- 网关解析 token 中的用户信息,网关将请求转发到微服务,转发时携带用户信息
- 微服务从 http 头信息获取用户信息
- 微服务之间远程调用使用内部接口(无状态接口)
2.2 网关内置过滤器
网关过滤器链中的过滤器有两种:
GatewayFilter
:路由过滤器,作用范围比较灵活,可以是任意指定的路由Route
。GlobalFilter
:全局过滤器,作用范围是所有路由,不可配置。
FilteringWebHandler
在处理请求时,会将 GlobalFilter
装饰为 GatewayFilter
,然后放到过滤器链中,排序以后依次执行。
Gateway
中内置了很多的 GatewayFilter
,详情可以参考官方文档:
常用的过滤器:StripPrefix
yaml
router
- id: product
uri: lb://item-service
predicates:
- Path=/product/**
filters:
- StripPrefix=1
StripPrefix=1
表示去除一级路径前缀,使用 StripPrefix=1
后
请求:http://localhost:8080/product/items
经过网关转换后,实际请求路径:http://localhost:8081/items
2.3 自定义过滤器
2.3.1 GlobalFilter 过滤器
java
package com.hmall.gateway.filter;
@Component
@Slf4j
public class PrintAnyGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 编写过滤器逻辑
log.info("打印全局过滤器");
// 放行
return chain.filter(exchange);
// 拦截
// ServerHttpResponse response = exchange.getResponse();
// 修改状态码
// response.setRawStatusCode(401);
// return response.setComplete();
}
@Override
public int getOrder() {
// 过滤器执行顺序,值越小,优先级越高
return 0;
}
}
全局过滤器不用在路由中配置。
java
package com.hmall.gateway.config;
// 配置拦截路径
@Data
@ConfigurationProperties(prefix = "hm.auth")
public class AuthProperties {
private List<String> includePaths;
private List<String> excludePaths;
}
2.3.2 GatewayFilter 过滤器
自定义 GatewayFilter
不是直接实现 GatewayFilter
,而是继承 AbstractGatewayFilterFactory
。
注意 :该类的名称一定要以
GatewayFilterFactory
为后缀!
java
package com.hmall.gateway.filter;
@Component
@Slf4j
public class FirstFilterGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {
@Override
public GatewayFilter apply(Object config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange,GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
log.info("请求路径:{}",request.getPath());
log.info("网关过滤器FirstFilterGatewayFilterFactory执行啦...");
// 放行
return chain.filter(exchange);
// 拦截 返回401状态码
// exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
// return exchange.getResponse().setComplete();
}
};
}
}
路由过滤器需要在配置中配置过滤器:
yaml
- id: product
uri: lb://item-service
predicates:
- Path=/product/**
filters:
- StripPrefix=1
- FirstFilter # 此处直接以自定义的 GatewayFilterFactory 类名称前缀类声明过滤器
2.3.3 总结
两种自定义过滤器的方式:
-
GatewayFilter
:路由过滤器作用范围比较灵活,可以是任意指定的路由
Route
.继承
AbstractGatewayFilterFactory
,并在路由配置中指定过滤器。过滤器的名称规则:以
GatewayFilterFactory
作为后缀。 -
GlobalFilter
:全局过滤器作用范围是所有路由,不可配置。
实现 GlobalFilter 接口。
实现 Ordered 接口可以指定过滤器顺序,实现
getOrder()
方法,返回值越小优先级越好。
2.4 身份校验过滤器
2.4.1 引入 JWT 工具类
./
│
├── com.*.gateway/
│ │
│ ├── config/
│ │ ├── AuthProperties.java # 配置身份校验需要拦截的路径
│ │ ├── JwtProperties.java # 定义与JWT工具有关的属性,比如秘钥文件位置
│ │ └── SecurityConfig.java # 工具的自动装配
│ │
│ └── util/
│ └── JwtTool.java # JWT工具,其中包含了校验和解析 token 的功能
│
└── resources/
│
└── hmall.jks # 秘钥文件
2.4.2 配置白名单
其中 AuthProperties
和 JwtProperties
所需的属性要在 application.yaml
中配置
yaml
hm:
jwt:
location: classpath:hmall.jks # 秘钥地址
alias: hmall # 秘钥别名
password: hmall123 # 秘钥文件密码
tokenTTL: 30m # 登录有效期
auth:
excludePaths: # 无需身份校验的路径
- /search/**
- /users/login
- /items/**
excludePaths 配置白名单地址,即无需身份校验的路径。
2.4.3 身份校验过滤器
./
│
└── com.*.gateway/
└── filter/
└── AuthGlobalFilter.java
2.5 总结
网关身份校验过滤器怎么实现?
我们项目中网关身份校验过滤器使用 Spring cloud Gateway 的 GlobalFilter 实现,GlobalFilter 是一种全局过滤器。实现过程如下:
- 配置密钥、白名单 等相关信息。
- 编写身份校验过滤器,实现 GlobalFilter 接口。
- 首先判断请求地址是否是白名单地址,如果是则放行
- 取出 http 头中的 token,然后校验 token 的合法性
- 如果 token 合法则将 token 中的用户信息向下传给微服务
- 如果 token 不合法则拒绝访问。