SpringCloud_Gateway服务网关

news2024/11/24 3:58:52

文章目录

  • 一、SpringCloudGateway服务网关概论
    • 1、SpringCloudGateway服务网关概论
    • 2、SpringCloudGateway的三大核心概念
  • 二、SpringCloudGateway的路由及断言
    • 1、子模块项目SpringCloudGateway的搭建
    • 2、SpringCloudGateway_Java API构建路由
    • 3、SpringCloudGateway的动态路由功能
    • 4、SpringCloudGateway的路由断言
  • 三、SpringCloudGateway的过滤器及跨域
    • 1、SpringCloudGateway的过滤器
    • 2、网关过滤器GatewayFilter
    • 3、自定义网关过滤器GatewayFilter
    • 4、自定义全局过滤器GlobalFilter
    • 5、内置全局过滤器
    • 6、服务网关Gateway实现跨域
  • 四、SpringCloudGateway实现用户鉴权
    • 1、JsonWebToken概论
    • 2、创建用户的微服务及登录操作
    • 3、服务网关Gateway实现用户鉴权
  • 总结

一、SpringCloudGateway服务网关概论

1、SpringCloudGateway服务网关概论

Spring Cloud Gateway 用"Netty + Webflux"实现,不需要导入Web依赖。

  1. Webflux模式替换了旧的Servlet线程模型。用少量的线程处理request和response io操作,这些线程称为Loop线程,而业务交给响应式编程框架处理,响应式编程是非常灵活的,用户可以将业务中阻塞的操作提交到响应式框架的work线程中执行,而不阻塞的操作依然可以在Loop线程中进行处理,大大提高了Loop线程的利用率。
    即Webflux中的Loop线程不仅可以处理请求和响应请求,还可以对业务中不阻塞的操作进行处理,从而提高它的利用率。阻塞的操作由work线程进行处理。
  2. Webflux虽然可以兼容多个底层的通信框架,但是一般情况下,底层使用的还是Netty,毕竟,Netty是目前业界认可的最高性能的通信框架。
    Netty 是一个基于NIO的客户、服务器端的编程框架。提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。
  3. Spring Cloud Gateway特点
    1)易于编写谓词( Predicates )和过滤器( Filters ) 。其Predicates和Filters
    可作用于特定路由。
    2)支持路径重写。
    3)支持动态路由。
    4)集成了Spring Cloud DiscoveryClient。

2、SpringCloudGateway的三大核心概念

  1. 路由(Route)
    这是网关的基本构建块。它由一个ID,一个目标URI,一组断言和一组过滤器定义。如果断言为真,则路由匹配。
    即根据URL请求去匹配路由。
  2. 断言(predicate)
    输入类型是一个ServerWebExchange。我们可以使用它来匹配来自HTTP请求的任何内容,例如headers或参数。匹配请求内容。
    匹配完路由后,每个路由上面都会有断言,然后根据断言来判断是否可以进行路由。
  3. 过滤(filter)
    在匹配完路由和断言为真后,可以在请求被路由前或者之后对请求进行修改。
    即根据业务对其进行监控,限流,日志输出等等。

二、SpringCloudGateway的路由及断言

1、子模块项目SpringCloudGateway的搭建

  1. 在cloud父项目中新建一个模块Module,创建子模块网关cloud-gateway-gateway9527

  2. 在POM文件中添加如下依赖

    <?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">
        <parent>
            <artifactId>cloud</artifactId>
            <groupId>com.zzx</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>cloud-gateway-gateway9527</artifactId>
    
        <properties>
            <maven.compiler.source>17</maven.compiler.source>
            <maven.compiler.target>17</maven.compiler.target>
        </properties>
        <dependencies>
            <!--  引入网关Gateway依赖   -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-gateway</artifactId>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.22</version>
            </dependency>
            <!--  引入Eureka client依赖 -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
            <!-- actuator监控信息完善 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>
        </dependencies>
    
    
    </project>
    
  3. 在gateway子模块中创建包com.zzx,在包下创建主启动类GatewayMain9527

    package com.zzx;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    @Slf4j
    public class GatewayMain9527 {
        public static void main(String[] args) {
            SpringApplication.run(GatewayMain9527.class,args);
            log.info("************ GatewayMain9527服务 启动成功 *************");
        }
    }
    
    
  4. 在resources目录下创建application.yml文件,配置如下

    server:
      port: 9527
    spring:
      cloud:
        gateway:
          routes:
            # 路由ID,没有固定规则但要求唯一,建议配合服务名
            - id: cloud-payment-provider
              # 匹配后提供服务的路由地址 (即目标服务地址)
              uri: http://localhost:8001
              # 断言会接收一个输入参数,返回一个布尔值结果
              predicates:
                # 路径相匹配的进行路由
                - Path=/payment/*
    
    
  5. 测试
    1)先开启7001和7002的Eureka服务,payment8001服务提供者和gateway9527服务。
    2)在浏览器使用9527端口,也就是网关进行访问payment8001服务即可。
    在浏览器输入:http://localhost:9527/payment/index

2、SpringCloudGateway_Java API构建路由

  1. 在子模块cloud-gateway-gateway9527中的com.zzx包下,创建包config,并在包下创建GatewayConfig

    package com.zzx.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 routeLocator(RouteLocatorBuilder builder){
            //获取路由
            RouteLocatorBuilder.Builder routes = builder.routes();
            /**
             * 设置路由
             * 1.路由id
             * 2.路由匹配规则
             * 3.目标地址
             */
            routes.route("path_route",r->r.path("/payment/*").uri("http://localhost:8001/")).build();
            return routes.build();
        }
    }
    
    
  2. 测试
    1)将yml文件中的gateway配置注释掉,然后重启该服务。
    2)在浏览器上访问:http://localhost:9527/payment/index

