微服务网关

基本概念

对于微服务的各个服务一般会有不同的地址,外部客户端的一个服务可能要调用诸多的接口,这会带来以下的问题

  • 客户端会多次请求不同的微服务,地址复杂
  • 存在跨域请求,处理复杂
  • 认证复杂
  • 难以重构
  • 某些微服务可能会存在对浏览器不友好的通信协议,交互起来也复杂

加入微服务网关之后的系统架构就如图所示

优点如下:

  • 安全 ,只有网关系统对外进行暴露,微服务可以隐藏在内网,通过防火墙保护。
  • 易于监控。可以在网关收集监控数据并将其推送到外部系统进行分析。
  • 易于认证。可以在网关上进行认证,然后再将请求转发到后端的微服务,而无须在每个微服务中进行认证。
  • 减少了客户端与各个微服务之间的交互次数
  • 易于统一授权。

总结:微服务网关就是一个系统,通过暴露该微服务网关系统,方便我们进行相关的鉴权,安全控制,日志统一处理,易于监控的相关功能。

网关技术

常见的网关技术

  • nginx Nginx (tengine x) 是一个高性能的HTTP和反向代理web服务器,同时也提供了IMAP/POP3/SMTP服务
  • zuul ,Zuul 是 Netflix 出品的一个基于 JVM 路由和服务端的负载均衡器。
  • spring-cloud-gateway, 是spring 出品的 基于spring 的网关项目,集成断路器,路径重写,性能比Zuul好。

Zuul与Gateway对比

  • Gateway是SpringCloud团队所开发的,SpringCloud能够进行很好的整合。Zuul经历过一段时间的维护,Gateway就是在这期间开发的,而如今虽然Zuul发布了2.x版本但是SpringCloud并没有整合的计划
  • Gateway采用的是异步非阻塞模型AIO,在高并发和非阻塞响应当中有很大的优势
  • Gateway的三个核心功能、路由、断言、过滤

网关使用

需求分析

由于我们开发的系统 有包括前台系统和后台系统,后台的系统 给管理员使用。那么也需要调用各种微服务,所以我们针对 系统管理搭建一个网关系统。分析如下:

搭建后台网关系统

搭建分析

由上可知道,由于 需要有多个网关,所以为了管理方便。我们新建一个项目,打包方式为pom,在里面建立各种网关系统模块即可。如图所示:

(1)引入依赖

修改changgou-gateway工程,打包方式为pom

pom.xml如下:

<?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>changgou-parent</artifactId><groupId>com.changgou</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>changgou-gateway</artifactId><packaging>pom</packaging><modules><module>changgou-gateway-web</module></modules><!--网关依赖--><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-hystrix</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency></dependencies></project>

在changgou-gateway工程中,创建 changgou-gateway-web工程,该网关主要用于对后台微服务进行一个调用操作,将多个微服务串联到一起。

pom.xml:

<?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>changgou-gateway</artifactId><groupId>com.changgou</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>changgou-gateway-web</artifactId><description>普通web请求网关</description>
</project>

(2)引导类

在changgou-gateway-web中创建一个引导类com.changgou.GatewayWebApplication,代码如下:

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

(3)application.yml配置

在changgou-gateway-web的resources下创建application.yml,代码如下:

spring:application:name: gateway-web
server:port: 8001
eureka:client:service-url:defaultZone: http://127.0.0.1:7001/eurekainstance:prefer-ip-address: true
management:endpoint:gateway:enabled: trueweb:exposure:include: true

跨域配置

有时候,我们需要对所有微服务跨域请求进行处理,则可以在gateway中进行跨域支持。修改application.yml,添加如下代码:

spring:cloud:gateway:globalcors:cors-configurations:'[/**]': # 匹配所有请求allowedOrigins: "*" #跨域处理 允许所有的域allowedMethods: # 支持的方法- GET- POST- PUT- DELETE

网关过滤配置


路由过滤器允许以某种方式修改传入的HTTP请求或传出的HTTP响应。 路径过滤器的范围限定为特定路径。 Spring Cloud Gateway包含许多内置的GatewayFilter工厂。如上图,根据请求路径路由到不同微服务去,这块可以使用Gateway的路由过滤功能实现。

