作者简介:一位大四、研0学生,正在努力准备大四暑假的实习
上期文章:详解SpringCloud微服务技术栈:Feign远程调用、最佳实践、错误排查
订阅专栏:微服务技术全家桶
希望文章对你们有所帮助
现在集群中会有很多的微服务,且都对应自己的一个数据库,同时每个微服务还需要在Nacos中做注册和配置管理,当微服务之间有相互调用的时候,直接通过Feign来做远程调用,而当外部要访问的时候,直接发请求就可以了。
但,外部可以随意访问微服务,并不是很安全,有些东西只有相应的专业人员才能访问,而网关就是负责解决这类问题的,一切请求都要先通过了网关才能去访问微服务。
身份认证和权限校验:通过才能去访问微服务
服务路由:当通过网关后,还需要根据请求的类型,将请求转发到对应的微服务中
负载均衡:确定了转发的微服务之后,微服务中的多个实例之间还应该做负载均衡
请求限流:限制访问的请求量
在SpringCloud中网关的实现包括2种:
gateway
zuul
zuul是基于Servlet的实现,属于阻塞式编程。而SpringCloudGateway则是基于Spring5中提供的webFlux,属于响应式编程的实现,具备更好的性能。
因此,我们会使用SpringCloudGateway。
需要先搭建一下网关,步骤:
1、创新新的module,引入SpringCloudGateway的依赖和Nacos的服务注册发现依赖:
<!--Nacos服务发现依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--网关gateway依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
2、搭建一下启动类,位置cn.itcast.gateway.GatewayApplication:
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
2、编写网关的路由配置及Nacos地址:
server:
port: 10010
spring:
application:
name: gateway
cloud:
nacos:
server-addr: localhost:8848 # Nacos地址
gateway:
routes:
- id: user-service # 路由的标识,必须唯一
# uri: http://localhost:8081 # 固定的路由目标地址
uri: lb://userservice # lb表示负载均衡,后面跟上服务的名称
predicates: # 路由的断言,也就是判断请求能不能符合路由规则的条件
- Path=/user/** # 路径满足/user/开头的就符合要求,托管给userservice服务
- id: order-service
uri: lb://orderservice
predicates:
- Path=/order/**
访问:
可以看到我们的网关本身并没有做什么操作,但是可以访问到相应信息,也就是说网关只是把请求转发给微服务。
流程图:
网关路由可以配置的信息包括:
路由id:路由唯一标识
uri:路由目的地,支持lb与http两种
predicates:路由断言,判断请求是否复杂要求,符合则转发到路由目的地
filters:路由过滤器,用于处理请求或者响应
我们在配置文件中写的断言规则只是字符串,这些字符串会被断言工厂(Predicate
Factory)读取并处理,转变为路由判断的条件。
Spring提供了11种基本的Predicate工厂:
如果不会写的话,就去Spring官网上面看实例,上面提供了11种断言工厂的演示。
在这里进行After的演示:
- After=2030-04-13T15:14:34.433+08:00[Asia/Shanghai]
只有在这个时间之后的请求才会符合这一条要求,显然这里是无法访问到的:
总结:
路由工厂的作用:读取用户的断言条件,对请求做出判断
Path=/user/**的作用:路径是以/user开头的就认为是符合的
GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理。
之前已经知道了,当用户发起请求的时候,要通过网关中的路由,基于断言工厂来确定将要去哪个微服务,但在这之间,网关可以给通过路由的请求再增加过滤器链,请求要经过这些过滤器链才能到达微服务,同样微服务返还数据给用户的时候也需要经过这些东西:
Spring提供了30多种不同的路由过滤工厂,这当然不是全部去学的,当有这方面的需求的时候就直接去看Spring官方文档就行了。
在这里进行一个简单的演示:给所有进入userservice的请求添加一个请求头。
实现方式:在gateway中修改application.yml文件,给userservice的路由添加过滤器
验证是否增加了这个请求头,只需要在UserController中获取请求头并且打印即可:
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id, @RequestHeader(value = "Truth", required = false) String truth) {
System.out.println("truth = " + truth);
return userService.queryById(id);
}
访问user网址后在控制台中可以看到输出:
而如果我们希望全局都可以增加这个过滤器,也就是全局都会增加这个请求头,只需要:
总结:
过滤器的作用:
(1)对路由的请求或响应做加工处理,比如添加请求头
(2)配置在路由下的过滤器只对当前路由的请求生效
defaultFilters的作用:对所有的路由都生效
全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。
区别在于GatewayFilter通过配置定义,处理逻辑是固定的。而GlobalFilter的逻辑需要自己写代码实现,而不是全部交给Spring容器,而且更灵活。
定义方式是实现GlobalFilter接口。
在这里定义定义一个全局过滤器,判断请求的参数是否满足:
1、参数中是否有authorization
2、authorization参数值是否为admin
如果是,则放行,否则拦截。
代码的编写还是比较麻烦的,因为这里面涉及了不少基于响应式编程的API,需要多花点时间敲才能更好的掌握。
//@Order(-1) //可以注解设置过滤器的优先级,越小则优先级越高,-1表示最高优先级
@Component //需要将实现设置为Spring的组件
public class AuthorizeFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//获取请求参数(通过上下文获取)
ServerHttpRequest request = exchange.getRequest();
MultiValueMap<String, String> params = request.getQueryParams();
//获取参数中的authorization参数
String auth = params.getFirst("authorization");
//判断参数值是否等于admin
if("admin".equals(auth)){
//相等,放行
//chain会直接找这个过滤器的下一个过滤器
return chain.filter(exchange);
}
//不相等,拦截
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);//设置状态码,401表示未登录
return exchange.getResponse().setComplete();
}
@Override
public int getOrder() {//也可以用接口指定优先级
return -1;
}
}
全局过滤器的作用:对所有路由都生效,并且可以自定义处理逻辑
实现全局过滤器的步骤:
(1)实现GlobalFilter接口
(2)添加@Order接口或实现Ordered接口用于设定优先级
(3)编写处理逻辑
请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter。
请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter合并到一个过滤器链中,排序后依次执行每个过滤器。
排序的规则:
每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前:
(1)GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由自己决定
(2)路由过滤器和defaultFilter的order是由Spring指定的,是按照配置文件中的声明顺序来递增的
当过滤器的order值一样时,会按照defaultFilter>路由过滤器>GlobalFilter的顺序执行。
记一下结论就行了,源码暂时还没去读。
在web的时候已经解决过了跨域问题,而微服务里面也有这样的问题,因为所有的请求都要经过网关,也就是说,跨域请求不需要在微服务这里做处理,只需要在网关这里处理就行了。
而网关这里的实现不太一样,它没有Servlet相关的API,是响应式编程。
先重新分析一下跨域问题。
跨域,即域名不一致的就是跨域,主要包括:
(1)域名不同: www.taobao.com 和 www.taobao.org 和 www.jd.com 和 miaosha.jd.com
(2)域名相同,端口不同:localhost:8080和localhost8081
跨域问题:浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题
解决方案:CORS
在网关其实已经做好了CORS相关的底层逻辑了,只需要做相应的配置就可以解决问题了。
cloud:
gateway:
globalcors: # 全局跨域处理
add-to-simple-url-handler-mapping: true # 解决options请求被拦截的问题
corsConfiguration:
'[/**]':
allowedOrigins: # 允许哪些网站的跨域请求
- "http://localhost:8090"
- "http://www.leyou.com"
allowedMethods: # 允许的跨域Ajax的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允许在请求中携带的头信息
allowCredentials: true # 是否允许携带cookie
maxAge: 360000 # 这次跨域检测的有效期