网关

简介: 网关

介绍

资料

官网https://spring.io/projects/spring-cloud-gateway

https://jihulab.com/xuxiaowei-cloud/xuxiaowei-cloud/-/tree/main/gateway?ref_type=heads

网关缘由

单体变成微服务后,需要一个统一的入口,类似门窗(网关只对浏览器过来的请求拦截,feign之间的调用也需要添加feign的全局拦截器)

按照现在主流使用微服务架构的特点,假设现在有A、B、C三个服务,假如这三个服务都需要做一些请求过滤和权限校验,请问怎么实现?

  • 每个服务自己实现登录
  • 写在一个公共的服务,然后让A、B、C服务引入公共服务的Maven依赖,但是和第一种一样需要校验
  • 使用服务网关,所有客户端请求服务网关进行请求过滤和权限校验,然后再路由转发到A、B、C服务

网关和nginx

微服务网关能够做的事情,Nginx也可以实现。
相同点:都是可以实现对api接口的拦截,负载均衡、反向代理、请求过滤等,可以实现和网关一样的效果。

不同点:
Nginx采用C语言编写的
在微服务领域中,都是自己语言编写的,比如我们使用java构建微服务项目,Gateway就是java语言编写的。

毕竟Gateway属于Java语言编写的, 能够更好对微服务实现扩展功能,相比Nginx如果想实现扩展功能需要结合Nginx+Lua语言等。

Nginx实现负载均衡的原理:属于服务器端负载均衡器。
Gateway实现负载均衡原理:采用本地负载均衡器的形式。

加载顺序--常常导致注入的依赖为null

可以通过实现ApplicationContextAware接口,然后通过上下文获取

demo(不加注册和发现服务版)

首先根据自己的springboot项目找对应的spring cloud版本,(单独引用不知道行不行)

https://spring.io/projects/spring-cloud#learn

https://start.spring.io/actuator/info

pom.xml

<properties>
  <project.version>0.0.1-SNAPSHOT</project.version>
  <java.version>17</java.version>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  <spring-boot.version>3.1.0</spring-boot.version>
  <!--需要与springboot版本-->
  <!--https://spring.io/projects/spring-cloud#learn-->
  <!--https://start.spring.io/actuator/info-->
  <spring-cloud.version>2022.0.4</spring-cloud.version>
</properties>
<!-- 依赖声明 -->
<dependencyManagement>
    <dependencies>
        <!-- Spring Boot 的依赖项管理,没有继承属性和插件-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring-boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>de.codecentric</groupId>
            <artifactId>spring-boot-admin-dependencies</artifactId>
            <version>${spring-boot-admin.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<dependencies>
    <!-- Spring Cloud Gateway -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
</dependencies>
<build>
    <!--包名-->
    <!--        <finalName>${project.artifactId}</finalName>-->
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>${spring-boot.version}</version>
            <!--替换为你的应用程序主类-->
            <configuration>
                <mainClass>com.cabin.gateway.GatewayApplication</mainClass>
                <excludes>
                    <exclude>
                        <groupId>org.projectlombok</groupId>
                        <artifactId>lombok</artifactId>
                    </exclude>
                </excludes>
            </configuration>
            <executions>
                <execution>
                    <id>repackage</id>
                    <goals>
                        <goal>repackage</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

启动类

com.cabin.gateway.GatewayApplication

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

方法一:yaml实现gateway

我的服务的接口是http://127.0.0.1:8080/util/test

那么gateway的yaml

server:
  port: 9090
spring:
  cloud:
    gateway:
      routes:
        - id: app           # 路由的唯一标识
          uri: http://localhost:8080  # 如果断言成功,将要转发去的地址
          order: 0                    # 优先级,越小优先级越高
          predicates:                 # 断言,满足所有断言,才会进行转发
            - Path=/app/**        # 注意:这是使用= 不是:
          filters:
            - StripPrefix=1 # #转发请求时去掉1级前缀,去掉端口后的第一个节点,这里是client

开启gateway和test的服务后访问gateway的路由

http://127.0.0.1:9090/app/util/test

方法二:代码实现gateway

yaml只需要开启服务

server:
  port: 9090

配置类,如何自定义Filter

package com.cabin.gateway.config;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class GatewayConfig {
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
        .route("login_route", r -> r.path("/login")
               .uri("http://your-login-service/login"))
        .route("app_route", r -> r.path("/app/**")
               .filters(f ->
                        f.stripPrefix(1)
                        // f.filter(new AuthFilter()).stripPrefix(1) //如果自定义Filter
                       )
               .uri("http://localhost:8080"))
        .build();
    }
}

filter

如果你的gateway路由是代码注入的,不是yaml配置的,一般都会指定一个filter

GatewayFilter

package com.cabin.gateway.config;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class AuthFilter implements GatewayFilter {
    // 黑名单列表,也可以放在缓存数据库里
    private static final String START_TIME = "startTime";
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        Mono<Void> noLogin = Filter.loginLogic(exchange);
        if (noLogin != null) return noLogin;
        Mono<Void> blackUser = Filter.blackLoginLogic(exchange);
        if (blackUser != null) return blackUser;
        // 下面3行代码在前过滤器pre filter执行
        String url = exchange.getRequest().getURI().getPath();
        System.out.println("ip来源: " + "这里从nginx获取ip握手时的ip");
        System.out.println("请求地址:" + url);
        System.out.println("入参:" + exchange.getRequest().getQueryParams().toString());
        // exchange的getAttributes可以存放信息
        exchange.getAttributes().put(START_TIME, System.currentTimeMillis());
        // chain.filter里面的逻辑相当于后过滤器post filter
        return chain.filter(exchange).then(
            Mono.fromRunnable(() -> {
                Long startTime = exchange.getAttribute(START_TIME);
                if (startTime != null) {
                    System.out.println(url + "耗时:" +
                                       (System.currentTimeMillis() - startTime) + "ms");
                }
            })
        );
        // 如果放行不需要日志
        //        return chain.filter(exchange);
    }
}

Filter的方法你可以自定义

package com.cabin.gateway.config;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.List;
/**
 * @author 伍六七
 * @date 2023/8/12 20:42
 */
public class Filter {
    static List<String> blackList = Arrays.asList("123", "456");
    /**
     * 黑名单
     *
     * @return 为null表示非黑名单
     */
    public static Mono<Void> blackLoginLogic(ServerWebExchange exchange) {
        // 用户id
        String id = exchange.getRequest().getHeaders().getFirst("id");
        // blackList可以做持久化
        if (blackList.contains(id)) {
            // 如果是黑名单就直接返回,不再往目标服务器转发
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return exchange.getResponse().setComplete();
        }
        return null;
    }
    /**
     * 登录逻辑 <br/>
     *
     * @return 为null表示登录成功
     */
    public static Mono<Void> loginLogic(ServerWebExchange exchange) {
        // 在这里编写登录检测逻辑
        // 如果登录有效,调用chain.filter(exchange)继续处理请求
        // 如果登录无效,可以返回未授权的响应或重定向到登录页面
        String token = null;
        HttpCookie tokenCookie = exchange.getRequest().getCookies().getFirst("token");
        if (tokenCookie != null) {
            token = tokenCookie.getValue();
        }
        // 功能1.拿到token判断是否已经登录
        if (!isLogin(token)) {
            // 如果没有登录则重定向都登录页面
            exchange.getResponse().setStatusCode(HttpStatus.FOUND);
            exchange.getResponse().getHeaders().set("Location", "/login");
            return exchange.getResponse().setComplete();
        }
        // 如果判断成功了放行
        System.out.println("登录成功");
        return null;
    }
    public static boolean isLogin(String token) {
        return true;
    }
}

使用时

package com.cabin.gateway.config;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class GatewayConfig {
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("login_route", r -> r.path("/login")
                        .uri("http://your-login-service/login"))
                .route("app_route", r -> r.path("/app/**")
                        .filters(f ->
                                f.filter(new AuthFilter())
                        )
                        .uri("http://localhost:8080"))
                .build();
    }
}

