微服务之网关、网关路由、网关登录校验

简介:来源:SpringCloud微服务开发与实战,java黑马商城项目微服务实战开发(涵盖MybatisPlus、Docker、MQ、ES、Redis高级等)

认识网关

前端请求不能直接访问微服务,而是要请求网关:

  • 网关可以做安全控制,也就是登录身份校验,校验通过才放行
  • 通过认证后,网关再根据请求判断应该访问哪个微服务,将请求转发过去
    在这里插入图片描述
    在SpringCloud当中,提供了两种网关实现方案:
  • Netflix Zuul:早期实现,目前已经淘汰
  • SpringCloudGateway:基于Spring的WebFlux技术,完全支持响应式编程,吞吐能力更强。
    SpringCloudGateway官网:https://spring.io/projects/spring-cloud-gateway#learn
    在这里插入图片描述

网关路由-快速入门

接下来,我们先看下如何利用网关实现请求路由。由于网关本身也是一个独立的微服务,因此也需要创建一个模块开发功能。大概步骤如下:

  • 创建网关微服务
  • 引入SpringCloudGateway、NacosDiscovery依赖
  • 编写启动类
  • 配置网关路由

1、创建项目
首先,我们要创建一个新的module,比如命名为gateway,作为网关微服务:

2、引入依赖
在gateway模块的pom.xml文件中引入依赖

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

3、编写启动类
在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);
    }
}

4、配置路由
接下来,在gateway模块的resources目录新建一个application.yaml文件,内容如下:

server:
  port: 8080