3、SpringCloudGateway的动态路由功能

  1. 再添加一个服务提供者,用以实现Gateway网关的动态路由的功能。
    1)复制payment8001服务,然后点击cloud父工程,ctrl+v进行粘贴,修改名字为8002
    2)修改POM文件:

    <artifactId>cloud-provider-payment8002</artifactId>
    

    3)将POM右键,选择添加为Maven项目Add as Maven Project
    在这里插入图片描述
    4)修改com.zzx包下的启动类的名字以及类中的名字

    package com.zzx;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    /**
     * 主启动类
     */
    @SpringBootApplication
    @Slf4j
    public class PaymentMain8002 {
        public static void main(String[] args) {
            SpringApplication.run(PaymentMain8002.class,args);
            log.info("****** PaymentMain8002服务启动成功 *****");
        }
    }
    
    

    5)将yml文件的端口号port和instance-id的名字有8001部分都修改为8002
    然后在启动类中运行该payment8002服务。

  2. 修改gateway9527项目的yml文件

    server:
      port: 9527
    
    eureka:
      instance:
        # 注册名
        instance-id: cloud-gateway-gateway9527
      client:
        service-url:
          # Eureka server的地址
          #集群
          defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka
          #单机
          #defaultZone: http://localhost:7001/eureka/
    spring:
      application:
        #设置应用名
        name: cloud-gateway
      cloud:
        gateway:
          routes:
            # 路由ID,没有固定规则但要求唯一,建议配合服务名
            - id: cloud-payment-provider
              # 匹配后提供服务的路由地址 (即目标服务地址) lb后跟提供服务的微服务的名字
              uri: lb://CLOUD-PAYMENT-PROVIDER
              # 断言会接收一个输入参数,返回一个布尔值结果
              predicates:
                # 路径相匹配的进行路由
                - Path=/payment/*
    
  3. 注释之前的配置文件GatewayConfig中的方法。

  4. 在服务提供者payment8001和payment8002中的com.zzx.controller的PaymentController类中添加如下代码

    @Value("${server.port}")
    private String port;
    @GetMapping("lb")
    public String lb(){
        return port;
    }
    

    即通过该lb的url请求来测试动态路由是否配置生效。

  5. 测试动态路由是否配置生效。
    1)重启payment8001和payment8002以及gateway9527服务
    2)浏览器中访问:http://localhost:9527/payment/lb
    在这里插入图片描述
    在这里插入图片描述
    此时刷新后随即出现8001或8002,估计是轮询的策略。

4、SpringCloudGateway的路由断言

  1. UTC时间格式的时间参数时间生成方法

    package demo;
    
    import java.time.ZonedDateTime;
    
    public class Test1 {
    	public static void main(String[] args) {
    		ZonedDateTime now = ZonedDateTime.now();
    		System.out.println(now);
    	}
    }
    
  2. Postman的下载地址:https://dl.pstmn.io/download/latest/win64
    Postman即用来URL请求测试的软件,可以很方便的添加任何请求参数。
    点击+号即可创建新的请求窗口,用来发送URL请求
    在这里插入图片描述

  3. After路由断言

    predicates:
    	- Path=/payment/*
    	# 在这个时间点之后才能访问
    	- After=2030-04-28T11:50:49.213572400+08:00[Asia/Shanghai]
    

    即使用生成的UTC时间格式的时间,在该时间之后才允许访问。

  4. Before路由断言

    predicates:
    	- Path=/payment/*
    	 # 在这个时间点之前才能访问
        - Before=2030-04-28T11:50:49.213572400+08:00[Asia/Shanghai]
    

    即使用生成的UTC时间格式的时间,在该时间之前才允许访问。

  5. Between路由断言

    predicates:
    	- Path=/payment/*
    	# 在两个时间内才能访问
        - Between=2030-04-28T11:50:49.213572400+08:00[Asia/Shanghai],2030-04-28T11:50:49.213572400+08:00[Asia/Shanghai]
    

    即使用生成的UTC时间格式的时间,在两个时间内才允许访问。

  6. Cookie路由断言
    1)Cookie验证的是Cookie中保存的信息,Cookie断言和上面介绍的两种断言使用方式大同小异,唯一的不同是它必须连同属性值一同验证,不能单独只验证属性是否存在。

    predicates:
    	- Path=/payment/*
    	- Cookie=username,zzx
    

    即Cookie的username的值为zzx才允许访问
    2)使用postman进行测试,在headers添加Cookie即可
    在这里插入图片描述
    此时如果不带Cookie,则报404错误

  7. Header路由断言
    1)这个断言会检查Header中是否包含了响应的属性,通常可以用来验证请求是否携带了访问令牌。

    predicates:
    	- Path=/payment/*
    	- Header=X-Request-Id,\d+
    

    2)使用postman进行测试,在headers添加X-Request-Id即可
    在这里插入图片描述

  8. Host路由断言
    1)Host 路由断言 Factory包括一个参数:host name列表。使用Ant路径匹配规则, .作为分隔符。访问的主机匹配http或者https, baidu.com 默认80端口, 就可以通过路由。 多个参数使用,号隔开。

    predicates:
    	- Path=/payment/*
    	- Host=127.0.0.1,localhost
    

    2)使用postman进行测试,在headers添加Host即可
    在这里插入图片描述

  9. Method路由断言
    1)即Request请求的方式,例如GET或POST请求,不匹配则无法进行请求

    predicates:
    	- Path=/payment/*
    	- Method=GET,POST
    

    2)可以使用postman,也可以使用浏览器直接访问,因为不需要加任何参数

  10. Query路由断言
    1)请求断言也是在业务中经常使用的,它会从ServerHttpRequest中的Parameters列表中查询指定的属性,例如验证参数的类型等

    predicates:
    	- Path=/payment/*
    	- Query=age,\d+
    

    2)在参数Params中添加age属性,值为正整数即可访问
    在这里插入图片描述

三、SpringCloudGateway的过滤器及跨域

1、SpringCloudGateway的过滤器

  1. 过滤器Filter
    在用户访问各个服务前,应在网关层统一做好鉴权、限流等工作。
    1)Filter的生命周期
    根据生命周期可以将Spring Cloud Gateway中的Filter分为"PRE"和"POST"两种。
    PRE:代表在请求被路由之前执行该过滤器,此种过滤器可用来实现参数校验、权限校验、流量监控、日志输出、协议转换等功能。
    POST:代表在请求被路由到微服务之后执行该过滤器。此种过滤器可用来实现响应头的修改(如添加标准的HTTP Header )、收集统计信息和指标、将响应发送给客户端、输出日志、流量监控等功能。
    即PRE是路由之前,POST是路由之后。
    2)Filter分类
    根据作用范围,Filter可以分为以下两种。
    GatewayFilter:网关过滤器,此种过滤器只应用在单个路由或者一个分组的路由上。
    GlobalFilter:全局过滤器,此种过滤器会应用在所有的路由上。

2、网关过滤器GatewayFilter

  1. 官方的配置文档:https://docs.spring.io/spring-cloud-gateway/docs/4.0.4/reference/html/#gatewayfilter-factories
  2. 使用内置过滤器SetStatus
    1)在yml文件中的filters下添加过滤器
    server:
      port: 9527
    
    eureka:
      instance:
        # 注册名
        instance-id: cloud-gateway-gateway9527
      client:
        service-url:
          # Eureka server的地址
          #集群
          defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka
          #单机
          #defaultZone: http://localhost:7001/eureka/
    spring:
      application:
        #设置应用名
        name: cloud-gateway
      cloud:
        gateway:
          routes:
            # 路由ID,没有固定规则但要求唯一,建议配合服务名
            - id: cloud-payment-provider
              # 匹配后提供服务的路由地址 (即目标服务地址) lb后跟提供服务的微服务的名字
              uri: lb://CLOUD-PAYMENT-PROVIDER
              # 断言会接收一个输入参数,返回一个布尔值结果
              predicates:
                # 路径相匹配的进行路由
                - Path=/payment/*
                # 在这个时间点之后才能访问
    #            - After=2030-04-28T11:50:49.213572400+08:00[Asia/Shanghai]
                # 在这个时间点之前才能访问
    #            - Before=2030-04-28T11:50:49.213572400+08:00[Asia/Shanghai]
                # 在两个时间内才能访问
    #            - Between=2030-04-28T11:50:49.213572400+08:00[Asia/Shanghai],2030-04-28T11:50:49.213572400+08:00[Asia/Shanghai]
    #            - Cookie=username,zzx
    #            - Header=X-Request-Id,\d+
    #            - Host=127.0.0.1,localhost
    #            - Method=GET,POST
    #            - Query=age,\d+
              #过滤器,请求在传递过程中可以通过过滤器对其进行一定的修改
              filters:
                # 修改原始响应的状态码
                - SetStatus=250
    
    2)在浏览器测试:http://localhost:9527/payment/lb
    在这里插入图片描述
    此时响应码成功修改为250。

3、自定义网关过滤器GatewayFilter

  1. 在gateway9527服务的com.zzx.config包下,创建日志网关过滤器类LogGatewayFilterFactory

    package com.zzx.config;
    
    import lombok.Data;
    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 java.util.Arrays;
    import java.util.List;
    
    /**
     * 日志网关过滤器
     */
    @Component
    @Slf4j
    public class LogGatewayFilterFactory extends AbstractGatewayFilterFactory<LogGatewayFilterFactory.Config> {
    
        public LogGatewayFilterFactory() {
            super(Config.class);
        }
    
        /**
         * 表示配置填写顺序
         * @return
         */
        @Override
        public List<String> shortcutFieldOrder() {
            return Arrays.asList("consoleLog");
        }
    
        /**
         * 执行过滤的逻辑
         * @param config
         * @return
         */
        @Override
        public GatewayFilter apply(Config config) {
            return ((exchange, chain) -> {
                if(config.consoleLog){
                    log.info("********* consoleLog日志 开启 ********");
                }
                return chain.filter(exchange);
            });
        }
    
        /**
         * 过滤器使用的配置内容
         *
         */
        @Data
        public static class Config{
            private boolean consoleLog;
        }
    
    }
    
    
  2. 在YML文件中,添加如下

    filters:
         # 控制日志是否开启
         - Log=true
    

    即开启日志,该true会被consoleLog获取到。 然后即可打印对应的日志。

  3. 测试
    1)重启Gateway9527服务
    2)在浏览器中访问:http://localhost:9527/payment/lb
    在这里插入图片描述
    步骤:
    1、类名必须叫做XxxGatewayFilterFactory,注入到Spring容器后使用时的名称就叫做Xxx。
    2、创建一个静态内部类Config, 里面的属性为配置文件中配置的参数, - 过滤器名称=参数1,参数2…
    2、类必须继承 AbstractGatewayFilterFactory,让父类帮实现配置参数的处理。
    3、重写shortcutFieldOrder()方法,返回List参数列表为Config中属性集合
    return Arrays.asList(“参数1”,参数2…)
    4、无参构造方法中super(Config.class)
    5、编写过滤逻辑 public GatewayFilter apply(Config config)