过滤器 有 20 多个 实现 类, 包括 头部 过滤器、 路径 类 过滤器、 Hystrix 过滤器 和 变更 请求 URL 的 过滤器, 还有 参数 和 状态 码 等 其他 类型 的 过滤器。

内置的过滤器工厂有22个实现类,包括 头部过滤器、路径过滤器、Hystrix 过滤器 、请求URL 变更过滤器,还有参数和状态码等其他类型的过滤器。根据过滤器工厂的用途来划分,可以分为以下几种:Header、Parameter、Path、Body、Status、Session、Redirect、Retry、RateLimiter和Hystrix。

Host 路由

比如用户请求cloud.itheima.com的时候,可以将请求路由给http://localhost:18081服务处理,如下配置:

上图配置如下:

      routes:- id: changgou_goods_routeuri: http://localhost:18081predicates:- Host=cloud.itheima.com**

测试请求http://cloud.itheima.com:8001/brand,效果如下:

注意:此时要想让cloud.itheima.com访问本地计算机,要配置C:\Windows\System32\drivers\etc\hosts文件,映射配置如下:

127.0.0.1 cloud.itheima.com

路径匹配过滤配置

我们还可以根据请求路径实现对应的路由过滤操作,例如请求中以/brand/路径开始的请求,都直接交给http://localhost:180801服务处理,如下配置:

上图配置如下:

      routes:- id: changgou_goods_routeuri: http://localhost:18081predicates:- Path=/brand**

测试请求http://localhost:8001/brand,效果如下:

PrefixPath 过滤配置

用户每次请求路径的时候,我们可以给真实请求加一个统一前缀,例如用户请求http://localhost:8001的时候我们让它请求真实地址http://localhost:8001/brand,如下配置:

上图配置如下:

      routes:- id: changgou_goods_routeuri: http://localhost:18081predicates:#- Host=cloud.itheima.com**- Path=/**filters:- PrefixPath=/brand

测试请求http://localhost:8001/效果如下:

StripPrefix 过滤配置

很多时候也会有这么一种请求,用户请求路径是/api/brand,而真实路径是/brand,这时候我们需要去掉/api才是真实路径,此时可以使用SttripPrefix功能来实现路径的过滤操作,如下配置:

上图配置如下:

      routes:- id: changgou_goods_routeuri: http://localhost:18081predicates:#- Host=cloud.itheima.com**- Path=/**filters:#- PrefixPath=/brand- StripPrefix=1

测试请求http://localhost:8001/api/brand,效果如下:

LoadBalancerClient 路由过滤器(客户端负载均衡)

上面的路由配置每次都会将请求给指定的URL处理,但如果在以后生产环境,并发量较大的时候,我们需要根据服务的名称判断来做负载均衡操作,可以使用LoadBalancerClientFilter来实现负载均衡调用。LoadBalancerClientFilter会作用在url以lb开头的路由,然后利用loadBalancer来获取服务实例,构造目标requestUrl,设置到GATEWAY_REQUEST_URL_ATTR属性中,供NettyRoutingFilter使用。

修改application.yml配置文件,代码如下:

上图配置如下:

      routes:- id: changgou_goods_route#uri: http://localhost:18081uri: lb://goodspredicates:#- Host=cloud.itheima.com**- Path=/**filters:#- PrefixPath=/brand- StripPrefix=1

测试请求路径http://localhost:8001/api/brand

网关限流

网关可以做很多的事情,比如,限流,当我们的系统 被频繁的请求的时候,就有可能 将系统压垮,所以 为了解决这个问题,需要在每一个微服务中做限流操作,但是如果有了网关,那么就可以在网关系统做限流,因为所有的请求都需要先通过网关系统才能路由到微服务中。

思路分析

令牌桶算法

令牌桶算法是比较常见的限流算法之一,大概描述如下:

  1. 所有的请求在处理之前都需要拿到一个可用的令牌才会被处理;
  2. 根据限流大小,设置按照一定的速率往桶里添加令牌;
  3. 桶设置最大的放置令牌限制,当桶满时、新添加的令牌就被丢弃或者拒绝;
  4. 请求达到后首先要获取令牌桶中的令牌,拿着令牌才可以进行其他的业务逻辑,处理完业务逻辑之后,将令牌直接删除;
  5. 令牌桶有最低限额,当桶中的令牌达到最低限额的时候,请求处理完之后将不会删除令牌,以此保证足够的限流

