Spring cloud alibaba

一. Spring cloud Gateway网关

什么是网关?就是网络请求的统一入口. 为什么需要网关? 1.如果我们的有成千上万个服务,我们在请求每个服务的时候都需要进行认证,难度与工作量可想而知,要控制用户对于整个服务的访问次数的限制。 2.如果没有统一的入口,那么前端在与服务端交互的时候定位到各个服务,假设服务器端作服务的重构,那么前端也得跟着一起修改。 gateway是spring cloud的第二代网关,未来会取代zuul,其性能是zuul的1.6倍左右,其内部是基于netty、reactor(多路复用)、webflux进行构建,性能强大。gateway需要从注册中心获取服务,然后通过网关来调用对应的服务。但是gateway不在web环境下运行,也就是说不能打成war包放在tomcat下运行

1.1 使用

1.创建springcloud-alibaba-microservice-gateway-9090子工程,导入依赖

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>springcloud-alibaba-microservice-manager</artifactId>
  7. <groupId>com.qf</groupId>
  8. <version>1.0-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>springcloud-alibaba-microservice-gateway-9090</artifactId>
  12. <dependencies>
  13. <dependency>
  14. <groupId>org.springframework.boot</groupId>
  15. <artifactId>spring-boot-starter-web</artifactId>
  16. <exclusions>
  17. <!-- 排除掉springmvc相关的配置信息 -->
  18. <exclusion>
  19. <groupId>org.springframework</groupId>
  20. <artifactId>spring-webmvc</artifactId>
  21. </exclusion>
  22. <!-- 排除掉tomcat相关的配置 -->
  23. <exclusion>
  24. <groupId>org.springframework.bootk</groupId>
  25. <artifactId>spring-boot-starter-tomcat</artifactId>
  26. </exclusion>
  27. <exclusion>
  28. <groupId>org.apache.tomcat.embed</groupId>
  29. <artifactId>tomcat-embed-core</artifactId>
  30. </exclusion>
  31. <exclusion>
  32. <groupId>org.apache.tomcat.embed</groupId>
  33. <artifactId>tomcat-embed-el</artifactId>
  34. </exclusion>
  35. <exclusion>
  36. <groupId>org.apache.tomcat.embed</groupId>
  37. <artifactId>tomcat-embed-websocket</artifactId>
  38. </exclusion>
  39. </exclusions>
  40. </dependency>
  41. <dependency>
  42. <groupId>com.alibaba.cloud</groupId>
  43. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  44. <version>2.2.1.RELEASE</version>
  45. </dependency>
  46. <!-- 网关相关配置 -->
  47. <dependency>
  48. <groupId>org.springframework.cloud</groupId>
  49. <artifactId>spring-cloud-starter-gateway</artifactId>
  50. <version>2.2.1.RELEASE</version>
  51. </dependency>
  52. </dependencies>
  53. <dependencyManagement>
  54. <dependencies>
  55. <dependency>
  56. <groupId>com.alibaba.cloud</groupId>
  57. <artifactId>spring-cloud-alibaba-dependencies</artifactId>
  58. <version>2.2.1.RELEASE</version>
  59. <type>pom</type>
  60. <scope>import</scope>
  61. </dependency>
  62. <dependency>
  63. <groupId>org.springframework.cloud</groupId>
  64. <artifactId>spring-cloud-dependencies</artifactId>
  65. <version>Hoxton.SR6</version>
  66. <type>pom</type>
  67. <scope>import</scope>
  68. </dependency>
  69. </dependencies>
  70. </dependencyManagement>
  71. </project>

2.创建 GateWayApplication 启动类

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

3.创建

spring:
  application:
    # 网关名称
    name: microservice-gateway

  cloud:
    nacos:
      discovery:
        enabled: true
        server-addr: 127.0.0.1:8848
        #表示是否将服务注册到nacos上,false表示不注册,默认true
        register-enabled: false
    gateway:
      discovery:
        locator:
          # 开启之后可以使用网关地址进行访问
          # 之前:http://localhost:8080/feign/getAll
          # 现在:http://localhost:9090/microservice-consumer/feign/getAll
          enabled: true