4、自定义全局过滤器GlobalFilter

  1. 在gateway9527服务的com.zzx.config包下,创建用户鉴权全局过滤器类AuthGlobalFilter

    package com.zzx.config;
    
    import org.apache.commons.lang.StringUtils;
    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.stereotype.Component;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Mono;
    
    /**
     * 用户鉴权全局过滤器
     */
    @Component
    public class AuthGlobalFilter implements GlobalFilter, Ordered {
        /**
         * 自定义全局过滤器逻辑
         * @param exchange
         * @param chain
         * @return
         */
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            //1。请求中获取Token令牌
            String token = exchange.getRequest().getQueryParams().getFirst("token");
            //2.判断token是否为空
            if(StringUtils.isEmpty(token)){
                System.out.println("鉴权失败,令牌为空");
                //将状态码设置为未授权
                exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                return exchange.getResponse().setComplete();
            }
            //3。判断token是否有效
            if(!token.equals("zzx")){
                System.out.println("token令牌无效");
                exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                return exchange.getResponse().setComplete();
            }
            return chain.filter(exchange);
        }
    
        /**
         * 全局过滤器执行顺序 数值越小,优先级越高
         * @return
         */
        @Override
        public int getOrder() {
            return 0;
        }
    }
    
    
  2. 使用postman测试,在params中添加一个token进行测试
    在这里插入图片描述