GlobalFilter

一般搭配Ordered,这个filter使用@Component注入即可,即可生效,对于所有路由

package com.cabin.gateway.config;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {
    private static final String START_TIME = "startTime";
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 下面3行代码在前过滤器pre filter执行
        String url = exchange.getRequest().getURI().getPath();
        System.out.println("ip来源: " + "这里从nginx获取ip握手时的ip");
        System.out.println("请求地址:" + url);
        System.out.println("入参:" + exchange.getRequest().getQueryParams().toString());
        // exchange的getAttributes可以存放信息
        exchange.getAttributes().put(START_TIME, System.currentTimeMillis());
        // chain.filter里面的逻辑相当于后过滤器post filter
        return chain.filter(exchange).then(
                Mono.fromRunnable(() -> {
                    Long startTime = exchange.getAttribute(START_TIME);
                    if (startTime != null) {
                        System.out.println(url + "耗时:" +
                                (System.currentTimeMillis() - startTime) + "ms");
                    }
                })
        );
    }
    @Override
    public int getOrder() {
        return 0;
    }
}
相关实践学习
部署高可用架构
本场景主要介绍如何使用云服务器ECS、负载均衡SLB、云数据库RDS和数据传输服务产品来部署多可用区高可用架构。
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
目录
相关文章
|
安全 Linux 网络安全
什么是VPN网关?
VPN网关是一款基于Internet的网络连接服务,通过加密通道的方式实现企业数据中心、企业办公网络或Internet终端与阿里云专有网络(VPC)安全可靠的连接。VPN网关提供IPsec-VPN连接和SSL-VPN连接。
1602 0
|
1天前
|
运维 Java 应用服务中间件
整合服务网关gateway
整合服务网关gateway
6 0
|
3月前
|
负载均衡 安全 网络协议
网关知识总结
网关知识总结
192 0
|
3月前
|
弹性计算 运维 监控
NAT网关介绍
NAT网关介绍
33 1
|
8月前
|
负载均衡 Java Nacos
Gateway 网关服务
Gateway 网关服务
106 0
|
负载均衡 Java API
Gateway服务网关
Gateway服务网关
1053 0
|
弹性计算 网络安全 网络架构
如何通过nat网关实现不同VPC间的互通
如何通过nat网关实现不同VPC间的互通
|
缓存 运维 监控
这么重要的网关,你还不需要么
这么重要的网关,你还不需要么
226 0
这么重要的网关,你还不需要么
|
Java API 微服务
Gateway服务网关(二)
Gateway服务网关
106 0
|
负载均衡 监控 Java
服务网关介绍|学习笔记
快速学习服务网关介绍
203 0
服务网关介绍|学习笔记