spring:
  application:
    name: gateway
  cloud:
    nacos:
      server-addr: 192.168.254.129:8848
    gateway:
      routes:
        - id: item # 路由规则id,自定义,唯一
          uri: lb://item-service # 路由的目标服务,lb代表负载均衡,会从注册中心拉取服务列表
          predicates: # 路由断言,判断当前请求是否符合当前规则,符合则路由到目标服务
            - Path=/items/**,/search/** # 这里是以请求路径作为判断规则
        - id: cart
          uri: lb://cart-service
          predicates:
            - Path=/carts/**
        - id: user
          uri: lb://user-service
          predicates:
            - Path=/users/**,/addresses/**
        - id: trade
          uri: lb://trade-service
          predicates:
            - Path=/orders/**
        - id: pay
          uri: lb://pay-service
          predicates:
            - Path=/pay-orders/**

网关路由-路由属性

网关路由对应的Java类型是RouteDefinition,其中常见的属性有

  • id:路由唯一标示
  • uri:路由目标地址
  • predicates: 路由断言,判断请求是否符合当前路由
  • filters:路由过滤器,对请求或响应做特殊处理

Spring Cloud Gateway官网:https://docs.spring.io/spring-cloud-gateway/docs/3.1.9/reference/html/

1、路由断言
12种Route Predicate Factories实现示例
在这里插入图片描述
2、路由过滤器
33种路由过滤器官网实现示例
在这里插入图片描述
在default-filters下设置的过滤器对所有路由生效:

spring:
  cloud:
    gateway:
      routes:
        - id: item
          uri: lb://item-service
          predicates:
            - Path=/items/**,/search/**
      default-filters:
        - AddRequestHeader=truth, anyone long-press like button will be rich

网关登录校验-思路分析

在这里插入图片描述
在这里插入图片描述

注意:NettyRoutingFilter是做请求转发的,所以应该在NettyRoutingFilter之前且在过滤器的pre逻辑之前进行登录校验

网关登录校验-自定义过滤器

在这里插入图片描述
自定义GlobalFilter过滤器

进行登录校验一般都选择自定义GlobalFilter过滤器

在这里插入图片描述
在这里插入图片描述
自定义GagtewayFilter过滤器

自定义GagtewayFilter过滤器比较麻烦,大多数情况都选择自定义GlobalFilter过滤器

自定义GatewayFilter不是直接实现GatewayFilter,而是实现AbstractGatewayFilterFactory。最简单的方式是这样的:

@Component
public class PrintAnyGatewayFilterFactory 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();
                // 编写过滤器逻辑
                System.out.println("过滤器执行了");
                // 放行
                return chain.filter(exchange);
            }
        };
    }
}

注意:该类的名称一定要以GatewayFilterFactory为后缀!

然后在yaml配置中这样使用:

spring:
  cloud:
    gateway:
      default-filters:
            - PrintAny # 此处直接以自定义的GatewayFilterFactory类名称前缀类声明过滤器

另外,这种过滤器还可以支持动态配置参数,不过实现起来比较复杂,示例:

@Component
public class PrintAnyGatewayFilterFactory // 父类泛型是内部类的Config类型
                extends AbstractGatewayFilterFactory<PrintAnyGatewayFilterFactory.Config> {

    @Override
    public GatewayFilter apply(Config config) {
        // OrderedGatewayFilter是GatewayFilter的子类,包含两个参数:
        // - GatewayFilter:过滤器
        // - int order值:值越小,过滤器执行优先级越高
        return new OrderedGatewayFilter(new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                // 获取config值
                String a = config.getA();
                String b = config.getB();
                String c = config.getC();
                // 编写过滤器逻辑
                System.out.println("a = " + a);
                System.out.println("b = " + b);
                System.out.println("c = " + c);
                // 放行
                return chain.filter(exchange);
            }
        }, 100);
    }

    // 自定义配置属性,成员变量名称很重要,下面会用到
    @Data
    static class Config{
        private String a;
        private String b;
        private String c;
    }
    // 将变量名称依次返回,顺序很重要,将来读取参数时需要按顺序获取
    @Override
    public List<String> shortcutFieldOrder() {
        return List.of("a", "b", "c");
    }
        // 返回当前配置类的类型,也就是内部的Config
    @Override
    public Class<Config> getConfigClass() {
        return Config.class;
    }

}

然后在yaml文件中使用:

spring:
  cloud:
    gateway:
      default-filters:
            - PrintAny=1,2,3 # 注意,这里多个参数以","隔开,将来会按照shortcutFieldOrder()方法返回的参数顺序依次复制

上面这种配置方式参数必须严格按照shortcutFieldOrder()方法的返回参数名顺序来赋值。
还有一种用法,无需按照这个顺序,就是手动指定参数名:

spring:
  cloud:
    gateway:
      default-filters:
            - name: PrintAny
              args: # 手动指定参数名,无需按照参数顺序
                a: 1
                b: 2
                c: 3

网关登录校验-网关实现登录校验(网关到微服务的用户传递)

整体实现思路:
在这里插入图片描述

一、在网关的登录校验过滤器中,将从token解析出来的用户Id写入请求头并传递到微服务:
需求:修改gateway模块中的登录校验拦截器,在校验成功后保存用户到下游请求的请求头中
提示: 要修改转发到微服务的请求,需要用到ServerWebExchange类提供的API,示例如下:

package com.hmall.gateway.filter;

import com.hmall.common.exception.UnauthorizedException;
import com.hmall.common.utils.CollUtils;
import com.hmall.gateway.config.AuthProperties;
import com.hmall.gateway.utils.JwtTool;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
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.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.List;

@Component
@RequiredArgsConstructor
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    @Autowired
    private AuthProperties authProperties;

    @Autowired
    private JwtTool jwtTool;

    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1、获取request
        ServerHttpRequest request = exchange.getRequest();
        //2、判断是否需要做登录拦截(即判断此请求路径是否在hm.auth的排除路径里)(排除路径就是:不需要登录就可以访问的路径)
        if (isExclude(request.getPath().toString())){
            //放行
            return chain.filter(exchange);
        }

        //3、获取token
        String token = null;
        List<String> headers = request.getHeaders().get("authorization");
        if (!CollUtils.isEmpty(headers)){
            token = headers.get(0);
        }
        //4、校验并解析token
        Long userId = null;
        try {
            userId = jwtTool.parseToken(token);
        }catch (UnauthorizedException e){
            //拦截,设置响应状态码为401
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }

        //TODO 5、传递用户信息
        String userInfo = userId.toString();
        ServerWebExchange swe = exchange.mutate()
                .request(builder -> builder.header("user-info", userInfo))
                .build();
        //6、放行
        return chain.filter(swe);
    }

    private boolean isExclude(String path) {
        for (String pathPattern : authProperties.getExcludePaths()) {
            if (antPathMatcher.match(pathPattern,path)){
                return true;
            }
        }
        return false;
    }

    @Override
    public int getOrder() {
        //过滤器执行顺序,值越小,优先级越高
        return 0;
    }
}

二、在common(公共模块)中编写SpringMVC拦截器,获取登录用户:
需求:由于每个微服务都可能有获取登录用户的需求,因此我们直接在common公共模块定义拦截器,这样微服务只需要引入依赖即可生效,无需重复编写。

拦截器类实现:

package com.hmall.common.interceptors;

import cn.hutool.core.util.StrUtil;
import com.hmall.common.utils.UserContext;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

//只用来获取登录用户的信息,不需要做登录拦截,因为真正的登录拦截校验已经在网关做过了
public class UserInfoInterceptor implements HandlerInterceptor {
    @Override//preHandle是在Controller之前执行
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1.获取请求头中的用户信息
        String userInfo = request.getHeader("user-info");
        // 2.判断是否为空
        if (StrUtil.isNotBlank(userInfo)) {//StrUtil.isNotBlank(userInfo):判断userInfo字符串不能为null且不能为空
            // 不为空,保存到ThreadLocal
            UserContext.setUser(Long.valueOf(userInfo));//UserContext是ThreadLocal工具类,方便调用
        }
        // 3.放行
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 移除用户
        UserContext.removeUser();//在Controller执行完后一定要清理用户
    }
}

ThreadLocal工具类实现:

package com.hmall.common.utils;

public class UserContext {
    private static final ThreadLocal<Long> tl = new ThreadLocal<>();

    /**
     * 保存当前登录用户信息到ThreadLocal
     * @param userId 用户id
     */
    public static void setUser(Long userId) {
        tl.set(userId);
    }

    /**
     * 获取当前登录用户信息
     * @return 用户id
     */
    public static Long getUser() {
        return tl.get();
    }

    /**
     * 移除当前登录用户信息
     */
    public static void removeUser(){
        tl.remove();
    }
}