5、内置全局过滤器

  1. 官方的配置文档:https://docs.spring.io/spring-cloud-gateway/docs/4.0.4/reference/html/#global-filters
    SpringCloud Gateway内部也是通过一系列的内置全局过滤器对整个路由转发进行处理的。
  2. 路由过滤器(Forward)
  3. 路由过滤器(LoadBalancerClient)
  4. Netty路由过滤器
  5. Netty写响应过滤器(Netty Write Response F)
  6. RouteToRequestUrl 过滤器
  7. 路由过滤器 (Websocket Routing Filter)
  8. 网关指标过滤器(Gateway Metrics Filter)
  9. 组合式全局过滤器和网关过滤器排序(Combined Global Filter and GatewayFilter Ordering)
  10. 路由(Marking An Exchange As Routed)

6、服务网关Gateway实现跨域

  1. 跨域
    即当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域

  2. 在resources目录下创建index.html文件

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Title</title>
    </head>
    <body>
    
    
    </body>
    <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
    <script>
    
    
      $.get("http://localhost:9527/payment/lb?token=zzx",function(data,status){
        alert("Data: " + data + "\nStatus: " + status);
       });
    </script>
    </html>
    
    
  3. 配置允许跨域
    1)在未配置允许跨域之前,打开该index.html文件时,如图
    在这里插入图片描述
    2)在yml文件中配置允许跨域

    spring:
      cloud:
       gateway:
        globalcors:
         cors-configurations:
          '[/**]':
           allowCredentials: true
           allowedOriginPatterns: "*"
           allowedMethods: "*"
           allowedHeaders: "*"
         add-to-simple-url-handler-mapping: true
    
    

3)配置后,打开该index.html文件时,如图
在这里插入图片描述

四、SpringCloudGateway实现用户鉴权

1、JsonWebToken概论

  1. JWT是一种用于双方之间传递安全信息的简洁的、URL安全的声明规范。定义了一种简洁的,自包含的方法用于通信双方之间以Json对象的形式安全的传递信息。特别适用于分布式站点的单点登录(SSO)场景。

  2. JWT优点
    1)无状态
    2)适合移动端应用
    3)单点登录友好

  3. 用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候会加上签名,服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。

  4. JWT 的三个部分依次如下:
    1)头部(header)
    JSON对象,描述 JWT 的元数据。其中 alg 属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ 属性表示这个令牌(token)的类型(type),统一写为 JWT。

    {
     "alg": "HS256",
     "typ": "JWT"
    }
    

    2)载荷(payload)
    内容又可以分为3种标准
    1.标准中注册的声明
    iss: jwt签发者
    sub: jwt所面向的用户
    aud: 接收jwt的一方
    exp: jwt的过期时间,这个过期时间必须要大于签发时间
    nbf: 定义在什么时间之前,该jwt都是不可用的.
    iat: jwt的签发时间
    jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
    2.公共的声明
    公共的声明可以添加任何的信息。一般这里我们会存放一下用户的基本信息(非敏感信息)。
    3.私有的声明
    私有声明是提供者和消费者所共同定义的声明。需要注意的是,不要存放敏感信息
    base64编码,任何人获取到jwt之后都可以解码!!

    {
     "sub": "1234567890",
     "name": "John Doe",
     "iat": 1516239022
    }
    

    3)签证(signature)
    这部分就是 JWT 防篡改的精髓,其值是对前两部分base64UrlEncode 后使用指定算法签名生成,以默认 HS256 为例,指定一个密钥(secret),就会按照如下公式生成:

    HMACSHA256(
     base64UrlEncode(header) + "." + base64UrlEncode(payload),
     secret,
    )
    
  5. 客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization字段里面。

2、创建用户的微服务及登录操作

  1. 在cloud父工程下,创建子模块项目cloud-auth-user6500
    在这里插入图片描述

  2. 在cloud-auth-user6500项目的pom文件中引入依赖

     <dependencies>
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- redis -->
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!-- eureka client 依赖 -->
        <dependency>
     <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
          <version>1.18.22</version>
        </dependency>
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
         <!--   引入JWT依赖   -->
        <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>2.0.23</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>4.2.1</version>
        </dependency>
      </dependencies>
    
    
  3. 在com.zzx中创建一个包utils,创建工具类JWTUtils

    package com.zzx.utils;
    
    import com.auth0.jwt.JWT;
    import com.auth0.jwt.JWTVerifier;
    import com.auth0.jwt.algorithms.Algorithm;
    import com.auth0.jwt.exceptions.JWTVerificationException;
    import com.auth0.jwt.interfaces.DecodedJWT;
    
    import java.util.Date;
    import java.util.concurrent.TimeUnit;
    
    public class JWTUtils {
        // 签发人
        private static final String ISSUSER = "zzx";
        // 过期时间 1分钟
        private static final long TOKEN_EXPIRE_TIME = 60*1000;
        // 秘钥
        public static final String SECRET_KEY = "zzx-13256";
        /**
         * 生成令牌
         * @return
         */
        public static String token(){
            Date now = new Date();
            Algorithm hmac256 = Algorithm.HMAC256(SECRET_KEY);
            // 1.创建JWT
            String token = JWT.create().
                    // 签发人
                    withIssuer(ISSUSER)
                    // 签发时间
                    .withIssuedAt(now)
                    // 过期时间
                    .withExpiresAt(new Date(now.getTime()+TOKEN_EXPIRE_TIME))
                    // 加密算法
                    .sign(hmac256);
            return token;
        }
    
        /**
         * 验证令牌
         * @return
         */
        public static boolean verify(String token){
            try {
                Algorithm hmac256 = Algorithm.HMAC256(SECRET_KEY);
                JWTVerifier verifier = JWT.require(hmac256)
                        // 签发人
                        .withIssuer(ISSUSER)
                        .build();
                // 如果校验有问题则抛出异常
                DecodedJWT verify = verifier.verify(token);
                return true;
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (JWTVerificationException e) {
                e.printStackTrace();
            }
            return false;
        }
    
        public static void main(String[] args) throws InterruptedException {
            String token = token();
            System.out.println(token);
            boolean verify = verify(token);
            System.out.println(verify);
            verify = verify(token+" 11");
            System.out.println(verify);
            TimeUnit.SECONDS.sleep(61);
            verify = verify(token);
            System.out.println(verify);
    
        }
    }
    
    

    在该工具类JWTUtils中创建main方法用来测试该工具类。后面需要删掉。

  4. 在com.zzx中创建一个包common,创建类Result

    package com.zzx.common;
    
    import lombok.AllArgsConstructor;
    import lombok.Builder;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    /**
     * 返回实体类
     */
    @AllArgsConstructor
    @NoArgsConstructor
    @Data
    @Builder
    public class Result {
        // 状态码
        private int code;
        // 描述信息
        private String msg;
        // token令牌
        private String token;
    }
    
    

    即用该类来封装返回值信息。

  5. 在com.zzx中创建一个包controller,创建控制层类UserController

    package com.zzx.controller;
    
    import com.zzx.common.Result;
    import com.zzx.utils.JWTUtils;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * 用户控制层
     */
    @RestController
    @RequestMapping("user")
    public class UserController {
        /**
         * 登录
         * @param username
         * @param password
         */
        @PostMapping("login")
        public Result login(String username, String password){
            // 1.验证用户名和密码
            // TODO 模拟数据库操作
            if("zzx".equals(username)&&"123456".equals(password)){
                // 2.生成令牌
                String token = JWTUtils.token();
                return Result.builder().code(200).msg("success").token(token).build();
            }else{
                return Result.builder().code(500).msg("用户名或密码不正确").build();
            }
        }
    }
    
    
  6. 在resources目录下创建一个application.yml配置文件

    server:
      port: 6500
    
    eureka:
      instance:
        # 注册名
        instance-id: cloud-auth-user6500
      client:
        service-url:
          # Eureka server的地址
          #集群
          defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka
          #单机
          #defaultZone: http://localhost:7001/eureka/
    spring:
      application:
        #设置应用名
        name: cloud-auth-user
    
  7. 在com.zzx中,修改主启动类Main,修改为UserMain6500

    package com.zzx;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    /**
     * 主启动类
     */
    @Slf4j
    @SpringBootApplication
    public class UserMain6500 {
        public static void main(String[] args) {
            SpringApplication.run(UserMain6500.class,args);
            log.info("************ UserMain6500服务 启动成功 ************");
        }
    }
    
  8. 测试User控制层的login方法
    1)启动eureka服务eureka7001和eureka7002以及user6500
    在这里插入图片描述