server:
  port: 9090

4.启动服务提供方消费方以及网关对应工程,进行测试

1.2 谓词配置

谓词(predicate)是gateway内置的的一下关于请求相关的处理,在application.yml中增加配置

spring:
  application:
    # 网关名称
    name: microservice-gateway

  cloud:
    nacos:
      discovery:
        enabled: true
        server-addr: 127.0.0.1:8848
        #表示是否将服务注册到nacos上,false表示不注册,默认true
        register-enabled: false
    gateway:
      discovery:
        locator:
          # 开启之后可以使用网关地址进行访问
          # 之前:http://localhost:8080/feign/getAll
          # 现在:http://localhost:9090/microservice-consumer/feign/getAll
          enabled: true
      routes:
        # id可以不为服务名,名字随意,但是不能重复。推荐使用服务名的方式,不暴露服务名
        - id: microservice-consumer
          # uri才是控制着某个具体的访问到达我们特定的服务
          uri: lb://microservice-consumer
          # 谓词: 就是满足的条件. 可以在 org.springframework.cloud.gateway.handler.predicate 这个包下
          predicates:
            # 配置访问controller的一级目录,即可通过http://localhost:9090/feign/getAll来访问
            - Path=/feign/**
            # 请求的参数中必须携带origin这个参数名,参数值符合 [a-zA-Z]+ 这个正则
            - Query=origin,[a-zA-Z]+
            #请求的方式
            - Method=get,post
            # 设置时间区间内访问
            - After=2019-11-12T00:00:00+08:00[Asia/Shanghai]
            - Before=2020-12-31T00:00:00+08:00[Asia/Shanghai]
            # 描述从172.18.96.1~172.18.96.255的ip地址才可以访问
#           - RemoteAddr=172.18.96.0/24
            # 请求的头中必须得携带 token, value值符合 [a-zA-Z0-9]+
#           - Header=token,[a-zA-Z0-9]+

server:
  port: 9090

id: 可以配置很多的路由信息,但是每个路由都有一个唯一的id来标识。
uri: 转发的地址,lb://开头标识转发到微服务的内部的某个服务。
predicates: 配置谓词。
Path: 配置请求的路径
Query: 请求的参数
Method: 请求的方式,大小写不敏感
After: 在指定的时间之后
Before: 在指定的时间之前
RemoteAddr: 请求的IP的来源
Header: 请求头

1.3 过滤器配置

GateWay提供了很多内置的过滤器让我们使用,具体的过滤器在spring-cloud-gateway-core-2.1.2.RELEASE.jar下的org.springframework.cloud.gateway.filter.factory包下,接下来我们挑其中一个非常常用的过滤来讲解用法,在实际的开发过程中,有这样一种业务需求,就是限制同一个IP对服务器频繁的请求,例如我们限制每个IP在每秒只能访问3次,那么要怎么实现呢?其实spring-boot已经帮我们实现好了一个,只需要做一定的配置就可以了。
IP限制的原理就是令牌桶算法,随着时间流逝,系统会按恒定 1/QPS 时间间隔(如果 QPS=100,则间隔是 10ms)往桶里加入 Token,如果桶已经满了就不再加了。新请求来临时,会各自拿走一个 Token,如果没有 Token 可拿了就阻塞或者拒绝服务。如下图所示:
图片.png
1.在springcloud-alibaba-microservice-gateway-9090工程中导入依赖

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

2.创建HostAddrKeyResolver类,实现获取IP的组件

package com.qf.resolver;

import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
public class RedisHostKeyResovler implements KeyResolver {

    /**
     * webflux:
     *      Mono: 用于返回单个值
     *      Flux: 用于返回集合数据
     */
    @Override
    public Mono<String> resolve(ServerWebExchange exchange) {
        //获取用户的访问的 ip
        String host = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
        return Mono.just(host); //构建 Mono<String>
    }
}