注意:Spring MVC的拦截器要想生效,必须要进行配置,也就是需要注册
重点:SpringBoot自动装配原理:要想使在不同包路径下的Spring扫不到的那些配置类生效**,则需要在resource目录下的META-INF包下定义一个spring.factories文件去记录这些配置类
扩展: 在新版本的springboot中spring.factories文件名已经改了。

配置类实现:

package com.hmall.common.config;

import com.hmall.common.interceptors.UserInfoInterceptor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@ConditionalOnClass(DispatcherServlet.class)
public class MvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new UserInfoInterceptor());
    }
}

spring.factories文件实现:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.hmall.common.config.MyBatisConfig,\
  com.hmall.common.config.MvcConfig,\
  com.hmall.common.config.JsonConfig

可以遇到的问题与报错:
①因为此模块是common公共模块,在微服务中网关模块pom.xml也可能需要引入common公共模块依赖; ②又因为WebMvcConfigurer属于spring mvc包下的,而网关不是SpringMvc环境的,而是一种非阻塞式的响应式编程。
所以为了避免冲突,使此配置类只在微服务生效而不在网关生效 。
解决方法
关键:Springboot自动装配原理是可以带条件的。
方法:在配置类中加一个条件@ConditionalOnClass(DispatcherServlet.class)
详解:DispatcherServlet.class(SpringMvc的核心API)判断此类在此环境中是否存在,存在则生效;这样就可以使此配置类只在微服务生效而不在网关生效,避免了冲突。

知识扩展:
@ConditionalOnClass(DispatcherServlet.class) 注解是 Spring Boot 的条件化配置注解之一。它的作用是使得该配置类(MvcConfig)只在 DispatcherServlet 类存在于类路径中时才会被加载。这意味着:

  1. 条件化加载:如果项目中没有 DispatcherServlet 类,Spring Boot 就不会加载这个配置类。通常,DispatcherServlet 是 Spring MVC 的核心组件,因此这个条件常用于确保该配置只在启用了 Spring MVC 的环境下生效。

  2. 用于模块化:通过这种方式,你可以确保配置类只在需要时才会被创建,从而减少不必要的配置和开销。

  3. 增强灵活性:这种做法使得应用更具灵活性,因为你可以在不同的环境中选择性地加载不同的配置。
    总结:
    @ConditionalOnClass(DispatcherServlet.class) 的使用在这里确保只有在有 Spring MVC 环境时,相关的拦截器和配置才会被应用,避免在不需要 MVC 功能的情况下进行不必要的配置。