2)在postman中,使用POST请求传入用户名和密码,对该url进行测试
在这里插入图片描述

3、服务网关Gateway实现用户鉴权

即在网关过滤器中加入JWT来鉴权

  1. 在gateway9527项目的POM文件中添加JWT依赖

     <!--   引入JWT依赖     -->
            <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>2.0.23</version>
            </dependency>
            <!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
            <dependency>
                <groupId>com.auth0</groupId>
                <artifactId>java-jwt</artifactId>
                <version>4.2.1</version>
            </dependency>
    
  2. 将user6500项目中com.zzx.utils包下的JWTUtils复制到gateway9527项目的com.zzx.utils包下

    package com.zzx.utils;
    
    import com.auth0.jwt.JWT;
    import com.auth0.jwt.JWTVerifier;
    import com.auth0.jwt.algorithms.Algorithm;
    import com.auth0.jwt.exceptions.JWTVerificationException;
    import com.auth0.jwt.interfaces.DecodedJWT;
    
    import java.util.Date;
    
    public class JWTUtils {
        // 签发人
        private static final String ISSUSER = "zzx";
        // 过期时间 1分钟
        private static final long TOKEN_EXPIRE_TIME = 60*1000;
        // 秘钥
        public static final String SECRET_KEY = "zzx-13256";
        /**
         * 生成令牌
         * @return
         */
        public static String token(){
            Date now = new Date();
            Algorithm hmac256 = Algorithm.HMAC256(SECRET_KEY);
            // 1.创建JWT
            String token = JWT.create().
                    // 签发人
                    withIssuer(ISSUSER)
                    // 签发时间
                    .withIssuedAt(now)
                    // 过期时间
                    .withExpiresAt(new Date(now.getTime()+TOKEN_EXPIRE_TIME))
                    // 加密算法
                    .sign(hmac256);
            return token;
        }
    
        /**
         * 验证令牌
         * @return
         */
        public static boolean verify(String token){
            try {
                Algorithm hmac256 = Algorithm.HMAC256(SECRET_KEY);
                JWTVerifier verifier = JWT.require(hmac256)
                        // 签发人
                        .withIssuer(ISSUSER)
                        .build();
                // 如果校验有问题则抛出异常
                DecodedJWT verify = verifier.verify(token);
                return true;
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (JWTVerificationException e) {
                e.printStackTrace();
            }
            return false;
        }
    
    }
    
    
  3. 修改application.yml文件

    server:
      port: 9527
    
    eureka:
      instance:
        # 注册名
        instance-id: cloud-gateway-gateway9527
      client:
        service-url:
          # Eureka server的地址
          #集群
          defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka
          #单机
          #defaultZone: http://localhost:7001/eureka/
    
    org:
      my:
        jwt:
          # 跳过认证路由
          skipAuthUrls:
            - /user/login
    
    spring:
      application:
        #设置应用名
        name: cloud-gateway
      cloud:
        gateway:
          # 路由配置
          routes:
            # 路由ID,没有固定规则但要求唯一,建议配合服务名
            - id: cloud-auth-user
              # 匹配后提供服务的路由地址 (即目标服务地址) lb后跟提供服务的微服务的名字
              uri: lb://CLOUD-AUTH-USER
              # 断言会接收一个输入参数,返回一个布尔值结果
              predicates:
                # 路径相匹配的进行路由
                - Path=/user/*
            # 路由ID,没有固定规则但要求唯一,建议配合服务名
            - id: cloud-payment-provider
              # 匹配后提供服务的路由地址 (即目标服务地址) lb后跟提供服务的微服务的名字
              uri: lb://CLOUD-PAYMENT-PROVIDER
              # 断言会接收一个输入参数,返回一个布尔值结果
              predicates:
                # 路径相匹配的进行路由
                - Path=/payment/*
                # 在这个时间点之后才能访问
    #            - After=2030-04-28T11:50:49.213572400+08:00[Asia/Shanghai]
                # 在这个时间点之前才能访问
    #            - Before=2030-04-28T11:50:49.213572400+08:00[Asia/Shanghai]
                # 在两个时间内才能访问
    #            - Between=2030-04-28T11:50:49.213572400+08:00[Asia/Shanghai],2030-04-28T11:50:49.213572400+08:00[Asia/Shanghai]
    #            - Cookie=username,zzx
    #            - Header=X-Request-Id,\d+
    #            - Host=127.0.0.1,localhost
    #            - Method=GET,POST
    #            - Query=age,\d+
              #过滤器,请求在传递过程中可以通过过滤器对其进行一定的修改
              filters:
                # 修改原始响应的状态码
    #            - SetStatus=250
                # 控制日志是否开启
                - Log=true
          globalcors:
            cors-configurations:
              '[/**]':
                allowCredentials: true
                allowedOriginPatterns: "*"
                allowedMethods: "*"
                allowedHeaders: "*"
            add-to-simple-url-handler-mapping: true
    

    即需要添加一个user微服务的路由,以及跳过权限验证的Path路径

  4. 将gateway9527项目的com.zzx.config包下原先的用户鉴权类AuthGlobalFilter上面的@Component注解注释掉,即不使用这个类来鉴权;创建使用另一个类UserAuthGlobalFilter来鉴权

    package com.zzx.config;
    
    import com.alibaba.fastjson.JSONObject;
    import com.zzx.common.Response;
    import com.zzx.utils.JWTUtils;
    import io.micrometer.common.util.StringUtils;
    import lombok.Data;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.cloud.gateway.filter.GatewayFilterChain;
    import org.springframework.cloud.gateway.filter.GlobalFilter;
    import org.springframework.core.Ordered;
    import org.springframework.core.io.buffer.DataBuffer;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.server.reactive.ServerHttpResponse;
    import org.springframework.stereotype.Component;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Flux;
    import reactor.core.publisher.Mono;
    
    import java.nio.charset.StandardCharsets;
    
    /**
     * 用户鉴权全局过滤器
     */
    @Data
    @ConfigurationProperties("org.my.jwt")
    @Component
    @Slf4j
    public class UserAuthGlobalFilter implements GlobalFilter, Ordered {
        private String[] skipAuthUrls;
        /**
         * 过滤器逻辑
         * @param exchange
         * @param chain
         * @return
         */
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            // 获取请求url地址
            String path = exchange.getRequest().getURI().getPath();
            // 跳过不需要验证的路径
            if(skipAuthUrls!=null && isSKip(path)){
                return chain.filter(exchange);
            }
            // 1.从请求头中获取token
            String token = exchange.getRequest().getHeaders().getFirst("token");
            // 2.判断token
            if(StringUtils.isEmpty(token)){
                // 3.设置响应
                ServerHttpResponse response = exchange.getResponse();
                // 4.设置响应状态码
                response.setStatusCode(HttpStatus.OK);
                // 5.设置响应头
                response.getHeaders().add("Content-Type","application/json;charset=UTF-8");
                // 6.创建响应对象
                Response res = new Response(200, "token 参数缺失");
                // 7.对象转字符串
                byte[] bytes = JSONObject.toJSONString(res).getBytes(StandardCharsets.UTF_8);
                // 8.数据流返回数据
                DataBuffer wrap = response.bufferFactory().wrap(bytes);
                return response.writeWith(Flux.just(wrap));
    
            }
            // 验证token
            boolean verify = JWTUtils.verify(token);
            if(!verify){
                // 3.设置响应
                ServerHttpResponse response = exchange.getResponse();
                // 4.设置响应状态码
                response.setStatusCode(HttpStatus.OK);
                // 5.设置响应头
                response.getHeaders().add("Content-Type","application/json;charset=UTF-8");
                // 6.创建响应对象
                Response res = new Response(200, "token 失效");
                // 7.对象转字符串
                byte[] bytes = JSONObject.toJSONString(res).getBytes(StandardCharsets.UTF_8);
                // 8.数据流返回数据
                DataBuffer wrap = response.bufferFactory().wrap(bytes);
                return response.writeWith(Flux.just(wrap));
            }
            // token 令牌通过
            return chain.filter(exchange);
        }
    
        @Override
        public int getOrder() {
            return 0;
        }
    
        private boolean isSKip(String url){
            for (String skipAuthUrl :skipAuthUrls) {
                if(url.startsWith(skipAuthUrl)){
                    return true;
                }
            }
            return false;
        }
    }
    
    
  5. 测试
    1)先启动eureka7001和eureka7002,还有Payment8001和Payment8002,以及user6500和gateway9527服务。
    在这里插入图片描述

