Spring Cloud Gateway的使用

news2025/7/15 13:25:51

Spring Cloud Gateway网关

  • Spring Cloud Gateway
    • 三大核心概念
      • Route(路由)
      • Predicate(断言)
      • Filter(过滤)
    • 开始使用
      • 动态路由
        • 配置路由
      • 断言
      • 过滤器
    • 实现Token+IP验证拦截

Spring Cloud Gateway

网关:微服务中最边缘的服务,用来做用户和微服务的桥梁

  • 没有网关❓:客户端直接访问我们的微服务,会需要在客户端配置很多的ip和端口,如果服务器并发比较大则无法完成负载均衡
  • 有网关❓:客户端访问网关,网关来访问微服务,(网关可以和注册中心整合,通过服务名称找到目标的ip:port)这样只需要使用服务名称即可访问微服务,可以实现负载均衡,可以实现token拦截,权限验证,等等操作…

网关组件:gatwayzuul

  • gatway是springcloud官方提供的,用于取代zuul
    在这里插入图片描述

核心逻辑:路由转发+执行过滤器链

三大核心概念

Route(路由)

eureka结合做动态路由

组成:

  • 由一个ID、一个URL、一组断言工厂、一组Filter组成
  • 如果路由断言成真,说明请求URL和配置路由匹配

Predicate(断言)

其实就是一个返回true,false的表达式

用于匹配信息做路由限制的

Filter(过滤)

Spring Cloud Gateway中的Filter分为两种类型的Filter,分别是Gateway Filter和Global Filter。过滤器Filter将会对请求和响应进行修改处理

  • 一个是针对某一个路由的filter(例如对单个接口做限制)
  • 一个是针对全局的filter(例如全局效验token)

开始使用

创建两个模块,一个login-service,一个gateway-server

  • login-service:暂时使用springweb依赖即可
  • gateway-server:暂时使用gateway依赖即可

pom.xml中依赖(例如jar)的版本与前期教程版本号一致

先编写一个接口在login-service

LoginController.java:

@RestController
public class LoginController {
    @GetMapping("doLogin")
    public String doLogin(String name,String pwd){
        System.out.println(name+"密码:"+pwd);
        String token= UUID.randomUUID().toString();
        return token;
    }
}

动态路由

  • 配置方式的路由
  • 代码方式的路由

两者不会冲突

准备依赖

login-service:

多一个eureka-client依赖

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.12.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.pengke</groupId>
    <artifactId>login-service02</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>login-service02</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.SR12</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

gateway-server:

也是多一个eureka-client依赖

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

启动类统一添加启动客户端服务注解

@EnableEurekaClient

配置路由

在login-service模块中进行配置文件配置

server:
  port: 8081
spring:
  application:
    name: login-service
eureka:
  client:
    service-url:
      defaultZone: eureka远程服务端地址
    registry-fetch-interval-seconds: 3 # 网关拉取服务列表的时间缩短
  instance:
    hostname: localhost
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}

在gateway-server模块中进行配置文件配置

server:
  port: 80
spring:
  application:
    name: gateway-server
  cloud:
    gateway:
      enabled: true
      discovery:
        locator:
          enabled: true # 开启动态路由
          lower-case-service-id: true # 开启服务名称小写
      routes:
          -   id: login-service-route # 这个是路由id,保持唯一即可
              uri: http://localhost:8081 # uri:统一资源定位符  url:统一资源标识符
#             uri: lb://login-service # 使用lb协议微服务名称做负载均衡
              predicates: # 断言是给某个路由设定的
                - Path=/doLogin # 匹配规则 只要Path匹配上/doLogin就往uri转发,并且将路径带上
eureka:
  client:
    service-url:
      defaultZone: eureka远程服务端地址
    registry-fetch-interval-seconds: 3 # 网关拉取服务列表的时间缩短
  instance:
    hostname: localhost
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}