网关登录校验-OpenFeign传递用户信息(微服务之间的用户传递)

在这里插入图片描述
在这里插入图片描述

总结

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

服务环境的搭建

一、基础环境搭建 1、python3 准备相关的jar包 Index of /ftp/python/3.7.9/ scp Python-3.7.9.tgz root192.168.1.245:/opt/dockerinstall/python3/ yum -y install gcc yum -y install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel…

【语音转文本新体验】Windows部署Whisper Web结合内网穿透轻松远程转录——“cpolar内网穿透”

文章目录 前言1.本地部署Whisper Web1.1 安装git1.2 安装Node.js1.3 运行项目 2. Whisper Web使用介绍3. 安装Cpolar内网穿透4. 配置公网地址5. 公网访问测试6. 配置固定公网地址 前言 OpenAI开源的 Whisper 语音转文本模型效果都说还不错&#xff0c;今天就给大家推荐 GitHub…

[A-14]ARMv8/ARMv9-Memory-内存模型的类型(Device Normal)

ver0.1 [看前序文章有惊喜。] 前言 前面花了很大的精力把ARM构建的VMSA中的几个核心的议题给大家做了介绍,相信大家已经能够理解并掌握ARM的内存子系统的工作原理大致框架。接下来我们会规划一些文章,对ARM内存子系统的一些细节做一下介绍,使ARM的内存子系统更加的丰满。本…

JetBrains IDE中GPU进程(JCEF)重启问题(Too many restarts of GPU-process)解决方案

目录 前言1. GPU进程重启问题概述1.1 什么是GPU进程重启问题&#xff1f;1.2 该问题带来的影响 2. GPU进程重启问题的原因分析2.1 显卡驱动的兼容性问题2.2 系统资源的限制2.3 JCEF组件的设置不合理 3. 解决方案3.1 方法一&#xff1a;通过自定义属性禁用GPU加速3.2 方法二&…

NVR批量管理软件/平台EasyNVR多个NVR同时管理支持UDP和TCP传输协议

随着科技的飞速发展&#xff0c;视频技术已成为现代社会不可或缺的一部分&#xff0c;广泛应用于安防监控、娱乐传播、在线教育、电商直播等多个领域。在这一背景下&#xff0c;NVR管理平台EasyNVR作为一款高效、灵活的视频监控管理系统&#xff0c;正经历着前所未有的发展机遇…

医学影像基础:常见的医学影像学术语和概念

目录 1. 基本影像术语 2. X射线相关术语 3. CT相关术语 4. MRI相关术语 5. 超声相关术语 6. 核医学相关术语 7. 影像质量和技术术语 8. 临床影像术语 总结 在医学影像学中&#xff0c;有许多术语和概念是常用且重要的。了解这些术语和概念有助于更好地理解影像报告、与…

稀土抗紫外屏蔽剂在化妆品上的应用

稀土抗紫外屏蔽剂的作用原理 稀土元素具有独特的电子结构&#xff0c;能够吸收和散射紫外线。其作用主要有以下几个方面&#xff1a; 1. 吸收紫外线&#xff1a;稀土元素可以吸收特定波长的紫外线&#xff0c;将其能量转化为热能或其他形式的能量&#xff0c;从而减少紫外线对…

2024 前端面试题!!! html css js相关

常见的块元素、行内元素以及行内块元素&#xff0c;三者有何不同&#xff1f;​​​​​​​ HTML、XML、XHTML它们之间有什么区别&#xff1f;​​​​​​​ DOCTYPE(⽂档类型) 的作⽤ Doctype是HTML5的文档声明&#xff0c;通过它可以告诉浏览器&#xff0c;使用哪一个HTM…

Android Studio安装完成后,下载gradle-7.4-bin.zip出现连接超时

文章目录 问题原因&#xff1a;因为下载镜像是谷歌&#xff0c;属于外网&#xff0c;不好正常连接&#xff0c;下载依赖。解决方法&#xff1a;找到gradle-wrapper.properties文件&#xff0c;修改镜像&#xff0c;如下图&#xff0c;然后再单击try again重新下载。 问题原因&a…

IntelliJ IDEA 2024社区版保姆及图文安装教程