2)使用postman工具来测试,先进行登录,拿到用户的token
在这里插入图片描述
3)再切换到之前9527的url测试
token有效时
在这里插入图片描述
token过期失效时
在这里插入图片描述
没有token时(即未登录时)
在这里插入图片描述

总结

  1. Spring Cloud Gateway 用"Netty + Webflux"实现,不需要导入Web依赖。
    1)Webflux模式替换了旧的Servlet线程模型。用少量的线程处理request和response io操作,这些线程称为Loop线程。Webflux中的Loop线程不仅可以处理请求和响应请求,还可以对业务中不阻塞的操作进行处理,从而提高它的利用率。阻塞的操作由work线程进行处理。
    Webflux底层使用的还是Netty,Netty是目前业界认可的最高性能的通信框架。
    2)SpringCloudGateway的三大核心概念,分别是路由、断言、过滤。
    即根据url请求进行匹配到指定路由;每个路由上面都有断言,根据断言来判断是否可以进行路由;最后对该url请求进行一个过滤,例如监控、限流和日志输出等操作。
  2. 1)SpringCloudGateway的搭建,需要先引入依赖,然后创建主启动类,最后配置Gateway的配置文件。- id属性值需要唯一;uri的属性值即对应的服务器ip地址+端口号;predicates断言的属性值,例如OrderController第一层@RequestMapping注解的url属性值,即判断url是否跟该值一致。
    2)服务网关Gateway通过Java API构建时需要实现RouteLocator接口构建路由规则。即先将yml文件中等价的gateway配置注释掉,然后创建一个config配置类,在配置类中,创建一个方法使用RouteLocator接口来构建路由。并在该方法上添加@Bean注解,即由SpringIOC容器进行管理。
    3)SpringCloudGateway的动态路由功能,即在yml文件中将原本路由的uri改成lb://服务提供者的微服务的名字;然后需要引入EureakaClient和Gateway等依赖即可实现Gateway的动态路由功能。
    也就是说需要配置和使用Eureka,但是可以设置不把自身注册到Eureka服务中。
    4)SpringCloudGateway的路由断言,路由断言分别有After、Before、Between、Cookie、Header、Host、Method、Query等。其中After、Before、Between都是跟时间有关的;Cookie、Header、Host都是在头文件Headers中携带的参数;Method是匹配指定的请求方法;Query是在Params中检查参数的合法性。
    断言是在YML文件的spring.cloud.gateway.routes.predicates下进行配置的。
    5)SpringCloudGateway的过滤器,在用户访问各个服务前,应在网关层统一做好鉴权、限流等工作。过滤器Filter的生命周期分为PRE和POST,即PRE是路由之前,POST是路由之后。它作用范围分为GatewayFilter和GlobalFilter,即GatewayFilter是网关路由器,是应用在单个路由或一个分组的路由上的,而GlobalFilter是全局路由器,会应用在所有路由上的。
  3. 1)内置过滤器,即在YML文件中,在filters下添加内置过滤器。
    2)自定义网关过滤器,即需要创建一个配置类,类名必须叫做XxxGatewayFilterFactory,在该类上使用@Component注解;该类需要创建一个静态内部类Config,里面的属性为配置文件中配置的参数;必须继承AbstractGatewayFilterFactory;重写shortcutFieldOrder()方法,返回List参数列表为Config中属性集合;创建无参构造方法,方法体为super(Config.class);编写过滤逻辑 public GatewayFilter apply(Config config)。
    3)自定义全局过滤器,当客户端第一次请求服务时,服务端对用户进行信息认证(登录);认证通过,将用户信息进行加密形成token,返回给客户端,作为登录凭证;以后每次请求,客户端都携带认证的token;服务端对token进行解密,判断是否有效,有效则继续允许访问服务,无效则不允许访问服务。
    此时需要实现GlobalFilter, Ordered接口,一个是全局过滤器接口,一个是全局过滤器执行顺序的接口。
    即在Ordered接口的实现类中返回一个数值,该值越小,当前过滤器的优先级越高。
    GlobalFilter接口的实现类,对请求参数进行一个过滤操作。
  4. 1)配置跨域,即在yml文件配置允许跨域即可。
    2)JsonWebToken,是一种用于双方之间传递安全信息的简洁的、URL安全的声明规范。适用于分布式的单点登录(SSO)。
    客户端收到服务器返回的 JWT,会把数据保存到loadStorage。JWT签证默认的算法是 HMAC SHA256(HS256)。
    3)用户登录并生成token返回的业务流程,登录成功时JWT的Token通过JWTUtils工具类生成,状态码为200,消息为成功,以及返回值的类型需要封装为Result实体类;登录失败时不生成token,状态码500,消息为用户名或密码错误,同时返回值的类型也是需要封装为Result实体类。此时该方法为Post请求,因为涉及帐号信息。
    并且需要引入JWT和fastjson依赖。
  5. 使用gateway网关进行用户鉴权,在application.yml文件中配置跳过login登录的鉴权,即其他的url请求都要进行用户鉴权;需要引入JWT和fastjson的依赖;使用JWTUtils的生成token方法以及验证token是否有效的方法;在用户鉴权时,需要先获取跳过的路径,进行匹配,匹配成功则跳过鉴权进行下一步的业务操作;匹配失败,则说明该请求需要验证token,首先需要从request请求的请求头中获取token,如果token为空,则返回一个response对象,包含状态码和字符串消息;如果token不为空,则进行下一步验证,即使用JWTUtils的token验证方法,如果返回false,则表示token无效或者失效,则返回一个response对象,包含状态码和字符串消息;如果token不为空且token有效,则进行下一步的业务操作。
    即用户鉴权,实际上就判断该请求是否需要跳过鉴权,以及token是否为空和token是否有效的操作。

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

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