启动两个服务,在浏览器中输入localhost/doLogin或者localhost:8081/doLogin或者localhost/login-service/doLogin都可实现访问

代码方式的路由:

gateway-server模块中创建config---》RouteConfig

  • .route(“路由id”,函数式访问路径.uri(请求根路径)).build()
@Configuration
public class RouteConfig {
    /**
     * 代码路由
     * 和配置形式路由不冲突
     * @param builder
     * @return
     */
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("test-id",r->r.path("/m_xiaozhilei").uri("https://blog.csdn.net"))
                .build();
    }
}

启动服务浏览器访问http://localhost/gateway-server/m_xiaozhilei或者http://localhost/m_xiaozhilei都可访问到最终请求的页面

这样就用会了动态路由!

断言

在配置文件中配置predicates属性

  • 断言是给某个路由设定的

在这里插入图片描述

主要有以上这些限制

例如:

 predicates:
	- After=2023-03-22T09:42:49.521+08:00[Asia/Shanghai]

即可实现请求时间在2023-03-22…之后请求才可以访问成功…

过滤器

按生命周期分两种

  • pre:在业务逻辑前
  • post:在业务逻辑后

按种类分也是两种

  • GatewayFilter:需要配置某个路由才能过滤,如果需要使用全局路由,需要配置Defilters
  • GlobalFilter:全局过滤器,不需要配置路由,系统初始化作用到所有路由上

在网关gatewayserver模块创建过滤器

filter=》MyGlobalFilter.java

/**
 * 定义过滤器
 */