1.下载 这里我们安装的是2024.2.3版本IntelliJ IDEA Community Edition 下载地址:https://www.jetbrains.com/idea/download/other.html 如果下载不便可以使用本文的网盘资源下载 2.安装 打开软件点击下一步 在安装过程中&#xff0c;我们可以选择安装的目录&#xff0c;也…

【二叉树进阶题目2】(算法题详细简介)

【二叉树进阶题目2】&#xff08;算法题详细简介&#xff09; 前言5. 二叉树的层序遍历 ||&#xff08;力扣107&#xff09;5.1 题目链接5.2 示例分析5.3 代码实现 6. 二叉树的层序遍历&#xff08;力扣102)6.1 题目链接6.2 代码实现 7. 根据二叉树创建字符串&#xff08;力扣6…

基于Pyecharts的数据可视化开发(二)调用通义千问api分析爬虫数据

上一篇博客做了关于“广州市2023年天气情况”的数据爬取&#xff0c;并保存为.csv文件。下一步是想用生成的.csv文件&#xff0c;直接调用大模型api进行分析&#xff0c;得出结论。通过调研&#xff0c;阿里云的通义千问大模型qwen-long可以实现对文件数据的分析。 通义千问大模…

SD-WAN:低延迟的国际网络连接方案

在全球化的商业环境中&#xff0c;企业业务正不断扩展至全球市场&#xff0c;急需实现高效、稳定的跨国网络连接。然而&#xff0c;传统的国际组网方式往往会遇到高延迟、低带宽和管理复杂等难题&#xff0c;导致企业运营效率下降并影响用户体验。本文将介绍SD-WAN如何解决这些…

WebRTC VAD 详解与代码示例

WebRTC VAD 详解与代码示例 WebRTC VAD的工作原理WebRTC VAD的代码示例总结WebRTC VAD(Voice Activity Detection,语音活动检测)是一种用于检测音频流中是否存在语音活动的技术。在实时通信系统中,VAD技术能够显著减少带宽消耗并优化系统资源利用,特别是在WebRTC这类实时音…

在macOS的多任务处理环境中,如何平衡应用的性能与用户体验?这是否是一个复杂的优化问题?如何优化用户体验|多任务处理|用户体验|应用设计

目录 一 多任务处理与应用性能 1. macOS中的多任务处理机制 2. 性能优化的基本策略 二 用户体验的关键要素 1. 响应速度 2. 界面友好性 3. 功能的直观性 三 平衡性能与用户体验的策略 1. 资源管理 2. 优化数据加载 3. 使用合适的线程模型 4. 实时监测和调整 四 使…

防静电监控系统为汽车电子工厂打造安全生产环境

汽车电子产品对静电极其敏感&#xff0c;微小的静电放电 (ESD) 都会导致元器件损坏&#xff0c;造成巨大的经济损失和产品质量问题。因此&#xff0c;在汽车电子工厂构建完善的ESD防静电防护体系至关重要。传统的防静电措施主要依赖人工巡检&#xff0c;效率低且难以保证实时监…

2024 AFS-46 电子数据存在性鉴定(移动终端)(2024能力验证)

一、委托事项 1.给出检材手机的MEID。 2.给出检材手机在2024年7月3日上午连接过的设备名称。 3.给出检材手机中kimi应用最近一次被中断回答的问题内容。 4.给出检材手机中安装过的即时通讯应用的包名&#xff08;不包含虚拟机中的应用&#xff09;。 5.检材手机中安装有几…

数据结构之双链表——考研笔记

文章目录 一.单链表VS双链表二.创建双链表&#xff08;带头结点&#xff09;三.双链表的插入四.双链表删除五.销毁双链表六.双链表遍历七. 循环链表八.静态链表1.用代码定义一个静态链表 一.单链表VS双链表 单链表中只包含指向它后继结点的指针&#xff0c;所以给定一个结点p找…

AI 原生时代,更要上云:百度智能云云原生创新实践

本文整理自百度云智峰会 2024 —— 云原生论坛的同名演讲。 我今天分享的主题&#xff0c;是谈谈在云计算和 AI 技术快速发展和深入落地的背景下&#xff0c;百度智能云在云原生的基础设施产品和技术层面做的一些创新实践。 毋庸置疑&#xff0c;过去十几年云计算和 AI 技术是…

商场紧急情况处理:SpringBoot技术实践

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常…