好好学习,天天向上




本文已收录至我的Github仓库DayDayUP:github.com/RobodLee/DayDayUP,欢迎Star,更多文章请前往:目录导航

微服务网关

介绍

网关是介于用户和微服务之前的中间层。说白了,网关就像是小区的保安,无论你想到小区的哪一户人家去,你都得先通过小区的大门。所以,小区的保安可以做人员统计,还可以控制某个时间段进去小区的人数,限制进入小区的资格等。保证了小区业主们的安全。微服务网关同样起着这些作用。

为什么要有微服务网关

不同的微服务一般会有不同的网络地址,而外部客户端可能需要调用多个服务的接口才能完成一个业务需求,如果让客户端直接与各个微服务通信,会有以下的问题:

  • 客户端会多次请求不同的微服务,增加了客户端的复杂性
  • 存在跨域请求,在一定场景下处理相对复杂
  • 认证复杂,每个服务都需要独立认证
  • 难以重构,随着项目的迭代,可能需要重新划分微服务。例如,可能将多个服务合并成一个或者将一个服务拆分成多个。如果客户端直接与微服务通信,那么重构将会很难实施
  • 某些微服务可能使用了防火墙 / 浏览器不友好的协议,直接访问会有一定的困难

那么有了微服务网关之后,这些问题就可以得到解决。它有着以下优点。

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

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

网关微服务

微服务搭建

一个项目中可能会用到不止一个网关,所以我们将网关微服务放在changgou-gateway父工程下。现在我们创建一个名为changou-gateway-web的微服务。有些依赖是所有网关微服务都要用到的,所以将这些依赖放在父工程下:

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

启动类和配置文件不能少,启动类就不贴了,配置文件如下

spring:
application:
name: gateway-web
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]': # 匹配所有请求
allowedOrigins: "*" #跨域处理 允许所有的域
allowedMethods: # 支持的方法
- GET
- POST
- PUT
- DELETE
server:
port: 8001
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:7001/eureka
instance:
prefer-ip-address: true
management:
endpoint:
gateway:
enabled: true
web:
exposure:
include: true

网关过滤配置

  • Host 路由
# 用户请求的域名规格配置,所有以robod.changgou.com开头的请求都将被路由到http://localhost:18081微服务
# 例如 http://robod.changgou.com:8001/brand ——> http://localhost:18081/brand
# 但是首先得在hosts文件中配置一下: 127.0.0.1 robod.changgou.com
spring:
cloud:
gateway:
routes:
- id: changgou_goods_route # 唯一标识符
uri: http://localhost:18081
predicates:
- Host=robod.changgou.com**
  • - Path 路径匹配过滤配置