@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {

    /**
     * 过滤方法
     * 过滤器链模式
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //针对请求的过滤
        ServerHttpRequest req=exchange.getRequest();
        String path=req.getURI().getPath();
        HttpHeaders header=req.getHeaders();
        String methods=req.getMethod().name();
        String host=req.getRemoteAddress().getHostName();
        String ip=req.getHeaders().getHost().getHostString();
        System.out.println(path+"header="+header+"方法="+methods+"host="+host+"ip="+ip);

        //响应的数据
        ServerHttpResponse resp=exchange.getResponse();
        resp.getHeaders().set("content-type","application/json;charset=utf-8");
        Map map=new HashMap();
        map.put("code",500);
        map.put("msg","token有误");
        ObjectMapper objectMapper=new ObjectMapper();
        //将map转字节
        byte[] bytes=new byte[0];
        try {
            bytes=objectMapper.writeValueAsBytes(map);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        //通过buffer工厂将字节数组包装成一个数据包
        DataBuffer warp=resp.bufferFactory().wrap(bytes);
        if(1>0){//判断一下(例如判断token是否正确)
            return resp.writeWith(Mono.just(warp));
        }else{
            //放行到下一个过滤器
            return chain.filter(exchange);
        }
    }

    /**
     * 指定顺序的方法
     * 越小越先执行
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }
}

实现GlobalFilter和Ordered的方法

  • 过滤器中chain.filter(exchange)表示放行
  • 过滤器获取相关参数在上方代码已示例
  • ordered中getOrder方法用来指定过滤器的执行顺序

通过上方即可实现过滤器,再次访问页面时则会返回{msg:token有误,coed:500}

实现Token+IP验证拦截

首先简单实现一下IP拦截,常用于黑名单,白名单

  • 在网关中定义一个IP拦截过滤器
@Component
public class IPcheckFilter implements GlobalFilter, Ordered {
    /**
     * 网关的并发比较高  不要在网关里直接操作数据库
     */
    public static final List<String> BLACK_LIST= Arrays.asList("127.0.0.1","144.125.231.14");
    /**
     * 拿到ip进行校验决定是否拦截
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request=exchange.getRequest();
        String ip=request.getHeaders().getHost().getHostString();
        //这里查数据库获取黑名单或者白名单进行相关操作

        if(!BLACK_LIST.contains(ip)){
            //不是黑名单放行
            return chain.filter(exchange);
        }
        //拦截
        ServerHttpResponse response=exchange.getResponse();
        response.getHeaders().set("context-type","application/json;charset=utf-8");
        HashMap<String,Object> map=new HashMap<>(4);
        map.put("code",438);
        map.put("msg","黑名单禁止访问");
        ObjectMapper objectMapper=new ObjectMapper();
        byte[] bytes= new byte[0];
        try {
            bytes = objectMapper.writeValueAsBytes(map);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        DataBuffer wrap=response.bufferFactory().wrap(bytes);
        return response.writeWith(Mono.just(wrap));
    }

    @Override
    public int getOrder() {
        return -5;
    }
}

代码如上,思路解析:

  • 定义一个常量用于存储黑名单ip或者白名单ip,这里我存的是黑名单ip(业务中常常存与临时存储处,这里演示我就定个变量去存储)
  • 通过.contains进行判断是否存在
  • 通过chain.filter(exchange)进行放行
  • 如果验证失败则可返相关信息告知访问者

开始实现token拦截验证

这里使用redis进行存储token(不会redis可查阅我另俩篇文章redis的开始使用--------->redis与springboot整合)

引入redis依赖

<!--        redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

在上方doLogin接口中存储登陆者的信息以及为它生成的token,这里我创了个User实体类用于模拟真实登陆场景

 @Autowired
    public StringRedisTemplate redisTemplate;
    @GetMapping("doLogin")
    public String doLogin(String name,String pwd){
        System.out.println(name+"密码:"+pwd);
        User user=new User(1,name,pwd,20);
        //token
        String token= UUID.randomUUID().toString();
        //存在radis
        redisTemplate.opsForValue().set(token,user.toString(), Duration.ofSeconds(7200));
        return token;
    }

在网关中编写token拦截接口

//指定放行路径
    public static final List<String> ALLOW_URL= Arrays.asList("/login-service/doLogin","/myUrl");
    @Autowired
    private StringRedisTemplate redisTemplate;
    /**
     * 约定好请求头携带 Authorization value:bearer token
     * - 拿到url效验决定是否需要token效验
     * - 拿到请求头
     * - 拿到token效验
     * - 决定是否放行
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request=exchange.getRequest();
        String path=request.getURI().getPath();
        if(ALLOW_URL.contains(path)){
            return chain.filter(exchange);//放行不进行token效验
        }
        HttpHeaders headers=request.getHeaders();
        List<String> authorization=headers.get("Authorization");
        if(!CollectionUtils.isEmpty(authorization)){
            //携带authorization了
            String token=authorization.get(0);
            if(StringUtils.hasText(token)){
                //约定好的前缀 bearer token
                String realToken=token.replaceFirst("bearer ","");
                if(StringUtils.hasText(realToken)&&redisTemplate.hasKey(realToken)){
                    //真携带了token
                    return chain.filter(exchange);
                }
            }

        }
        //拦截
        ServerHttpResponse response=exchange.getResponse();
        response.getHeaders().set("context-type","application/json;charset=utf-8");
        HashMap<String,Object> map=new HashMap<>(4);
        map.put("code",403);
        map.put("msg","无权限访问");
        ObjectMapper objectMapper=new ObjectMapper();
        byte[] bytes= new byte[0];
        try {
            bytes = objectMapper.writeValueAsBytes(map);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        DataBuffer wrap=response.bufferFactory().wrap(bytes);
        return response.writeWith(Mono.just(wrap));
    }

首先指定放行非需要token的请求,例如登陆接口!!!

大概验证流程为代码中的注释

通过redisTemplate.hasKey(token)去匹对是否有该token来决定是否允许访问该接口

最终就实现了一个基本的业务拦截需求了~✌

下一篇讲解实现redis限流!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/369559.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python使用VTK对容积超声图像进行体绘制(三维重建)

目录VTK简介什么是体绘制&#xff1f;体绘制效果图流程CodeQ&AReferenceVTK简介 VTK&#xff08;Visualization Toolkit&#xff09;是一个用于3D计算机图形学、图像处理和可视化的开源软件包。它包括一组C类和工具&#xff0c;可以让用户创建和处理复杂的3D图形和数据可视…

论文投稿指南——中文核心期刊推荐(音乐)

【前言】 &#x1f680; 想发论文怎么办&#xff1f;手把手教你论文如何投稿&#xff01;那么&#xff0c;首先要搞懂投稿目标——论文期刊 &#x1f384; 在期刊论文的分布中&#xff0c;存在一种普遍现象&#xff1a;即对于某一特定的学科或专业来说&#xff0c;少数期刊所含…

超纯水制备,MB-106UP抛光树脂的技术解析

超纯水&#xff08;Ultrapure water&#xff09;又称UP水&#xff0c;是指电阻率达到18 MΩ*cm&#xff08;25℃&#xff09;的水。这种水中除了水分子外&#xff0c;几乎没有什么杂质&#xff0c;更没有细菌、病毒、含氯二噁英等有机物&#xff0c;当然也没有人体所需的矿物质…

低代码是什么意思?企业为什么要用低代码平台?

低代码是什么意思&#xff1f;企业为什么要用低代码平台&#xff1f; 这两个问题似乎困扰了很多人&#xff0c;总有粉丝跟小简抱怨&#xff0c;一天到晚念叨低代码&#xff0c;倒是来个人解释清楚啊&#xff01; 来了&#xff0c;这次一文让你全明白。 先解释这几个名词&…

mysql5.7.39数据库服务搭建(win10)

mysql下载下载地址&#xff1a;https://downloads.mysql.com/archives/community如上图&#xff0c;选择了mysql 5.7.39版本&#xff0c;64位Windows操作系统&#xff1b;然后下载ZIP Archive格式的安装文件&#xff0c;点击“Download” 按钮即可。下载好后&#xff0c;进行解…

kafka入门到精通

文章目录一、kafka概述&#xff1f;1.定义1.2消息队列1.2.1 传统消息队列的使用场景1.2.2 消息队列好处1.2.3 消息队列两种模式1.3 kafka基础架构二、kafka快速入门1.1使用docker-compose安装kafka1.2测试访问kafka-manager1.3 查看kafka版本号1.4 查看zookeeper版本号1.5 扩展…

win11右键新建菜单添加选项

需要操作 2 处注册表&#xff0c; 以下以在右键新建菜单中添加 .html 为例 在主键 HKEY_CLASSES_ROOT 中&#xff0c;搜索 .html 找到后 &#xff0c;右键点击它&#xff0c;选 新建 ->项&#xff0c; 在这里插入图片描述 项目名字是&#xff1a;ShellNew 新建后&#x…

【Linux学习笔记】4.Linux 文件基本属性及文件与目录管理

前言 本章介绍Linux的文件基本属性和文件与目录管理。 Linux 文件基本属性 Linux 系统是一种典型的多用户系统&#xff0c;不同的用户处于不同的地位&#xff0c;拥有不同的权限。 为了保护系统的安全性&#xff0c;Linux 系统对不同的用户访问同一文件&#xff08;包括目录…

Linux命令及CPU占用过高的定位分析思路

一、vim命令不要使用vim打开大文件&#xff0c;vim会一次性读取所有内容到内存&#xff0c;容易造成宿主机内存溢出。 打开文件前&#xff0c;可以使用du -h命令查看文件大小。一般&#xff0c;100MB以下为宜。1、普通模式j 向下30j 向下移动30行k 向上h 向左l 向右0 到行首^ 到…

3.15版本poi导致FileMagic文件找不到问题解决过程记录

maven中的dependencies和dependencyManagement的区别_shenzhou_yh的博客-CSDN博客 maven 中 dependencies 与 dependencyManagement 的区别_Jaemon的博客-CSDN博客_snapshot dependencies和artifact dependencies的区别 com.alibaba.excel.exception.ExcelAnalysisException: …

ubuntu/linux系统知识(37)systemd管理临时文件的方法systemd-tmpfiles

1、systemd-tmpfiles Linux产生大量的临时文件和目录&#xff0c;例如/tmp、/run 。systemd提供了一个结构化的可配置方法来管理临时文件和目录&#xff0c;即systemd-tmpfiles工具和配套的几个服务&#xff0c;以实现创建、删除和管理临时文件。 systemd创建了几个调用syste…

React(一):初识React、类组件、jsx的基础语法

React&#xff08;一&#xff09;一、初识React1.简单介绍2.React的三个依赖3.Hello React案例二、类组件1.定义类组件并渲染2.绑定事件函数&#xff08;奇怪的this问题&#xff09;3.数组形式数据的展示&#xff08;电影案例&#xff09;4.计数器案例三、jsx语法详解1.jsx的书…

【GUI】用于电动助力车性能分析的GUI(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5;&#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密…

202302-第四周资讯

山川软件愿为您提供最优质的服务。 您的每一个疑问都会被认真对待&#xff0c;您的每一个建议都将都会仔细思考。 我们希望人人都能分析大数据&#xff0c;人人都能搭建应用。 因此我们将不断完善我们的DEMO、文档、以及视频&#xff0c;期望能在最大程度上快速帮助用户快速…

最新OpenMVG编译安装与逐命令运行增量式和全局式SfM教程

openmvg是一个轻便的可以逐步运行的SfM开源库&#xff0c;它同时实现了增量式和全局式两种算法。 说明文档地址&#xff1a;https://openmvg.readthedocs.io/en/latest/ github主页地址&#xff1a;https://github.com/openMVG/openMVG 1 编译安装 openmvg的安装比较简单&…

【centos7下部署mongodb】

一.安装环境 CentOS7MongoDB4.0.13正式版。 二.下载MongoDB 1.1 官网下载地址&#xff1a;https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-4.0.13.tgz 1.2 将压缩包通过xftp上传到服务器/opt目录&#xff0c;然后解压、改名 三. 配置环境变量及配置文件 3.1配置系…

8000+字,就说一个字Volatile

简介 volatile是Java提供的一种轻量级的同步机制。Java 语言包含两种内在的同步机制&#xff1a;同步块&#xff08;或方法&#xff09;和 volatile 变量&#xff0c;相比于synchronized&#xff08;synchronized通常称为重量级锁&#xff09;&#xff0c;volatile更轻量级&…

2023最新版网络安全保姆级指南,手把手带你从零基础进阶渗透攻防工程师

前言 一份网络攻防渗透测试的学习路线&#xff0c;不藏私了&#xff01; 1、学习编程语言(phpmysqljshtml) 原因&#xff1a; phpmysql可以帮助你快速的理解B/S架构是怎样运行的&#xff0c;只有理解了他的运行原理才能够真正的找到问题/漏洞所在。所以对于国内那些上来就说…

Java实现在线沟通功能

文章目录1、介绍 和 特点2、整合SpringBoot2.1、导入依赖2.2、websocket 配置类2.3、消息处理类2.4、启动服务2.5、前端代码&#xff1a;张三2.6、前端代码&#xff1a;李四3、效果4、小结1、介绍 和 特点 t-io是基于JVM的网络编程框架&#xff0c;和netty属同类&#xff0c;所…

MES系统需求误区,一文告诉你需求分析有哪些

在企业的实际应用中&#xff0c;对MES系统需求的分析常常会出现六个错误。 要求广泛&#xff0c;目标不明确由于对MES系统的概念和企业的实际运作不了解&#xff0c;导致企业在提出MES系统的要求时&#xff0c;常常会笼统而不明确&#xff0c;有时会混淆目标和需要。比如&#…