一、网关的简单介绍
(一)网关的定义和职能
1、网关的的定义
网关的角色是作为一个 API 架构,用来保护、增强和控制对于 API 服务的访问。
API 网关是一个处于应用程序或服务(提供 REST API 接口服务)之前的系统,用来管理授权、访问控制和流量限制等,这样 REST API 接口服务就被 API 网关保护起来,对所有的调用者透明。因此,隐藏在 API 网关后面的业务系统就可以专注于创建和管理服务,而不用去处理这些策略性的基础设施。
2、网关的的职能
(二)Gateway的介绍
1、什么是Gateway
Spring Cloud Gateway是Spring官方基于Spring 5.0,Spring Boot 2.0和Project Reactor等技术开发的网关,Spring Cloud Gateway旨在为微服务架构提供一种简单而有效的统一的API路由管理方式。Spring Cloud Gateway作为Spring Cloud生态系中的网关,目标是替代ZUUL,其不仅提供统一的路由方式,并且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/埋点,和限流等。
2、为什么要用Gateway
Gateway是用来替代Zuul网关的,其明确的区分了Router和Filter,并且内置了许多功能都可以通过配置就行直接使用。
其内置了多种Router,可以在配置文件中根据Header、Path、Host等方式来做路由,我后面只介绍通过path的方式进行路由,至于其他方式可以自行查看网上资料。
其也内置了一般Filter和全局Filter,我们也可以自定义Filter进行业务处理,后面将介绍通过内置的Filter对请求path进行处理以及通过自定义Filter对token进行校验。
3、概念
二、基础路由规则的配置使用
(一)项目说明
用gateway作为项目的整体网关,所有请求都先打到gateway上,经过安全、权限过滤以后通过的请求再根据路由规则从nacos上获取服务名然后转发到对应的服务上。
gateway网关项目整体就是一个springboot项目,在项目里面做路由配置以及权限过滤等操作,该项目整体结构如下:
(二)依赖的导入
由于是微服务的,所以需要导入springcloud和springCloudAlibaba的依赖,至于2者与springboot版本之间的应该选择哪个版本可以参考我之前的文章:Spring,SpringCloud,SpringCloudAlibaba各版本对应关系
由于要用nacos做为注册中心获取各服务的真实地址,所以还需要导入nacos
当然gateway本身的依赖也是必不可少的
(三)配置文件说明
server:
port: 7000
#
spring:
application:
name: cloud-gateway
cloud:
gateway:
globalcors:
# gateway 跨域设置
cors-configurations:
'[/**]':
allowedOrigins: "*"
allowedHeaders: "*"
allowCredentials: true
allowedMethods:
- GET
- POST
- PUT
- OPTIONS
discovery:
locator:
####开启以服务id去注册中心上获取转发地址
enabled: true
###路由策略
routes:
##路由策略id
- id: cloud-admin
#### 基于lb负载均衡形式转发(通过注册中心服务名称,获取真实访问地址)
uri: lb://cloud-admin
###匹配规则
predicates:
- Path=/cloud-admin/**
filters:
# 去除的路径前缀的个数(strip)
- StripPrefix=1
nacos: #注册中心地址
discovery:
server-addr: 10.10.83.73:8848
配置文件解释:
跨域和nacos注册中心的配置就不说了,主要细说路由策略
1)上面配置了一个路由规则id为cloud-admin,当请求的路径为/cloud-admin/**时将请求通过负载均衡转发到服务名为cloud-admin的服务上,从nacos上根据服务名获取服务真是地址进行转发。
2)至于配置文件中的StripPrefix=1代表将请求的前缀去除一个,例如我的请求路径是/cloud-admin/user/getUserById,该请求被转发至cloud-admin服务上时请求路径就变成了/user/getUserById。
为什么要去除前缀,直接用/user/getUserById不就行了吗?
前端服务可能会调取多个后端服务的方法,如果不在路径上做区分,等所有请求都到gateway时就没法区分调用哪个服务。
注:路由策略有很多,我只是用来path来做路由,真正用的时候可能多个Router共同组合来做策略。
三、全局token校验处理
gateway不仅能做路由转发,还能对所有的请求进行权限校验、过滤。我这个项目中需要在gateway中校验是否携带token,token校验通过以后把提取到的用户id再传到后面的服务中。
我在项目中自定义了一个token然后实现全局的filter,这样所有的请求都得进行token校验,代码如下:
package com.cloud.cloudgateway.filter;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
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.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
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.List;
/**
* @Description: 自定义全局token校验
* @Author: yl
* @Date: 2021年10月9日15:42:31
*/
@Component
public class GlobalTokenFilter implements GlobalFilter, Ordered {
private static final String PATH = "/login";
public static final String SECRET = "develop-Platform"; // JWT密钥
public static final String TOKEN_KEY = "Authorization";// 存放Token的Header Key
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
// 获取请求路径
String path = request.getPath().toString();
//登录请求直接到认证服务
if(path.contains(PATH)){
return chain.filter(exchange);
}
//验证token
HttpHeaders headers = request.getHeaders();
List<String> headerList = headers.get(TOKEN_KEY);
if(headerList.isEmpty()){
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
String msg = "无token,请重新登录";
DataBuffer buffer = response.bufferFactory().wrap(msg.getBytes());
return response.writeWith(Mono.just(buffer));
}
String token = headerList.get(0);
String userId;
try {
userId = JWT.decode(token).getAudience().get(0);
} catch (JWTDecodeException j) {
throw new RuntimeException("401");
}
// 验证 token
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(SECRET)).build();
try {
jwtVerifier.verify(token);
} catch (JWTVerificationException e) {
throw new RuntimeException("401");
}
ServerHttpRequest host = request.mutate().header("userId",userId).header("userToken",token).build();
//将request变成change对象
ServerWebExchange build = exchange.mutate().request(host).build();
return chain.filter(build);
}
//实现Ordered接口保证优先级,值越小加载的优先级越高
@Override
public int getOrder() {
return -1;
}
}