好好学习,天天向上




本文已收录至我的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 集合(Set) python

    # -*- coding: utf-8 -*- import redis r = redis.Redis(host="126.56.74.190",port=639,passwor ...

  2. 自定义类支持foreach

    建议使用yield语句简化迭代 using System; using System.Collections; namespace 自定义类实现foreach { class A { int[] w; ...

  3. webpack源码-loader的原理

    版本 webpack :"version": "3.12.0", webpack配置中的loaders配置是如何传递的 webpack/lib/NormalMo ...

  4. coding如何绑定二次验证码_虚拟MFA_两步验证_身份验证?

    Coding.net 是一个面向开发者的云端开发平台,提供 Git/SVN 代码托管.任务管理.在线 WebIDE.Cloud Studio.开发协作.文件管理.Wiki 管理.提供个人服务及企业管理 ...

  5. 组件缓存注意事项 ---keep-alive

  6. Netty 学习笔记(4) ------ EventLoopGroup

    EventLoopGroup负责管理Channel的事件处理任务,继承自java.util.concurrent包下的Executor,所以其结构类似与线程池,管理多个EventLoop. 而一个Ev ...

  7. .log文件超过2.56MB?Pycharm的.log文件读取不完全?.log文件无法被调用?

    问题截图: 问题表现情况: 1.pycharm头部出现上图警告 2.该.log文件读取不完全 3.该.log文件无法被调用 解决步骤: 参考博客:https://blog.csdn.net/Shen1 ...

  8. 大汇总 | 一文学会八篇经典CNN论文

    本文主要是回顾一下一些经典的CNN网络的主要贡献. 论文传送门 [google团队] [2014.09]inception v1: https://arxiv.org/pdf/1409.4842.pd ...

  9. 项目管理--PMBOK 读书笔记(4)【项目整合管理】

    项目整合管理:包括对隶属于项目管理过程组的各种过程和项目管理活动进行识别.定义.组合.统一和协调的各个过程. 项目整合管理的核心概念: 1.确保产品.服务或成果的交付日期,项目生命周期以及效益管理计划 ...

  10. Django开发之模态框提交内容到后台[Object Object]

    版本 Python 3.8.2 Django 3.0.6 场景 前端页面:使用bootstrap-table展示后台传入数据,选中多行提交修改,弹出bootstrap模态框 模态框内容:根据选中表格行 ...