相关文章

cmd 批量ping命令

cmd 批量ping命令 1、批量ping 1个网段2、批量ping多个网段 1、批量ping 1个网段 ping 1个网段 for /l %X in (2,1,254) do (ping -n 2 10.1.2.%X && echo 10.1.2.%X >> ok.txt || echo 10.1.2.%X >> no.txt)命令说明&#xff1a; %l 表示在(2,1,254) 按…

免费矢量图标网站都有哪些,推荐这10个

矢量图标是我们日常设计应用程序和网页过程中不可缺少的元素之一。通过小矢量图标&#xff0c;我们可以快速方便地实现视觉指导和功能划分。 但在创作中&#xff0c;设计师往往需要花费大量的时间和精力来寻找不同网站的矢量图标&#xff0c;以满足他们的设计需求&#xff0c;…

电子价签能给生鲜零售带来什么?

生鲜零售 变价难 超市中的水果、蔬菜、鱼肉海鲜等商品&#xff0c;往往会受季节变化、运输和储存成本、自然环境引起的生产成本、供需关系等因素影响&#xff0c;其商品价格变动比较频繁。如不能及时更新价格&#xff0c;容易影响商品的销售&#xff0c;进而影响超市的盈利能…

asp.net基于web的大学生交友网站shfw9294程序

为社会的和谐做出了贡献。 通过网络&#xff0c;不同地区的人员可跨越时间、地点进行交流。建设一个安全、可靠、开放、纯净的大学生交友网站系统&#xff0c;为中山市大学生提供一个使用方便的、 界面美观的交友园地是本系统的主要目的。 系统使用Visual studio.net2010作为系…

【计算机网络】总结复习(1)

