好好学习,天天向上




本文已收录至我的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. 记一次开发CefSharp做浏览器时Shopify绑定不上Paypal问题

    问题:CefSharp做浏览器时Shopify绑定不上Paypal. shopify绑定Paypal的流程大概是如下图所示 步骤1 步骤2 步骤3 步骤4 出现问题大概是在绑定最后一步,并没有如愿的返 ...

  2. CMMI规范目录结构

  3. Quartz.Net系列(十六):通过Plugins模式使用Xml方式配置Job和Trigger和自定义LogPrivider

    1.简单介绍 Quarz.Net中采用插件式来实现配置文件配置,通过XMLSchedulingDataProcessor类进行Xml数据处理 默认配置文件命名:quart_jobs.xml publi ...

  4. nginx: command not found

    nginx 正常启动,可以访问服务器地址:welcome to nginx 使用nginx -t 等命令时 报错:nginx: command not found 这是环境变量未配置 配置环境变量 v ...

  5. 技术干货:Jenkins集成GitLab

    Jenkins免密拉取GitLab项目 1.在Jenkins上为GitLab创建一个专有的拉取代码的账号 Jenkins需要构建哪些项目就在GitLab给予账号相应权限 我这里已经创建过Jenkins ...

  6. 给咱的WP站点搬家

    前言 WordPress 作为全球最流行的博客系统,使用简单,功能丰富,用它来建站的用户非常多.对于站长们来说,网站搬家也是少不了的,有时我们需要更换主机空间,把网站从一个服务器迁移到另一个服务器上, ...

  7. p44_IP数据包格式

    一.IP数据报格式 二.IP分片 数据链路层每帧可封装数据有上限,IP数据超过的要分片. 标识:同一数据报的分片使用同一标识 标志: 片偏移(13bit):用于还原数据报顺序,指出某片在原分组1中的相 ...

  8. 设计模式:Iterator模式

    目的:将数据的存储和数据的查询分开,降低数据的耦合性 继承关系图: 例子: //定义迭代器接口 template<typename T> class Iterator { public: ...

  9. 题解 洛谷 P2179 【[NOI2012]骑行川藏】

    题意为在满足\(\sum\limits_{i=1}^nk_i(v_i-v_i^\prime)^2s_i\leqslant E_U\)的条件下最小化\(\sum\limits_{i=1}^n\frac{ ...

  10. PowerJob 技术综述,能领悟多少就看你下多少功夫了~

    本文适合有 Java 基础知识的人群 作者:HelloGitHub-Salieri HelloGitHub 推出的<讲解开源项目>系列.从本章开始,就正式进入 PowerJob 框架的技术 ...