3.在application.yml中增加配置

spring:
  application:
    # 网关名称
    name: microservice-gateway

  cloud:
    nacos:
      discovery:
        enabled: true
        server-addr: 127.0.0.1:8848
        #表示是否将服务注册到nacos上,false表示不注册,默认true
        register-enabled: false
    gateway:
      discovery:
        locator:
          # 开启之后可以使用网关地址进行访问
          # 之前:http://localhost:8080/feign/getAll
          # 现在:http://localhost:9090/microservice-consumer/feign/getAll
          enabled: true
      routes:
        # id可以不为服务名,名字随意,但是不能重复。推荐使用服务名的方式
        - id: microservice-consumer
          # uri才是控制着某个具体的访问到达我们特定的服务
          uri: lb://microservice-consumer
          # 谓词: 就是满足的条件. 可以在 org.springframework.cloud.gateway.handler.predicate 这个包下
          predicates:
            # 配置controller的一级目录,即可通过http://localhost:9090/feign/getAll来访问
            - Path=/feign/**
            # 请求的参数中必须携带origin这个参数名,参数值符合 [a-zA-Z]+ 这个正则
            - Query=origin,[a-zA-Z]+
            #请求的方式
            - Method=get,post
            # 设置时间区间内访问
            - After=2019-11-12T00:00:00+08:00[Asia/Shanghai]
            - Before=2020-12-31T00:00:00+08:00[Asia/Shanghai]
            # 描述从172.18.96.1~172.18.96.255的ip地址才可以访问
#            - RemoteAddr=172.18.96.0/24
            # 请求的头中必须得携带 token, value值符合 [a-zA-Z0-9]+
#            - Header=token,[a-zA-Z0-9]+
          filters:
            # RequestRateLimiter是固定值
            - name: RequestRateLimiter
              args:
                # key-resolver是用于限流的bean对象,通过SpEL的方式 #{@XXX} 取出spring容器中的bean
                keyResolver: '#{@redisHostKeyResovler}'
                # 每秒往令牌桶中存放的数量
                redis-rate-limiter.replenishRate: 1
                # 令牌桶中最多的令牌的数量
                redis-rate-limiter.burstCapacity: 3
  redis:
    port: 6379
    host: localhost

server:
  port: 9090

4.可以让局域网内的其他用户访问本机:http://本机ip:9090/feign/getUsers?origin=abc

1.4 自定义全局过滤器

1.创建全局过滤器类

package com.qf.filter;

import com.alibaba.fastjson.JSONObject;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Configuration
public class FilterConfig {

    @Bean
    @Order(-100)//过滤器加载顺序,负数值越大,正数值越小,则先执行,尽量使用负值,避免与系统自带的过滤器冲突
    public GlobalFilter loginFilter() {
        return new GlobalFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                // TODO 登录放过

                System.out.println("登录的全局过滤器");
                ServerHttpRequest request = exchange.getRequest();

                // key是字符串, value值是list的集合
                MultiValueMap<String, String> multiValueMap = request.getQueryParams();

                List<String> usernameList = multiValueMap.get("username");
                List<String> passwordList = multiValueMap.get("password");

                ServerHttpResponse response = exchange.getResponse();

                Map<String, Object> result = new HashMap<>();

                // 设置响应的状态码
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                // 设置响应的头信息
                response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");

                // 请求的参数中没有用户名和密码
                if(null == usernameList || usernameList.size() == 0 || null == passwordList || passwordList.size() == 0) {
                    result.put("code", -1);
                    result.put("msg", "username and password is required.");
                    // 得到json数据
                    byte[] bs = JSONObject.toJSONString(result).getBytes(Charset.defaultCharset());
                    // 获取 DataBuffer
                    DataBuffer dataBuffer = response.bufferFactory().wrap(bs);

                    return response.writeWith(Mono.just(dataBuffer));
                }else {
                    String username = usernameList.get(0);
                    String password = passwordList.get(0);

                    if("jack".equals(username) && "123".equals(password)) {
                        return chain.filter(exchange); // 接着往后走
                    }else {
                        result.put("code", -1);
                        result.put("msg", "login fail");

                        byte[] bs = JSONObject.toJSONString(result).getBytes(Charset.defaultCharset());
                        // 获取 DataBuffer
                        DataBuffer dataBuffer = response.bufferFactory().wrap(bs);

                        return response.writeWith(Mono.just(dataBuffer));
                    }
                }
            }
        };
    }

    @Bean
    @Order(-99)
    public GlobalFilter otherFilter() {
        return new GlobalFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                System.out.println("其他的全局过滤器");
                return chain.filter(exchange);
            }
        };
    }
}

2.浏览器访问:http://localhost:9090/feign/getUsers?origin=qwer&username=jack&password=123 进行测试

二. nacos配置管理路径

配置管理,就是将所有的微服务的配置统一进行管理,这样做的好处是我们的配置不用写在项目中,实现集中化的管理,方便环境的变更。

2.1 基本配置

1.在springcloud-alibaba-microservice-gateway-9090和springcloud-alibaba-microservice-consumer-8080工程中都导入依赖

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

将原来的application.yml拷贝到手动创建的文件夹并打包,然后设置失效,即重新命名为application.yml.bak,创建bootstrap.yml配置文件来连接nacos,需要在springcloud-alibaba-microservice-gateway-9090和springcloud-alibaba-microservice-consumer-8080工程中都配置bootstrap.yml,分别为

spring:
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848
        # 配置文件的后缀名
        file-extension: yml
        # 配置在nacos上的组名
        group: microservice-gateway
  application:
    # 服务名称
    name: microservice-gateway
  profiles:
    # 配置文件环境(生产环境,开发环境,测试环境等等,对应不同的application-*.yml文件)
    active: dev

在nacos中新建配置文件的时候,文件名的命名规则是 ${spring.application.name}-${spring.profile.active}.yml,对应到上面的配置就是 microservice-gateway-dev.yml(参考课件提供的配置文件)
图片.png

2.2 配置的实时刷新

1.在Controller中添加方法并测试,修改springcloud-alibaba-microservice-consumer-8080中FeignUserController

@Value("${user.username}")
private String username;

@RequestMapping("getUserName")
public String getUsername(){
    return username;
}

2.在nacos面板中分别修改microservice-consumer-dev.yml和microservice-consumer-test.yml
图片.png
图片.png
3.修改springcloud-alibaba-microservice-consumer-8080中bootstrap.yml的配置
4.重启springcloud-alibaba-microservice-consumer-8080进行测试,如果访问不到,可在FeignUserController上添加@RefreshScope再次测试

三. 链路追踪

微服务架构是一个分布式架构,它按业务划分服务单元,一个分布式系统往往有很多个服务单元。由于服务单元数量众多,业务的复杂性,如果出现了错误和异常,很难去定位。主要体现在,一个请求可能需要调用很多个服务,而内部服务的调用复杂性,决定了问题难以定位。所以微服务架构中,必须实现分布式链路追踪,去跟进一个请求到底有哪些服务参与,参与的顺序又是怎样的,从而达到每个请求的步骤清晰可见,出了问题,很快定位。
举个例子,在微服务系统中,一个来自用户的请求,请求先达到前端A(如前端界面),然后通过远程调用,达到系统的中间件B、C(如负载均衡、网关等),最后达到后端服务D、E,后端经过一系列的业务逻辑计算最后将数据返回给用户。对于这样一个请求,经历了这么多个服务,怎么样将它的请求过程的数据记录下来呢?这就需要用到服务链路追踪。
在本章中我们主要会讲解spring cloud sleuth整合Zipkin来实现链路的追踪,整合起来非常的简单,只需要引入相关的依赖,再加上相关的配置即可。

3.1 Sleuth基本术语

span: 基本工作单元,发送一个远程调度任务 就会产生一个Span,Span是一个64位ID唯一标识的,Trace是用另一个64位ID唯一标识的,Span还有其他数据信息,比如摘要、时间戳事件、Span的ID、以及进度ID。
Trace: 一系列Span组成的一个树状结构。请求一个微服务系统的API接口,这个API接口,需要调用多个微服务,调用每个微服务都会产生一个新的Span,所有由这个请求产生的Span组成了这个Trace。
Annotation:用来及时记录一个事件的,一些核心注解用来定义一个请求的开始和结束 。这些注解包括以下:

  • cs - Client Sent -客户端发送一个请求,这个注解描述了这个Span的开始
  • sr - Server Received -服务端获得请求并准备开始处理它,如果将其sr减去cs时间戳便可得到网络传输的时间。
  • ss - Server Sent (服务端发送响应)–该注解表明请求处理的完成(当请求返回客户端),如果ss的时间戳减去sr时间戳,就可以得到服务器请求的时间。
  • cr - Client Received (客户端接收响应)-此时Span的结束,如果cr的时间戳减去cs时间戳便可以得到整个请求所消耗的时间。

Zipkin是Twitter开源的分布式跟踪系统,主要用来收集系统的时序数据,从而跟踪系统的调用问题。

3.2 链路追踪的搭建

Zipkin Server的下载地址:https://github.com/openzipkin/zipkin
1.启动Zipkin Server,命令如下:java -jar zipkin-server-2.19.1-exec.jar,访问:http://localhost:9411/
图片.png
2.在springcloud-alibaba-microservice-gateway-9090中添加依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zipkin</artifactId>
    <version>2.2.1.RELEASE</version>
</dependency>

3.在nacos面板中分别修改microservice-consumer-dev.yml和microservice-gateway-dev.yml中配置如下内容,并分别在各自工程中的bootstrap.yml中指定调用该文件

spring:
  zipkin:
    base-url: http://localhost:9411/
    discovery-client-enabled: false
  sleuth:
    sampler:
    # 抽样率 100%, 默认10%
    probability: 0.2

D. 查看结果
图片.png

四.Jwt

4.1 介绍

JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的,常用于单点登录。

4.2 使用场景

  • Authorization (授权) : 这是使用JWT的最常见场景。一旦用户登录,后续每个请求都将包含JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是现在广泛使用的JWT的一个特性,因为它的开销很小,并且可以轻松地跨域使用。
  • Information Exchange (信息交换) : 对于安全的在各方之间传输信息而言,JSON Web Tokens无疑是一种很好的方式。因为JWTs可以被签名,例如,用公钥/私钥对,你可以确定发送人就是它们所说的那个人。另外,由于签名是使用头和有效负载计算的,您还可以验证内容没有被篡改。

    4.3 结构

    JSON Web Token由三部分组成,它们之间用圆点(.)连接。这三部分分别是:头部,载荷,签名

  • Header

  • Payload
  • Signature

    4.4 身份认证

    HTTP协议是无状态的,也就是说,如果我们已经认证了一个用户,那么他下一次请求的时候,服务器不知道我是谁,我们必须再次认证.
    传统的做法是将已经认证过的用户信息存储在服务器上,比如Session。用户下次请求的时候带着Session ID,然后服务器以此检查用户是否认证过。
    这种基于服务器的身份认证方式存在一些问题:

  • Sessions : 每次用户认证通过以后,服务器需要创建一条记录保存用户信息,通常是在内存中,随着认证通过的用户越来越多,服务器的在这里的开销就会越来越大。

  • Scalability : 由于Session是在内存中的,这就带来一些扩展性的问题。
  • CORS : 当我们想要扩展我们的应用,让我们的数据被多个移动设备使用时,我们必须考虑跨资源共享问题。当使用AJAX调用从另一个域名下获取资源时,我们可能会遇到禁止请求的问题。
  • CSRF : 用户很容易受到CSRF攻击。

使用Jwt的好处:

  • 无状态和可扩展性:Tokens存储在客户端。完全无状态,可扩展。我们的负载均衡器可以将用户传递到任意服务器,因为在任何地方都没有状态或会话信息。
  • 安全:Token不是Cookie。(The token, not a cookie.)每次请求的时候Token都会被发送。而且,由于没有Cookie被发送,还有助于防止CSRF攻击。即使在你的实现中将token存储到客户端的Cookie中,这个Cookie也只是一种存储机制,而非身份认证机制。没有基于会话的信息可以操作,因为我们没有会话!

    4.5 JWT入门

    导入依赖
    <dependency>
      <groupId>io.jsonwebtoken</groupId>
      <artifactId>jjwt-impl</artifactId>
      <version>0.11.2</version>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>io.jsonwebtoken</groupId>
      <artifactId>jjwt-api</artifactId>
      <version>0.11.2</version>
    </dependency>
    <dependency>
      <groupId>io.jsonwebtoken</groupId>
      <artifactId>jjwt-jackson</artifactId>
      <version>0.11.2</version>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-pool2</artifactId>
      <version>2.7.0</version>
    </dependency>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-text</artifactId>
      <version>1.8</version>
    </dependency>
    
    测试 ```java package com.qf.jwt;

import com.auth0.jwt.JWT; import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.DecodedJWT; import org.junit.Test;

import java.util.Calendar; import java.util.HashMap;

public class TestJwt {

@Test
public void createJwt(){
    //头部信息(一般不传,使用默认值)
    HashMap<String, Object> map = new HashMap<>();
    map.put("alg","HS256");
    map.put("typ","JWT");

    //时间对象
    Calendar calendar = Calendar.getInstance();
    calendar.add(Calendar.SECOND,30);
    //创建Jwt
    String token = JWT.create()
            .withExpiresAt(calendar.getTime())//过期时间
            .withHeader(map)//头部信息
            .withClaim("username", "张三")//载荷
            .withClaim("num", 12345)
            .sign(Algorithm.HMAC512("qwer"));//签名
    //输出
    System.out.println(token);
}

@Test
public void verifyJwt(){
    //算法和签名要和之前一致
    JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC512("qwer")).build();
    //校验
    DecodedJWT decodedJWT = jwtVerifier.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJudW0iOjEyMzQ1LCJleHAiOjE2MTg4NTE0MDksInVzZXJuYW1lIjoi5byg5LiJIn0.MNsbA-X0MZDSqdxTx2fw8R6DBE26TtieSNXRDd7Jj757Yi1GoPeXLULjhxREzER5AOpcuK8zHYGF0biLgjTvIQ");
    //输出
    System.out.println(decodedJWT.getHeader());
    System.out.println(decodedJWT.getPayload());
    System.out.println(decodedJWT.getSignature());
    System.out.println(decodedJWT.getToken());
    System.out.println(decodedJWT.getClaim("username").asString());
    System.out.println(decodedJWT.getClaim("num").asInt());
}

}

Jwt工具类
```java
package com.qf.jwt;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;

import java.util.Calendar;
import java.util.Map;

public class JwtUtils {

    private static final String signature = "qwer";

    public static String createJwt(Map<String,String> map){
        //时间对象
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.SECOND,3600);//默认一小时过期
        //获取Builder对象,目的将集合中的数据,都存储到Jwt的载荷中
        JWTCreator.Builder builder = JWT.create();
        //遍历并赋值
        map.forEach( (k,v) -> { builder.withClaim(k,v);});
        //获取Jwt
        String token = builder.withExpiresAt(calendar.getTime()).sign(Algorithm.HMAC256(signature));
        //返回
        return token;
    }

    public static DecodedJWT verifyJwt(String token){
        return JWT.require(Algorithm.HMAC256(signature)).build().verify(token);
    }
}

4.5 jjwt

JJWT的是在JVM上创建和验证JSON Web Token(JWTs)的库,基于JWT、JWS、JWE、JWK和JWA RFC规范的Java实现。
1.导入依赖

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.2</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.2</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.11.2</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.7.0</version>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-text</artifactId>
    <version>1.8</version>
</dependency>

2.运行Jwt工具类,访问 https://jwt.io/ 测试

package com.qf.jwt;

import io.jsonwebtoken.*;
import org.apache.commons.lang3.time.DateUtils;
import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.util.Date;

public class JwtHelper {

    // 生成Jwt
    public static String jwsWithHS(SignatureAlgorithm signatureAlgorithm, String userInfo, int expire, String secret) {
        Key key = new SecretKeySpec(secret.getBytes(), signatureAlgorithm.getJcaName());
        Claims claims = Jwts.claims();
        claims.setSubject(userInfo);  // jwt的信息的本身
        claims.setExpiration(DateUtils.addSeconds(new Date(), expire)); //jwt多久过期
        String jws = Jwts.builder().setClaims(claims).signWith(key, signatureAlgorithm).compact();
        return jws;
    }

    // 校验Jwt
    public static Jwt verifySign(String jws, String secret, SignatureAlgorithm signatureAlgorithm) {
        Key key = new SecretKeySpec(secret.getBytes(), signatureAlgorithm.getJcaName());
        Jwt jwt = Jwts.parserBuilder().setSigningKey(key).build().parse(jws);
        return jwt;
    }

    public static void main(String[] args) {
        //通过密文生成jwt,然后可以去官网进行校验
        String jwtCode = jwsWithHS(SignatureAlgorithm.HS256, "张三", 600, "202cb962ac59075b964b07152d234b70");
        System.out.println(jwtCode);

        //使用生成的jwtCode进行校验
        Jwt jwt = verifySign(
                "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiLlvKDkuIkiLCJleHAiOjE2MTAwMzA2ODB9.stYoxYmkoQRtpS0JADo2gOGhPazJOWe50wovZv9CBBI",
                "202cb962ac59075b964b07152d234b70", SignatureAlgorithm.HS256);

        //通过Jwt对象获取信息
        System.out.println(jwt);
        System.out.println(jwt.getBody());//用户信息
    }
}

4.6 使用Jwt鉴权校验

1.在springcloud-alibaba-microservice-gateway-9090工程创建鉴权过滤器

package com.qf.filter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
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.Mono;

import java.util.HashMap;
import java.util.Map;

/**
 * 鉴权过滤
 */
