Directorio de artículos
I. Introducción
Hasta ahora, se ha publicado la serie de artículos de puerta de enlace de microservicio:
- [Cloud Native & Microservices > SCG Gateway Part 1] Por qué hay una puerta de enlace y cómo elegir una puerta de enlace en un entorno de producción
- Cloud Native y Microservices > SCG Gateway Part 2] Esos métodos de liberación en escala de grises en producción
- [Cloud Native & Microservices > SCG Gateway Part 3] ¿Qué es Spring Cloud Gateway y casos de uso detallados?
- Cloud Native & Microservices > SCG Gateway Part 4] Cómo usar las 11 PredicateFactorys integradas en Spring Cloud Gateway
- [Cloud Native & Microservices > SCG Gateway Part 5] Spring Cloud Gateway Custom PredicateFactory
- [Cloud Native & Microservices > SCG Gateway Chapter 6] 18 posturas de uso de filtros integradas en Spring Cloud Gateway
- [Cloud Native & Microservices > SCG Gateway Chapter 6] Spring Cloud Gateway implementa la limitación, la fusión y el reintento actuales en función de los filtros integrados
Se trataron los siguientes temas:
- ¿Por qué tener una puerta de enlace? ¿Cuál es el papel de una puerta de enlace?
- ¿Clasificación de las puertas de enlace?
- ¿Selección de tecnología de puerta de enlace?
- ¿Cuáles son los métodos de publicación en escala de grises comúnmente utilizados cuando se utilizan puertas de enlace?
- ¿Qué es Spring Cloud Gateway? ¿Caso de uso detallado?
- 11 tipos de PredicateFactory integrados en Spring Cloud Gateway
- ¿Cómo personalizar PredicateFactory?
- 18 filtros de uso común integrados en Spring Cloud Gateway
- Spring Cloud Gateway implementa la limitación, la fusión y el reintento actuales en función de los filtros integrados
Luego, este artículo habla sobre cómo Spring Cloud Gateway personaliza Filter y GlobalFilter
PD: información de la versión de SpringCloud:
<properties>
<spring-boot.version>2.4.2</spring-boot.version>
<spring-cloud.version>2020.0.1</spring-cloud.version>
<spring-cloud-alibaba.version>2021.1</spring-cloud-alibaba.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--整合spring cloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--整合spring cloud alibaba-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2. Filtro personalizado
GatewayFilterFactory solo puede actuar en una sola ruta Ruta. Lo siguiente personaliza un filtro para implementar la función de registro;
1. Heredar de AbstractNameValueGatewayFilterFactory para implementar la función de registro
1> AbstractNameValueGatewayFilterFactory子类MyLogNameValueGatewayFilterFactory:
package com.saint.gateway.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractNameValueGatewayFilterFactory;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
/**
* 统一日志记录Filter
* 继承自AbstractNameValueGatewayFilterFactory,内置了两个参数:name和value;
*
* @author Saint
*/
@Component
@Slf4j
public class MyLogNameValueGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {
@Override
public GatewayFilter apply(NameValueConfig config) {
return ((exchange, chain) -> {
// Filter的前置处理Prev
log.info("MyLogGatewayFilterFactory [Pre] Filter Request, config.getName() = " + config.getName());
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
// 在then方法中是请求执行结束之后的后置处理,即Filter的POST
log.info("MyLogGatewayFilterFactory [Post] Response Filter");
}));
});
}
}
2 > Configurar Filtro para Ruta en application.yml:
server:
port: 9999
spring:
cloud:
gateway:
routes:
- id: add_request_parameter_route
uri: http://127.0.0.1:9001
predicates:
- Path=/**
filters:
# 自定义过滤器的名字,即:MyLogNameValueGatewayFilterFactory
- name: MyLogNameValue
args:
name: Saint_name
value: Saint_value
3> prueba:
Interfaz de acceso: http://127.0.0.1:9999/hello/sayHello
, vea la salida de la consola del centro de la puerta de enlace de la siguiente manera:
2. Heredar de AbstractGatewayFilterFactory para implementar la función de registro
Cuando queremos especificar algunos otros parámetros para Filter, podemos hacerlo heredándolos de AbstractGatewayFilterFactory.
1> subclase AbstractGatewayFilterFactory MyLogGatewayFilterFactory:
package com.saint.gateway.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
/**
* 统一日志记录Filter
* AbstractGatewayFilterFactory,可以自定义参数;
*
* @author Saint
*/
@Component
@Slf4j
public class MyLogGatewayFilterFactory extends AbstractGatewayFilterFactory<MyLogGatewayFilterFactory.MyConfig> {
public MyLogGatewayFilterFactory() {
super(MyConfig.class);
}
@Override
public GatewayFilter apply(MyConfig config) {
return ((exchange, chain) -> {
// Filter的前置处理Prev
log.info("MyLogGatewayFilterFactory [Pre] Filter Request, config.getName() = " + config.getName());
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
// 在then方法中是请求执行结束之后的后置处理,即Filter的POST
log.info("MyLogGatewayFilterFactory [Post] Response Filter");
}));
});
}
/**
* MyConfig只是一个配置类,该类中只有一个属性name。这个属性可以在yml文件中使用
*/
public static class MyConfig {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
2 > Configurar Filtro para Ruta en application.yml:
server:
port: 9999
spring:
cloud:
gateway:
routes:
- id: add_request_parameter_route
uri: http://127.0.0.1:9001
predicates:
- Path=/**
filters:
# 自定义过滤器的名字,即:MyLogGatewayFilterFactory
- name: MyLog
args:
name: Saint_log_name
3> prueba:
Interfaz de acceso: http://127.0.0.1:9999/hello/sayHello
, vea la salida de la consola del centro de la puerta de enlace de la siguiente manera:
3. Personalizar GlobalFilter
GlobalFilter actuará en todas las rutas de enrutamiento. El siguiente es un GlobalFilter personalizado para implementar la función de autenticación en el nivel de la puerta de enlace;
1. Personalice GlobalFilter para implementar la función de autenticación
1> GlobalFilter implementa la clase AuthFilter:
package com.saint.gateway.filter;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpCookie;
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.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.List;
/**
* 对所有路由生效的GlobalFilter,鉴权使用
*
* @author Saint
*/
@Component
public class AuthFilter implements GlobalFilter, Ordered {
private static final Logger logger = LoggerFactory.getLogger(AuthFilter.class);
@Value("#{'${auth.skip.urls:}'.split(',')}")
private List<String> skipAuthUrls;
/**
* 存放token信息的请求头属性
*/
public static final String COOKIE_NAME_TOKEN = "saint_token";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1.获取请求对象
ServerHttpRequest request = exchange.getRequest();
//2.获取响应对象
ServerHttpResponse response = exchange.getResponse();
//3.判断 是否需要直接放行
logger.info("uri: {}", request.getURI());
if (skipAuthUrls.contains(request.getURI().toString())) {
return chain.filter(exchange);
}
//4 校验
//4.1 从头header中获取令牌数据
String token = request.getHeaders().getFirst(COOKIE_NAME_TOKEN);
if (StringUtils.isEmpty(token)) {
//4.2 从cookie中中获取令牌数据
HttpCookie first = request.getCookies().getFirst(COOKIE_NAME_TOKEN);
if (first != null) {
token = first.getValue();//就是令牌的数据
}
}
if (StringUtils.isEmpty(token)) {
//4.3 从请求参数中获取令牌数据
token = request.getQueryParams().getFirst(COOKIE_NAME_TOKEN);
}
if (StringUtils.isEmpty(token)) {
//4.4. 如果没有数据 没有登录,要重定向到登录到页面
response.setStatusCode(HttpStatus.FORBIDDEN);//403
// 4.5. 在响应头里添加鉴权失败信息
response.getHeaders().set("auth_error", "xxxx");
return response.setComplete();
}
//TODO 5 解析令牌数据 ( 判断解析是否正确,正确 就放行 ,否则 结束)
try {
logger.info("authed user info: {}", "*****");
} catch (Exception e) {
logger.error("parse token error:", e);
//解析失败
response.setStatusCode(HttpStatus.FORBIDDEN);
return response.setComplete();
}
// 6 将token添加到头信息,传递给后续的服务
request.mutate().header(COOKIE_NAME_TOKEN, token);
return chain.filter(exchange);
}
/**
* 这里表示将当前Filter放在过滤链的最前面
*
* @return
*/
@Override
public int getOrder() {
return 0;
}
}
AuthFilter no solo implementa GlobalFilter para filtrar todas las rutas, sino que también implementa la
Ordered
interfaz y reescribe el método getORder() para devolver 0, lo que indica que el AuthFilter actual se ejecuta primero.
2> Introduzca commons-lang3 en pom.xml:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
3> En el archivo application.yml, configure la URI sin autenticación:
auth:
skip:
urls: http://localhost:9001/service-a/hello, http://localhost:9001/service-b/hello
4. Resumen
En este artículo, hablamos sobre tres formas de personalizar Filter/GlobalFilter, a saber: heredar AbstractNameValueGatewayFilterFactory, heredar AbstractGatewayFilterFactory e implementar la interfaz GlobalFilter. A continuación, hablemos de la integración de Spring Cloud Gateway y Nacos.