本文主要记录在看小林coding 时的一些体会&#xff0c;会记录一些我认为重要的知识点以及感受 网络基础篇 osi 七层协议 tcp/ip 四层协议 应用层 传输层 网络层 网络接口层 实际场景&#xff1a; 输出网址 到 网页显示 过程url 解析&#xff08;协议web 服务器 数据源路径…

智慧城市规划数字化管理:数字孪生技术的创新应用

随着智能城市的不断发展&#xff0c;数字孪生技术也开始在智慧城市的建设中得到了广泛应用。数字孪生作为一种数字化的复制技术&#xff0c;它可以模拟真实世界中的实体和过程。 在城市规划方面&#xff0c;数字孪生可以帮助城市规划师更加直观地了解城市的整体规划和发展趋势&…

菜鸡shader2:L2基于BlinnPhong假环境反射,生锈材质

目录 假环境反射材质&#xff08;不锈钢材质&#xff09;生锈材质 假环境反射材质&#xff08;不锈钢材质&#xff09; 先放上最终图&#xff1a; 处理高光部分&#xff1a; 这里是phong模型不是blinnphong。应用观察方向的反射方向和光方向点乘算出高光&#xff0c;然后重…

PHP+vue大学生心理健康评价和分析系统8w3ff

本整个大学生心理健康管理系统是按照整体需求来实现各个功能的&#xff0c;它可以通过心理健康测评来检测大学生的心理健康&#xff0c;并且给予预警&#xff0c;还可以预约医生来解决问题。并且&#xff0c;管理员可以查看用户信息&#xff0c;发布一些关于心理健康的文章。该…

CVE-2023-29489 cPanel XSS漏洞分析研究

前言 由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;文章作者不为此承担任何责任。 如果文章中的漏洞出现敏感内容产生了部分影响&#xff0c;请及时联系作者&#xff0c;望谅解。 一、漏洞原理 漏洞简述 cPa…

MySQL之Log Buffer详解

前言 本文已收录在MySQL性能优化原理实战专栏&#xff0c;点击此处浏览更多优质内容。 上一篇文章MySQL之Doublewrite Buffer详解首次提到Redo Log的概念&#xff0c;Redo Log是数据库体系架构中非常重要的一个模块&#xff0c;它能保证数据库的Crash-safe&#xff08;崩溃恢复…

Linux Docker 搭建WordPress个人博客(避坑篇)

本文主要参考文章&#xff1a;Docker实战&#xff1a;Docker安装WordPress&#xff0c;快速搭建自己的博客 但我在参考过程中踩坑较多&#xff0c;特此记录下 1、What is WordPress 官网&#xff1a;https://wordpress.com/zh-cn/ wordpress是世界上应用最广泛的开源CMS程序…

Kotlin中的密封类和密封接口

密封类和密封接口是 Kotlin 中允许创建受限类层次结构的两个特性。这两个构造用于定义一组有限的可能子类型&#xff0c;并防止在声明的层次结构之外定义其他子类型。 密封类 密封类是一个可以被子类化的类&#xff0c;但只能在声明它的同一个文件中进行子类化。这意味着密封…

内网穿透(ngrock)

什么是内网穿透&#xff1f; 内网穿透&#xff0c;即NAT穿透&#xff0c;网络连接时术语&#xff0c;计算机是局域网内时&#xff0c;外网与内网的计算机节点需要连接通信&#xff0c;有时就会出现不支持内网穿透。简单来说&#xff0c;就是让身处局域网的电脑&#xff0c;被大…

《基于深度学习模型的非接触式面部视频记录技术用于心房颤动的检测》阅读笔记

目录 一、论文摘要 二、论文十问 Q1: 论文试图解决什么问题&#xff1f; Q2: 这是否是一个新的问题&#xff1f; Q3: 这篇文章要验证一个什么科学假设&#xff1f; Q4: 有哪些相关研究&#xff1f;如何归类&#xff1f;谁是这一课题在领域内值得关注的研究员&#xff1f; …

python安装教程(新手)(超详细)

python安装教程 手把手&#xff0c;简单的教你搭建python的开发环境 1.环境下载 1.下载地址 下载的时候要注意自己电脑的版本和python版本之间的关系。 我的是win10&#xff0c;用的是3.9.0&#xff0c;目前最新的3.11.x 老电脑最好下载3.7.0版本及一下的 Download Python | P…

Word控件Spire.Doc 【打印】教程(1):通过 5 个步骤以编程方式打印 Word 文档

Spire.Doc for .NET是一款专门对 Word 文档进行操作的 .NET 类库。在于帮助开发人员无需安装 Microsoft Word情况下&#xff0c;轻松快捷高效地创建、编辑、转换和打印 Microsoft Word 文档。拥有近10年专业开发经验Spire系列办公文档开发工具&#xff0c;专注于创建、编辑、转…

04-菜单维护 尚筹网

在【菜单维护】页面&#xff0c;通过树形结构&#xff0c;使用zTree显示整个菜单。 准备工作 一、在数据库创建菜单表&#xff1a; #使用project_rowd表 use project_rowd;#创建菜单的数据库表 create table t_menu (id int(11) not null auto_increment, pid int(11), nam…

【蓝桥杯选拔赛真题55】Scratch昼夜变换 少儿编程scratch图形化编程 蓝桥杯选拔赛真题讲解

目录 scratch昼夜变换 一、题目要求 编程实现 二、案例分析 1、角色分析

5月3日 7H|5月4日 9H↕️|时间轴复盘

2:00-9:00 起床 9:00-9:30 洗漱到教室 9:30-9:40 泡豆浆 9:40-11:40 语法 【2h】 11:40-12:16 午饭回教室 12:16-14:30 健身 14:30-15:30 午睡 15:30-17:00 不背单词 【1.5h】 17:00-18:13 三篇阅读 【1h13min】 18:13-18:57 晚饭 19:00-19:15 …

Flink从入门到精通之-08多流转换

Flink从入门到精通之-08多流转换 无论是基本的简单转换和聚合&#xff0c;还是基于窗口的计算&#xff0c;我们都是针对一条流上的数据进行处理的。而在实际应用中&#xff0c;可能需要将不同来源的数据连接合并在一起处理&#xff0c;也有可能需要将一条流拆分开&#xff0c;…