@Component  // 让AuthFilter 加入到容器中
public class AuthFilter implements GlobalFilter, Ordered {

    @Value("${jwt.signature-algorithm}")
    private String signatureAlgorithm;//HS256算法

    @Value("${jwt.secret}")
    private String secret;//密文

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 如果是登陆接口不拦截
        if (exchange.getRequest().getPath().toString().contains("login")) {
            // 放行 不拦截
            return chain.filter(exchange);
        }
        //获取jwt
        String token = exchange.getRequest().getHeaders().getFirst("token");
        //判断token
        if (token == null) {//进行拦截
            ServerHttpResponse response = exchange.getResponse();
            Map<String, Object> map = new HashMap<>();
            map.put("code", 401);
            map.put("message", "未登录");
            try {
                // 将信息转换为 JSON
                ObjectMapper objectMapper = new ObjectMapper();
                byte[] data = objectMapper.writeValueAsBytes(map);

                // 输出错误信息到页面
                DataBuffer buffer = response.bufferFactory().wrap(data);
                response.setStatusCode(HttpStatus.UNAUTHORIZED);

                // 让浏览器解析为json
                response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
                return response.writeWith(Mono.just(buffer));
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }

        // 放行 不拦截
        return chain.filter(exchange);
    }

    /**
     * order  决定者当前  Filer 执行顺序,数值愈小,越优先, Filter 越优先执行
     */
    @Override
    public int getOrder() {
        // 所有过滤器执行完了 当前过滤器才再次执行
        return 1;
    }
}

