SpringCloud實戰之路 | 應用篇(六)網關組件Gateway及其過濾器
網關應用
Gateway工作流程
Spring Cloud Gateway是基於Reactor模型異步非阻塞的。
客戶端向Spring Cloud Gateway發送請求。如果在Gateway Handler Mapping中找到與請求匹配的路由,則將其發送到Gateway Web Handler。Handler通過指定的過濾器鏈來將請求發送到我們實際的服務執行業務邏輯。然後返回。過濾器之間用虛線分庫是因爲過濾器可能會在發送代理請求前(pre)或後(post)執行業務邏輯。
- Filter在pre時: 可以做參數校驗、權限校驗、限流、日誌輸出等
- Filter在post時: 可以做響應內容、響應頭的修改、日誌輸出、溜了監控等
代碼實現
構建Gateway服務搭建
引入maven依賴
<?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>
<artifactId>cloud-gateway-server</artifactId>
<!--spring boot 父啓動器依賴-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--GateWay 網關-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--引入webflux-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!--日誌依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<!--測試依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--lombok工具-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
<scope>provided</scope>
</dependency>
<!--引入Jaxb,開始-->
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>2.2.11</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.2.11</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.2.10-b140310.1920</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
<!--引入Jaxb,結束-->
<!-- Actuator可以幫助你監控和管理Spring Boot應用-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--熱部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<!--Config 客戶端依賴-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
<!--鏈路追蹤-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<!--spring cloud依賴版本管理-->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<!--編譯插件-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>11</source>
<target>11</target>
<encoding>utf-8</encoding>
</configuration>
</plugin>
<!--打包插件-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
創建入口啓動類
package com.cloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class GateWayApplication {
public static void main(String[] args) {
SpringApplication.run(GateWayApplication.class,args);
}
}
創建配置文件yml
server:
port: 9002
eureka:
client:
serviceUrl: # eureka server的路徑
defaultZone: http://LagouEurekaServerA:8761/eureka,http://LagouEurekaServerB:8762/eureka
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
spring:
application:
name: cloud-gateway-server
cloud:
gateway:
routes: # 路由可以有多個
- id: service-code-router
uri: lb://cloud-service-code
predicates:
- Path=/api/code/**
filters:
- StripPrefix=1
- id: service-email-router
uri: lb://cloud-service-email
predicates:
- Path=/api/email/**
filters:
- StripPrefix=1
- id: service-user-router #自定義的路由id 要保證唯一
uri: lb://cloud-service-user #lb表示從註冊中心獲取的需要轉發的服務名稱
#路由的規則
#例如訪問localhost:9002/api/user/login,這裏會路由到localhost:8080/user/login/
predicates:
- Path=/api/user/**
filters: # 可以去掉api之後轉發
- StripPrefix=1
GateWay過濾器
Spring Cloud Gateway過濾器分爲兩種GlobalFilter(應用到全部路由)和GateWayFilter(應用到單個路由)
GlobalFilter
基於自定義過濾器實現全局的黑白名單
@Slf4j
@Component // 讓容器掃描到,等同於註冊了
public class BlackListFilter implements GlobalFilter, Ordered {
// 模擬⿊名單(實際可以去數據庫或者redis中查詢)
private static List<String> blackList = new ArrayList<>();
static {
blackList.add("0:0:0:0:0:0:0:1"); // 模擬本機地址
}
/**
* 過濾器核⼼⽅法
* @param exchange 封裝了request和response對象的上下⽂
* @param chain ⽹關過濾器鏈(包含全局過濾器和單路由過濾器)
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 思路:獲取客戶端ip,判斷是否在⿊名單中,在的話就拒絕訪問,不在的話就放⾏
// 從上下⽂中取出request和response對象
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
// 從request對象中獲取客戶端ip
String clientIp = request.getRemoteAddress().getHostString();
// 拿着clientIp去⿊名單中查詢,存在的話就決絕訪問
if(blackList.contains(clientIp)) {
// 決絕訪問,返回
response.setStatusCode(HttpStatus.UNAUTHORIZED); // 狀態碼
log.debug("=====>IP:" + clientIp + " 在⿊名單中,將被拒絕訪問!");
String data = "Request be denied!";
DataBuffer wrap = response.bufferFactory().wrap(data.getBytes());
return response.writeWith(Mono.just(wrap));
}
// 合法請求,放⾏,執⾏後續的過濾器
return chain.filter(exchange);
}
/**
* 返回值表示當前過濾器的順序(優先級),數值越⼩,優先級越⾼
*/
@Override
public int getOrder() {
return 0;
}
}
GateWayFilter
針對某一個路由實現token信息驗證
@Slf4j
public class TokenFilter implements GatewayFilter, Ordered {
/**
* 過濾器核心方法
* @param exchange 封裝了request和response對象的上下文
* @param chain 網關過濾器鏈(包含全局過濾器和單路由過濾器)
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
List<HttpCookie> cookieList = request.getCookies().get("token");
if (cookieList != null && cookieList.size() >0) {
return chain.filter(exchange);
}
response.setStatusCode(HttpStatus.UNAUTHORIZED);
String data = "token fail!!!";
DataBuffer wrap = response.bufferFactory().wrap(data.getBytes());
return response.writeWith(Mono.just(wrap));
}
/**
* 返回值表示當前過濾器的順序(優先級),數值越小,優先級越高
* @return
*/
@Override
public int getOrder() {
return 0;
}
}
@Configuration
public class RouteLocatorConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder
.routes()
.route(r -> r.path("/api/user/info/**")
.filters(f -> f.stripPrefix(1).filter(new TokenFilter()))
.uri("lb://cloud-service-user")
.order(0)
.id("service-user-router"))
.build();
}
}