# 所有以/brand开头的请求都将路由到http://localhost:18081
# 例如 localhost:8001/brand ——> localhost:18081/brand
spring:
cloud:
gateway:
routes:
- id: changgou_goods_route
uri: http://localhost:18081
predicates:
- Path=/brand/**
  • PrefixPath 过滤配置
# 自动加上某个前缀,用户请求/** ——>/brand/**
# 例如 localhost:8001/111 ——> localhost:8001/brand/111 ——> localhost:18081/brand/111
spring:
cloud:
gateway:
routes:
- id: changgou_goods_route
uri: http://localhost:18081
predicates:
- Path=/**
filters:
- PrefixPath=/brand
  • StripPrefix 过滤配置
# 将请求路径中的前n个路径去掉,请求路径以/区分,一个/代表一个路径
# 例如 localhost:8001/api/brand/111 ——> localhost:8001/brand/111 ——> localhost:18081/brand/111
spring:
cloud:
gateway:
routes:
- id: changgou_goods_route
uri: http://localhost:18081
predicates:
- Path=/**
filters:
- StripPrefix=1
  • LoadBalancerClient 路由过滤器(客户端负载均衡)
# 使用LoadBalancerClient实现负载均衡,后面的goods是微服务的名称,主要应用于集群环境
# 比如现在有5台服务器都是goods微服务,网关就会自动将请求发送给不同的服务器达到负载均衡的目的
spring:
cloud:
gateway:
routes:
- id: changgou_goods_route
uri: lb://goods

网关限流

当访问量多大的时候,我们的服务就可能会挂掉,所以我们需要对每个微服务进行限流,但是这样比较麻烦。有了网关之后,我们可以对网关进行限流,因为所有的请求必须通过网关才能到达微服务,这样比较方便。

令牌桶算法

常见的限流算法有计数器,漏斗,令牌桶算法。令牌桶算法有以下几个特点:

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

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

spring cloud gateway 默认使用redis的RateLimter限流算法来实现。首先在changgou-gateway-web中添加Redis的依赖:

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

然后我们需要有限流的Key,这里用IP来当作限流的Key,限制某一个IP在一定时间段的访问次数,在启动类中定义一个Bean用于获取key

@Bean(name = "ipKeyResolver")
public KeyResolver userKeyResolver() {
return exchange -> {
String ip = Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getHostName();
return Mono.just(ip);
};
}

我这里使用了Lamda去简化书写。接下来还得在配置文件中配置一下

spring:
application:
name: gateway-web
cloud:
gateway:
routes:
filters:
- name: RequestRateLimiter #请求数限流 名字不能随便写 ,使用默认的factory
args:
# 用户身份唯一标识符
key-resolver: "#{@ipKeyResolver}"
# 允许用户每秒执行多少请求,而不会丢弃任何请求。这是令牌桶填充的速率
redis-rate-limiter.replenishRate: 1
# 令牌桶的容量,允许在一秒钟内完成的最大请求数
redis-rate-limiter.burstCapacity: 1

既然是使用redis的RateLimter限流算法,那么Redis的配置自然不能少。

#Redis配置
spring:
application:
redis:
host: 192.168.31.200
port: 6379

限流的配置就配置好了,现在如果在1秒内请求超过1次的话就会被拒绝。

JWT

在实现用户登录功能之前,我们先来介绍一下JWT(JSON Web Token)。是一种用于通信双方之间传递安全信息的简洁的、URL安全的表述性声明规范。

JWT的构成

一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。为了能够直观的看到JWT的结构,我画了一张思维导图:

最终生成的JWT令牌就是下面这样,有三部分,用 . 分隔。

base64UrlEncode(JWT 头)+"."+base64UrlEncode(载荷)+"."+HMACSHA256(base64UrlEncode(JWT 头) + "." + base64UrlEncode(有效载荷),密钥)

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

JWT的使用

  • 导入依赖:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
  • 创建Token
public String createToken() {
JwtBuilder builder = Jwts.builder()
.setId("test1")
.setSubject("Robod")
.setAudience("马化腾")
.setIssuedAt(new Date());
.signWith(SignatureAlgorithm.HS256,"robod666");
Map<String,Object> map = new HashMap<>();
map.put("ha","哈哈哈");
builder.addClaims(map);
return builder.compact();
}
  • 解析Token
public String parseToken() {
String compactJwt="eyJhbGciOiJIUzI1NiJ9" +
".eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1NjIwNjIyODd9" +
".RBLpZ79USMplQyfJCZFD2muHV_KLks7M1ZsjTu6Aez4";
Claims claims = Jwts.parser().
setSigningKey("robod666").
parseClaimsJws(compactJwt).
getBody();
return claims.toString();
}

用户登录与鉴权

介绍了JWT之后,我们就来用JWT实现用户登录与鉴权。流程如下:

首先我们需要准备一个JWT的工具类,JWTUtil,放在changgou-common下:

public class JwtUtil {
//默认有效期,一个小时
public static final Long JWT_TTL = 3600000L; //Jwt令牌信息
public static final String JWT_KEY = "RobodLee"; //密钥
public static SecretKey secretKey = generalKey(); //生成令牌
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); //封装Jwt令牌信息
JwtBuilder builder = Jwts.builder()
.setId(id) //唯一的ID
.setSubject(subject) // 主题 可以是JSON数据
.setIssuer("robod") // 签发者
.setIssuedAt(now) // 签发时间
.signWith(signatureAlgorithm,secretKey) // 签名算法以及密匙
.setExpiration(expDate); // 设置过期时间
return builder.compact();
} //生成加密 secretKey
public static SecretKey generalKey() {
byte[] encodedKey = Base64.getEncoder().encode(JwtUtil.JWT_KEY.getBytes());
return new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
} //解析令牌
public static Claims parseJWT(String jwt) throws Exception {
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwt)
.getBody();
}
}

我发现资料提供的代码中每次调用generalKey()、parseJWT()方法的时候都去调用generalKey()方法去生成SecretKey,但是generalKey()方法内容是不变的,所以可以将SecretKey单独提取出来,这样就不用每次都调用generalKey()去生成了。

然后创建一个用户微服务changou-service-user在UserController中编写登录逻辑

@RequestMapping("/login")
public Result<String> login(String username, String password, HttpServletResponse response) {
User user = userService.findById(username);
if (BCrypt.checkpw(password,user.getPassword())){
Map<String,Object> tokenInfo = new HashMap<>(4);
tokenInfo.put("role","USER");
tokenInfo.put("success","SUCCESS");
tokenInfo.put("username",username);
String token = JwtUtil.createJWT(UUID.randomUUID().toString(), JSON.toJSONString(tokenInfo), null);
Cookie cookie = new Cookie("Authorization",token);
cookie.setDomain("localhost");
cookie.setPath("/");
response.addCookie(cookie);
return new Result<>(true,StatusCode.OK,"登录成功",token);
}
return new Result<>(false,StatusCode.LOGIN_ERROR,"登录失败");
}

在这段代码中,调用Service层从数据库中查出对应的User,然后比对password,看密码是否正确。如果正确,就调用JwtUtil创建一个JWT令牌,并放入一些简单的信息。然后将JWT令牌存入Cookie中,并返回给前端。如果登录失败就返回登录失败的信息。

然后就是在网关微服务中添加相应的逻辑了,在changgou-gateway-web中配置一下,配置一下User微服务的路由。

spring:
application:
name: gateway-web
cloud:
gateway:
routes:
- id: changgou_user_route # 唯一标识符
uri: http://localhost:18088
predicates:
- Path=/api/user/**,/api/address/**,/api/areas/**,/api/cities/**,/api/provinces/**
filters:
- StripPrefix=1

再添加一个过滤器:

@Component
public class AuthorizeFilter implements GlobalFilter, Ordered { private static final String AUTHORIZE_TOKEN = "Authorization"; @Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
String token;
//从头中获取Token
token = request.getHeaders().getFirst(AUTHORIZE_TOKEN);
//请求头中没有Token就从参数中获取
if (StringUtils.isEmpty(token)){
token = request.getQueryParams().getFirst(AUTHORIZE_TOKEN);
}
//参数中再没有Token就从Cookie中获取
if (StringUtils.isEmpty(token)){
HttpCookie cookie = request.getCookies().getFirst(AUTHORIZE_TOKEN);
if (cookie!=null){
token = cookie.getValue();
}
}
//还是没有Token就拦截
if (StringUtils.isEmpty(token)){
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
//Token不为空就校验Token
try {
JwtUtil.parseJWT(token);
} catch (Exception e) {
//报异常说明Token是错误的,拦截
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
return chain.filter(exchange);
} @Override
public int getOrder() {
return 0;
}
}

这段代码就是分别从Header,参数,Cookie中看有没有Token信息,没有的话就说明用户没有权限,拦截下来。有Token的话就解析一下Token有没有错,错误就拦截下来。如果都没有问题的话就放行,将请求路由到用户微服务中。

这是没有Token的情况下

当我们登陆后就会获取到Token

当我们携带着token去访问就没有问题了

小结

这篇文章中,首先介绍了微服务网关及网关的搭建及过滤配置和限流配置。然后介绍了JWT,最后使用了JWT去实现了用户登录与鉴权的操作。

如果我的文章对你有些帮助,不要忘了点赞收藏转发关注。要是有什么好的意见欢迎在下方留言。让我们下期再见!

畅购商城(八):微服务网关和JWT令牌的更多相关文章

  1. 畅购商城(九):Spring Security Oauth2

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

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

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

  3. 畅购商城(四):Lua、OpenResty、Canal实现广告缓存与同步

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

  4. 畅购商城(二):分布式文件系统FastDFS

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

  5. 【SpringCloud构建微服务系列】微服务网关Zuul

    一.为什么要用微服务网关 在微服务架构中,一般不同的微服务有不同的网络地址,而外部客户端(如手机APP)可能需要调用多个接口才能完成一次业务需求.例如一个电影购票的手机APP,可能会调用多个微服务的接 ...

  6. Bumblebee微服务网关的部署和扩展

    Bumblebee是.netcore下开源基于BeetleX.FastHttpApi扩展的HTTP微服务网关组件,它的主要作用是针对WebAPI集群服务作一个集中的转发和管理:作为应用网关它提供了应用 ...

  7. 微服务-网关-node.js by 大雄daysn

    目录 序言 一.node.js入门1.1 下载并安装1.2 从helloworld到一个web应用1.3 Express框架二.node.js搭建网关 三.node.js集群搭建   序言 首先一个问 ...

  8. 使用 Node.js 搭建微服务网关

    目录 Node.js 是什么 安装 node.js Node.js 入门 Node.js 应用场景 npm 镜像 使用 Node.js 搭建微服务网关 什么是微服务架构 使用 Node.js 实现反向 ...

  9. springcloud使用Zuul构建微服务网关入门

    为什么要使用微服务网关 不同的微服务一般会经过不同的网络地址,而外部客户端可能需要调用多个服务的接口才能完成一个业务需求. 如果让客户端直接与各个微服务通信,会有以下的问题: 客户端会多次请求不同的微 ...

随机推荐

  1. redis源码学习之工作流程初探

    目录 背景 环境准备 下载redis源码 下载Visual Studio Visual Studio打开redis源码 启动过程分析 调用关系图 事件循环分析 工作模型 代码分析 动画演示 网络模块 ...

  2. scala 数据结构(五):队列 Queue

    1 队列 Queue-基本介绍 队列的说明 1)队列是一个有序列表,在底层可以用数组或是链表来实现. 2)其输入和输出要遵循先入先出的原则.即:先存入队列的数据,要先取出.后存入的要后取出 3)在Sc ...

  3. 数据库02 /MySQL基础数据类型、完整性约束、sql_mode模式

    2.MySQL基础数据类型.完整性约束.sql_mode模式 目录 2.MySQL基础数据类型.完整性约束.sql_mode模式 1. MySQL常用数据类型 MySQL常用数据类型预览 1. 1 数 ...

  4. bzoj2648SJY摆棋子&&bzoj2716[Violet 3]天使玩偶*

    bzoj2648SJY摆棋子 bzoj2716[Violet 3]天使玩偶 题意: 棋盘上有n个棋子,现在有m个操作,一种是加棋子,一种是查询离某个点最近的棋子.n,m≤500000. 题解: 先将已 ...

  5. 作为程序员居然没用过这款神器?太out了吧。

    背景 工欲善其事,必先利其器.​后面我将陆陆续续推荐一些软件利器帮助大家提高效率(主要针对 Mac 电脑). 如果你在使用 Mac 电脑,并且没有如某些人那样安装并使用 Windows 系统,那么你可 ...

  6. DEX文件解析--4、dex类的类型解析

    一.前言   前几篇系列文章链接:     DEX文件解析---1.dex文件头解析     DEX文件解析---2.Dex文件checksum(校验和)解析     DEX文件解析--3.dex文件 ...

  7. Ethical Hacking - GAINING ACCESS(4)

    SERVER SIDE ATTACKS - METASPLOIT Metasploit is an exploit development and execution tool. It can als ...

  8. three.js 将图片马赛克化

    这篇郭先生来说说BufferGeometry,类型化数组和粒子系统的使用,并且让图片有马赛克效果(同理可以让不清晰的图片清晰化),如图所示.在线案例点击博客原文 1. 解析图片 解析图片和上一篇一样 ...

  9. Java应用服务器之tomcat会话复制集群配置

    会话是识别用户,跟踪用户访问行为的一个手段,通过cookie(存在客户端)或session(存在服务端)来判断本次请求是那个客户端发送过来:常用的会话保持有绑定会话,就是前边我们聊的在代理上通过算法或 ...

  10. Spring事务管理接口定义

    Spring事务管理接口介绍 Spring事务管理接口: PlatformTransactionManager: (平台)事务管理器 TransactionDefinition: 事务定义信息(事务隔 ...