2.在springcloud-alibaba-microservice-gateway-9090工程中application.yml添加配置:

jwt:
  secret: 21232f297a57a5a743894a0e4a801fc3
  signature-algorithm: HS256

3.在springcloud-alibaba-microservice-commons工程中创建JsonResult实体类

package com.qf.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class JsonResult {
    private Integer code;
    private String msg;
    private Object data;
}

4.创建子工程springcloud-alibaba-microservice-authentic-6060并添加依赖

<?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>springcloud-alibaba-microservice-manager</artifactId>
        <groupId>com.qf</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>springcloud-alibaba-microservice-authentic-6060</artifactId>

    <dependencies>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
            <version>2.2.1.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
            <version>2.2.1.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.2.9.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <version>2.2.1.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>com.qf</groupId>
            <artifactId>springcloud-alibaba-microservice-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
            <version>2.2.3.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
            <version>2.2.1.RELEASE</version>
        </dependency>

    </dependencies>

    <dependencyManagement>
        <dependencies>

            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.2.1.RELEASE</version>
            </dependency>

            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR6</version>
            </dependency>

        </dependencies>
    </dependencyManagement>

</project>

5.在springcloud-alibaba-microservice-authentic-6060工程中配置application.yml

server:
  port: 6060

spring:
  zipkin:
    base-url: http://localhost:9411/
    discovery-client-enabled: false
  sleuth:
    sampler:
        # 抽样率 100%, 默认10%
    probability: 0.2

  application:
    # 注册到注册中心的服务名
    name: authentic

  cloud:
    nacos:
      discovery:
        # 开启nacos的服务发现
        enabled: true
        server-addr: 127.0.0.1:8848,127.0.0.1:8849

  redis:
    host: localhost
    port: 6379