如下图:

这个算法的实现,有很多技术,Guaua是其中之一,redis客户端也有其实现。四种常见的限流算法在畅购商城三——Lua与canal实现广告缓存的部分有所提及

使用令牌桶进行请求次数限流

spring cloud gateway 默认使用redis的RateLimter限流算法来实现。所以我们要使用首先需要引入redis的依赖

(1)引入redis依赖

在changgou-gateway的pom.xml中引入redis的依赖

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

(2)定义KeyResolver

在Applicatioin引导类中添加如下代码,KeyResolver用于计算某一个类型的限流的KEY也就是说,可以通过KeyResolver来指定限流的Key。

我们可以根据IP来限流,比如每个IP每秒钟只能请求一次,在GatewayWebApplication定义key的获取,获取客户端IP,将IP作为key,如下代码:

/**** IP限流* @return*/
@Bean(name="ipKeyResolver")
public KeyResolver userKeyResolver() {return new KeyResolver() {@Overridepublic Mono<String> resolve(ServerWebExchange exchange) {//获取远程客户端IPString hostName = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();System.out.println("hostName:"+hostName);return Mono.just(hostName);}};
}

(3)修改application.yml中配置项,指定限制流量的配置以及REDIS的配置,如图

修改如下图:


配置代码如下:

spring:cloud:gateway:globalcors:corsConfigurations:'[/**]': # 匹配所有请求allowedOrigins: "*" #跨域处理 允许所有的域allowedMethods: # 支持的方法- GET- POST- PUT- DELETEroutes:- id: changgou_goods_routeuri: lb://goodspredicates:- Path=/api/brand**filters:- StripPrefix=1- name: RequestRateLimiter #请求数限流 名字不能随便写 ,使用默认的facatoryargs:key-resolver: "#{@ipKeyResolver}"redis-rate-limiter.replenishRate: 1redis-rate-limiter.burstCapacity: 1application:name: gateway-web#Redis配置redis:host: 192.168.211.132port: 6379server:port: 8001
eureka:client:service-url:defaultZone: http://127.0.0.1:7001/eurekainstance:prefer-ip-address: true
management:endpoint:gateway:enabled: trueweb:exposure:include: true

解释:

redis-rate-limiter.replenishRate是您希望允许用户每秒执行多少请求,而不会丢弃任何请求。这是令牌桶填充的速率

redis-rate-limiter.burstCapacity是指令牌桶的容量,允许在一秒钟内完成的最大请求数,将此值设置为零将阻止所有请求。

key-resolver: "#{@ipKeyResolver}"用于通过SPEL表达式来指定使用哪一个KeyResolver.

如上配置:

表示 一秒内,允许 一个请求通过,令牌桶的填充速率也是一秒钟添加一个令牌。

最大突发状况 也只允许 一秒内有一次请求,可以根据业务来调整 。

多次请求会发生如下情况

用户登录

用户信息表tb_user

CREATE TABLE `tb_user` (`username` varchar(50) NOT NULL COMMENT '用户名',`password` varchar(100) NOT NULL COMMENT '密码,加密存储',`phone` varchar(20) DEFAULT NULL COMMENT '注册手机号',`email` varchar(50) DEFAULT NULL COMMENT '注册邮箱',`created` datetime NOT NULL COMMENT '创建时间',`updated` datetime NOT NULL COMMENT '修改时间',`source_type` varchar(1) DEFAULT NULL COMMENT '会员来源:1:PC,2:H5,3:Android,4:IOS',`nick_name` varchar(50) DEFAULT NULL COMMENT '昵称',`name` varchar(50) DEFAULT NULL COMMENT '真实姓名',`status` varchar(1) DEFAULT NULL COMMENT '使用状态(1正常 0非正常)',`head_pic` varchar(150) DEFAULT NULL COMMENT '头像地址',`qq` varchar(20) DEFAULT NULL COMMENT 'QQ号码',`is_mobile_check` varchar(1) DEFAULT '0' COMMENT '手机是否验证 (0否  1是)',`is_email_check` varchar(1) DEFAULT '0' COMMENT '邮箱是否检测(0否  1是)',`sex` varchar(1) DEFAULT '1' COMMENT '性别,1男,0女',`user_level` int(11) DEFAULT NULL COMMENT '会员等级',`points` int(11) DEFAULT NULL COMMENT '积分',`experience_value` int(11) DEFAULT NULL COMMENT '经验值',`birthday` datetime DEFAULT NULL COMMENT '出生年月日',`last_login_time` datetime DEFAULT NULL COMMENT '最后登录时间',PRIMARY KEY (`username`),UNIQUE KEY `username` (`username`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表';

用户微服务

(1)公用API

(2)依赖

在changgou-service-user的pom.xml引入如下依赖:

<?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>changgou-service</artifactId><groupId>com.changgou</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>changgou-service-user</artifactId><!--依赖--><dependencies><dependency><groupId>com.changgou</groupId><artifactId>changgou-service-user-api</artifactId><version>1.0-SNAPSHOT</version></dependency></dependencies>
</project>

(3)启动类创建

在changgou-service-user微服务中创建启动类com.changgou.UserApplication,代码如下:

@SpringBootApplication
@EnableEurekaClient
@MapperScan("com.changgou.user.dao")
public class UserApplication {public static void main(String[] args) {SpringApplication.run(UserApplication.class,args);}
}

(4)application.yml配置

在changgou-service-user的resources中创建application.yml配置,代码如下:

server:port: 18089
spring:application:name: userdatasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://192.168.211.132:3306/changgou_user?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTCusername: rootpassword: 123456
eureka:client:service-url:defaultZone: http://127.0.0.1:7001/eurekainstance:prefer-ip-address: true
feign:hystrix:enabled: true

登录业务逻辑

登录的时候,需要进行密码校验,这里采用了BCryptPasswordEncoder进行加密,需要将资料中的BCrypt导入到common工程中,其中BCrypt.checkpw(“明文”,“密文”)用于对比密码是否一致。

修改changgou-service-user的com.changgou.user.controller.UserController添加登录方法,代码如下:

/**** 用户登录*/
@RequestMapping(value = "/login")
public Result login(String username,String password){//查询用户信息User user = userService.findById(username);if(user!=null && BCrypt.checkpw(password,user.getPassword())){return new Result(true,StatusCode.OK,"登录成功!",user);}return  new Result(false,StatusCode.LOGINERROR,"账号或者密码错误!");
}

注意:这里密码进行了加密。

网关关联

在我们平时工作中,并不会直接将微服务暴露出去,一般都会使用网关对接,实现对微服务的一个保护作用,如上图,当用户访问/api/user/的时候我们再根据用户请求调用用户微服务的指定方法。当然,除了/api/user/还有/api/address//api/areas//api/cities//api/provinces/都需要由user微服务处理,修改网关工程changgou-gateway-web的application.yml配置文件,如下代码:

spring:cloud:gateway:globalcors:corsConfigurations:'[/**]': # 匹配所有请求allowedOrigins: "*" #跨域处理 允许所有的域allowedMethods: # 支持的方法- GET- POST- PUT- DELETEroutes:- id: changgou_goods_routeuri: lb://goodspredicates:- Path=/api/goods/**filters:- StripPrefix=1- name: RequestRateLimiter #请求数限流 名字不能随便写 ,使用默认的facatoryargs:key-resolver: "#{@ipKeyResolver}"redis-rate-limiter.replenishRate: 1redis-rate-limiter.burstCapacity: 1#用户微服务- id: changgou_user_routeuri: lb://userpredicates:- Path=/api/user/**,/api/address/**,/api/areas/**,/api/cities/**,/api/provinces/**filters:- StripPrefix=1application:name: gateway-web#Redis配置redis:host: 192.168.211.132port: 6379server:port: 8001
eureka:client:service-url:defaultZone: http://127.0.0.1:7001/eurekainstance:prefer-ip-address: true
management:endpoint:gateway:enabled: trueweb:exposure:include: true

基于JWT的鉴权处理

JWT令牌组成

一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。

头部(Header)

头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。这也可以被表示成一个JSON对象。

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

在头部指明了签名算法是HS256算法。 我们进行BASE64编码http://base64.xpcha.com/,编码后的字符串如下:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

小知识:Base64是一种基于64个可打印字符来表示二进制数据的表示方法。由于2的6次方等于64,所以每6个比特为一个单元,对应某个可打印字符。三个字节有24个比特,对应于4个Base64单元,即3个字节需要用4个可打印字符来表示。JDK 中提供了非常方便的 BASE64EncoderBASE64Decoder,用它们可以非常方便的完成基于 BASE64 的编码和解码

载荷(playload)

载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分

(1)标准中注册的声明(建议但不强制使用)

iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

(2)公共的声明

公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.

(3)私有的声明

私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

这个指的就是自定义的claim。比如下面面结构举例中的admin和name都属于自定的claim。这些claim跟JWT标准规定的claim区别在于:JWT规定的claim,JWT的接收方在拿到JWT之后,都知道怎么对这些标准的claim进行验证(还不知道是否能够验证);而private claims不会验证,除非明确告诉接收方要对这些claim进行验证以及规则才行。

定义一个payload:

{"sub":"1234567890","name":"John Doe","admin":true}

然后将其进行base64加密,得到Jwt的第二部分。

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

签证(signature)

jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

header (base64后的)

payload (base64后的)

secret

这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。

TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

将这三部分用.连接成一个完整的字符串,构成了最终的jwt:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。

JJWT工具库

JJWT是一个提供端到端的JWT创建和验证的Java库。永远免费和开源(Apache License,版本2.0),JJWT很容易使用和理解。它被设计成一个以建筑为中心的流畅界面,隐藏了它的大部分复杂性。

官方文档:

https://github.com/jwtk/jjwt

创建Token

(1)依赖引入

在changgou-parent项目中的pom.xml中添加依赖:

<!--鉴权-->
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.0</version>
</dependency>

(2)创建测试

在changgou-common的/test/java下创建测试类,并设置测试方法

public class JwtTest {/***** 创建Jwt令牌*/@Testpublic void testCreateJwt(){JwtBuilder builder= Jwts.builder().setId("888")             //设置唯一编号.setSubject("小白")       //设置主题  可以是JSON数据.setIssuedAt(new Date())  //设置签发日期.signWith(SignatureAlgorithm.HS256,"itcast");//设置签名 使用HS256算法,并设置SecretKey(字符串)//构建 并返回一个字符串System.out.println( builder.compact() );}
}

运行打印结果:

eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1NjIwNjIyODd9.RBLpZ79USMplQyfJCZFD2muHV_KLks7M1ZsjTu6Aez4

再次运行,会发现每次运行的结果是不一样的,因为我们的载荷中包含了时间。

TOKEN解析

我们刚才已经创建了token ,在web应用中这个操作是由服务端进行然后发给客户端,客户端在下次向服务端发送请求时需要携带这个token(这就好像是拿着一张门票一样),那服务端接到这个token 应该解析出token中的信息(例如用户id),根据这些信息查询数据库返回相应的结果。

/**** 解析Jwt令牌数据*/
@Test
public void testParseJwt(){String compactJwt="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1NjIwNjIyODd9.RBLpZ79USMplQyfJCZFD2muHV_KLks7M1ZsjTu6Aez4";Claims claims = Jwts.parser().setSigningKey("itcast").parseClaimsJws(compactJwt).getBody();System.out.println(claims);
}

运行打印效果:

{jti=888, sub=小白, iat=1562062287}

试着将token或签名秘钥篡改一下,会发现运行时就会报错,所以解析token也就是验证token.
同时Claims类也支持设置过期时间,如果超过了过期时间,则解析也会报错

自定义claims

我们刚才的例子只是存储了id和subject两个信息,如果你想存储更多的信息(例如角色)可以定义自定义claims。

创建测试类,并设置测试方法:

创建token:

运行打印效果:

eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1NjIwNjMyOTIsImFkZHJlc3MiOiLmt7HlnLPpu5Hpqazorq3nu4PokKXnqIvluo_lkZjkuK3lv4MiLCJuYW1lIjoi546L5LqUIiwiYWdlIjoyN30.ZSbHt5qrxz0F1Ma9rVHHAIy4jMCBGIHoNaaPQXxV_dk

解析TOKEN:

/**** 解析Jwt令牌数据*/
@Test
public void testParseJwt(){String compactJwt="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1NjIwNjMyOTIsImFkZHJlc3MiOiLmt7HlnLPpu5Hpqazorq3nu4PokKXnqIvluo_lkZjkuK3lv4MiLCJuYW1lIjoi546L5LqUIiwiYWdlIjoyN30.ZSbHt5qrxz0F1Ma9rVHHAIy4jMCBGIHoNaaPQXxV_dk";Claims claims = Jwts.parser().setSigningKey("itcast").parseClaimsJws(compactJwt).getBody();System.out.println(claims);
}

运行效果:

鉴权处理

  1. 用户通过访问微服务网关调用微服务,同时携带头文件信息
  2. 在微服务网关这里进行拦截,拦截后获取用户要访问的路径
  3. 识别用户访问的路径是否需要登录,如果需要,识别用户的身份是否能访问该路径[这里可以基于数据库设计一套权限]
  4. 如果需要权限访问,用户已经登录,则放行
  5. 如果需要权限访问,且用户未登录,则提示用户需要登录
  6. 用户通过网关访问用户微服务,进行登录验证
  7. 验证通过后,用户微服务会颁发一个令牌给网关,网关会将用户信息封装到头文件中,并响应用户
  8. 用户下次访问,携带头文件中的令牌信息即可识别是否登录

用户登录签发TOKEN

(1)生成令牌工具类

在changgou-common中创建类entity.JwtUtil,主要辅助生成Jwt令牌信息,代码如下:

public class JwtUtil {//有效期为public static final Long JWT_TTL = 3600000L;// 60 * 60 *1000  一个小时//Jwt令牌信息public static final String JWT_KEY = "itcast";public static String createJWT(String id, String subject, Long ttlMillis) {//指定算法SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;//当前系统时间long nowMillis = System.currentTimeMillis();//令牌签发时间Date now = new Date(nowMillis);//如果令牌有效期为null,则默认设置有效期1小时if(ttlMillis==null){ttlMillis=JwtUtil.JWT_TTL;}//令牌过期时间设置long expMillis = nowMillis + ttlMillis;Date expDate = new Date(expMillis);//生成秘钥SecretKey secretKey = generalKey();//封装Jwt令牌信息JwtBuilder builder = Jwts.builder().setId(id)                    //唯一的ID.setSubject(subject)          // 主题  可以是JSON数据.setIssuer("admin")          // 签发者.setIssuedAt(now)             // 签发时间.signWith(signatureAlgorithm, secretKey) // 签名算法以及密匙.setExpiration(expDate);      // 设置过期时间return builder.compact();}/*** 生成加密 secretKey* @return*/public static SecretKey generalKey() {byte[] encodedKey = Base64.getEncoder().encode(JwtUtil.JWT_KEY.getBytes());SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");return key;}/*** 解析令牌数据* @param jwt* @return* @throws Exception*/public static Claims parseJWT(String jwt) throws Exception {SecretKey secretKey = generalKey();return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();}
}

(2) 用户登录成功 则 签发TOKEN,修改登录的方法:

/**** 用户登录*/
@RequestMapping(value = "/login")
public Result login(String username,String password){//查询用户信息User user = userService.findById(username);if(user!=null && BCrypt.checkpw(password,user.getPassword())){//设置令牌信息Map<String,Object> info = new HashMap<String,Object>();info.put("role","USER");info.put("success","SUCCESS");info.put("username",username);//生成令牌String jwt = JwtUtil.createJWT(UUID.randomUUID().toString(), JSON.toJSONString(info),null);return new Result(true,StatusCode.OK,"登录成功!",jwt);}return  new Result(false,StatusCode.LOGINERROR,"账号或者密码错误!");
}

自定义全局过滤器

创建 过滤器类,如图所示:

AuthorizeFilter代码如下:

@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {//令牌头名字private static final String AUTHORIZE_TOKEN = "Authorization";/**** 全局过滤器* @param exchange* @param chain* @return*/@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {//获取Request、Response对象ServerHttpRequest request = exchange.getRequest();ServerHttpResponse response = exchange.getResponse();//获取请求的URIString path = request.getURI().getPath();//如果是登录、goods等开放的微服务[这里的goods部分开放],则直接放行,这里不做完整演示,完整演示需要设计一套权限系统if (path.startsWith("/api/user/login") || path.startsWith("/api/brand/search/")) {//放行Mono<Void> filter = chain.filter(exchange);return filter;}//获取头文件中的令牌信息String tokent = request.getHeaders().getFirst(AUTHORIZE_TOKEN);//如果头文件中没有,则从请求参数中获取if (StringUtils.isEmpty(tokent)) {tokent = request.getQueryParams().getFirst(AUTHORIZE_TOKEN);}//如果为空,则输出错误代码if (StringUtils.isEmpty(tokent)) {//设置方法不允许被访问,405错误代码response.setStatusCode(HttpStatus.METHOD_NOT_ALLOWED);return response.setComplete();}//解析令牌数据try {Claims claims = JwtUtil.parseJWT(tokent);} catch (Exception e) {e.printStackTrace();//解析失败,响应401错误response.setStatusCode(HttpStatus.UNAUTHORIZED);return response.setComplete();}//放行return chain.filter(exchange);}/**** 过滤器执行顺序* @return*/@Overridepublic int getOrder() {return 0;}
}

配置过滤规则

修改网关系统的yml文件:

上述代码如下:

spring:cloud:gateway:globalcors:corsConfigurations:'[/**]': # 匹配所有请求allowedOrigins: "*" #跨域处理 允许所有的域allowedMethods: # 支持的方法- GET- POST- PUT- DELETEroutes:- id: changgou_goods_routeuri: lb://goodspredicates:- Path=/api/album/**,/api/brand/**,/api/cache/**,/api/categoryBrand/**,/api/category/**,/api/para/**,/api/pref/**,/api/sku/**,/api/spec/**,/api/spu/**,/api/stockBack/**,/api/template/**filters:- StripPrefix=1- name: RequestRateLimiter #请求数限流 名字不能随便写 ,使用默认的facatoryargs:key-resolver: "#{@ipKeyResolver}"redis-rate-limiter.replenishRate: 1redis-rate-limiter.burstCapacity: 1#用户微服务- id: changgou_user_routeuri: lb://userpredicates:- Path=/api/user/**,/api/address/**,/api/areas/**,/api/cities/**,/api/provinces/**filters:- StripPrefix=1application:name: gateway-web#Redis配置redis:host: 192.168.211.132port: 6379server:port: 8001
eureka:client:service-url:defaultZone: http://127.0.0.1:7001/eurekainstance:prefer-ip-address: true
management:endpoint:gateway:enabled: trueweb:exposure:include: true

会话保持

用户每次请求的时候,我们都需要获取令牌数据,方法有多重,可以在每次提交的时候,将数据提交到头文件中,也可以将数据存储到Cookie中,每次从Cookie中校验数据,还可以每次将令牌数据以参数的方式提交到网关,这里面采用Cookie的方式比较容易实现。

登录封装Cookie

修改user微服务,每次登录的时候,添加令牌信息到Cookie中,修改changgou-service-user的com.changgou.user.controller.UserControllerlogin方法,代码如下

过滤器获取令牌数据

每次在网关中通过过滤器获取Cookie中的令牌,然后对令牌数据进行解析,修改微服务网关changgou-gateway-web中的AuthorizeFilter,代码如下:

添加Header信息

我们还可以在Gateway的全局过滤器中添加请求头信息,例如可以讲令牌信息添加到请求头中,在微服务中获取头信息,如下代码:

修改微服务网关中的AuthorizeFilter过滤器,在令牌信息校验那块将令牌加入到请求头中,如下代码:

畅购商城六:微服务网关与jwt令牌相关推荐

  1. 畅购商城(三):商品管理

    好好学习,天天向上 本文已收录至我的Github仓库DayDayUP:github.com/RobodLee/DayDayUP,欢迎Star,更多文章请前往:目录导航 畅购商城(一):环境搭建 畅购商 ...

  2. 商品品牌信息的增删改查操作步骤_畅购商城(三):商品管理

    好好学习,天天向上 本文已收录至我的Github仓库 DayDayUP:github.com/RobodLee/DayDayUP,欢迎Star 畅购商城(一):环境搭建 畅购商城(二):分布式文件系统 ...

  3. 畅购商城(五):Elasticsearch实现商品搜索

    好好学习,天天向上 本文已收录至我的Github仓库DayDayUP:github.com/RobodLee/DayDayUP,欢迎Star,更多文章请前往:目录导航 畅购商城(一):环境搭建 畅购商 ...

  4. 畅购商城(十三):秒杀系统「上」

    好好学习,天天向上 本文已收录至我的Github仓库DayDayUP:github.com/RobodLee/DayDayUP,欢迎Star 畅购商城(一):环境搭建 畅购商城(二):分布式文件系统F ...

  5. 商城项目的微服务网关,微服务网关的主要作用

    微服务网关的作用: 不使用微服务网关的缺陷: 1.每一个页面会对接很多个微服务,调用起来繁琐 2.调用微服务过多,很容易暴露出端口,增加了服务器的受攻击的面积 3.跨域的问题,没有加网关时,我们是加的 ...

  6. Java毕业设计项目【畅购商城】

    为了帮助更多的铁汁们,快速进步,完成毕业设计,挺近大厂,我前面已经分享了很多项目 但是有铁汁们觉得实战项目不够,为了给支持我的朋友吧 此次分享的是商城项目,里面包含视频和代码,涉及到SSM.Sprin ...

  7. 畅购商城_第11章_ 订单

    畅购商城_第11章_ 订单 文章目录 畅购商城_第11章_ 订单 第11章 订单 课程内容 1 订单结算页 1.1 收件地址分析 1.2 实现用户收件地址查询 1.2.1 代码实现 1.2.2 测试 ...

  8. 微服务网关鉴权:gateway使用、网关限流使用、用户密码加密、JWT鉴权

    点击上方"芋道源码",选择"设为星标" 管她前浪,还是后浪? 能浪的浪,才是好浪! 每天 10:33 更新文章,每天掉亿点点头发... 源码精品专栏 原创 | ...

  9. 畅购商城4.0 微信支付

    畅购商城4.0 1.微信支付 1.1流程分析 1.2微信支付概述 1.2.1账号申请 步骤一:注册公众号,根据自身主体类型注册对应的公众号 只能申请服务号,订阅号没有办法申请支付 https:// ...

最新文章

  1. Didn't find class net.oschina.app.AppContext on
  2. 解读计算机视觉论文投稿到接收,不可不知的关键环节
  3. literature review and methodology
  4. python实现rsa加密源代码_python实现RSA加密(解密)算法
  5. EF 执行存储过程方法
  6. 切面是异步还是同步操作‘_【 .NET Core 3.0 】框架之十 || AOP 切面思想
  7. 9适应之力加多少攻击_剑盾铠之孤岛DLC新增内容大全! 宝可梦史上第一款DLC到底香不香(含试玩视频)...
  8. js对HTML字符转义与反转义
  9. STM32(五)------GPIO位带操作
  10. 五层验证系统,带你预防区块链业务漏洞
  11. element-UI 表单校验失效处理
  12. ffmpeg转码参数
  13. 计算机基础知识题二,计算机基础知识题库(五)
  14. 云计算与大数据——数据挖掘常用算法
  15. (转) Java线程池实现原理及其在美团业务中的实践
  16. home为什么是地点副词_home为什么可以做副词
  17. linux u盘分区 mdev 卸载问题,迅为开发板最小Linux自动挂载TF卡/U盘等存储设备
  18. matlab 标量双精度值,matlab数据类型(数值类型)
  19. c语言程序0xc0000005解决方案,应用程序无法正常启动0xc0000005如何解决
  20. 小悦悦事件,18路人视而不见,不只是道德问题

热门文章

  1. 阿里云人脸人体--人物动漫化效果
  2. 播放量轻松破万,中视频计划播放量不够怎么办,教你两招
  3. 怎样做一个网课查题公众号
  4. vim 精确匹配查找字符串
  5. 观察者模式设计qq邮箱日历提醒 -- qq终端,qq邮箱终端,微信终端提醒
  6. 【pyspark】jieba 中文分词
  7. 简单的jsp权限管理
  8. Excel表格如何进行美化,Leo老师来教你!
  9. 中央民族大学计算机专业分数线,2017中央民族大学各省各专业录取分数线(7)
  10. 编写算法求无向图的连通分量的个数,求无向图的连通分量