jwt:
  secret: 21232f297a57a5a743894a0e4a801fc3
  signature-algorithm: HS256

6.在springcloud-alibaba-microservice-authentic-6060工程中创建启动类

package com.qf;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Authentic {
    public static void main(String[] args) {
        SpringApplication.run(Authentic.class,args);
    }
}

7.在springcloud-alibaba-microservice-authentic-6060工程中创建LoginController

package com.qf.controller;

import com.qf.jwt.JwtHelper;
import com.qf.pojo.JsonResult;
import com.qf.redis.RedisPrefix;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;

@RestController
@RequestMapping("/auth")
public class LoginController {

    @Value("${jwt.signature-algorithm}")
    private String signatureAlgorithm;

    @Value("${jwt.secret}")
    private String secret;

    @Autowired
    private RedisTemplate redisTemplate;//保存token到redis,用于换设备登录校验(此处省略未写)

    @RequestMapping("/login")
    public JsonResult login(String username, String password, HttpServletResponse response) {

        JsonResult jsonResult = new JsonResult();

        if("jack".equals(username) && "123".equals(password)) {

            //使用系统变量存储JWT的获取方式
            //String secret = System.getenv().get("JWT_SECRET");

            // 生成token
            String jwtString = JwtHelper.jwsWithHS(SignatureAlgorithm.forName(signatureAlgorithm), username, 1200, secret);

            response.addHeader("token", jwtString);

            //保存到redis中,更换密文时,需要比对
            //redisTemplate.opsForValue().set(RedisPrefix.AuthenticRedisPrefix.USERTOKEN+"jack",jwtString);

            jsonResult.setCode(200);
            jsonResult.setMsg("success");
            jsonResult.setData(jwtString);
        }else{
            jsonResult.setCode(500);
            jsonResult.setMsg("error");
            jsonResult.setData("user is invalid");
        }

        return jsonResult;
    }
}

启动并测试:http://localhost:9090/feign/findAll?origin=qwer 由于Header中未携带tonken则无法访问
再次访问:http://localhost:6060/auth/login?username=jack&password=123 获取token后再次携带token访问

令牌桶算法和漏桶算法

这两种算法的主要区别在于“漏桶算法”能够强行限制数据的传输速率,而“令牌桶算法”在能够限制数据的平均传输速率外,还允许某种程度的突发传输。在“令牌桶算法”中,只要令牌桶中存在令牌,那么就允许突发地传输数据直到达到用户配置的门限,因此它适合于具有突发特性的流量。