网关的作用
网关的作用主要有:
- 对用户请求做身份认证、权限校验
- 将用户请求路由到微服务,并实现负载均衡
- 对用户请求做限流
在SpringCloud中网关的实现主要包括两种:
- zuul,基于servlet实现,属于阻塞式编程。
- gateway,基于Spring5中提供的WebFlux,属于响应式编程的实现,性能更好。更推荐。
搭建网关
第一步,创建一个网关服务,引入依赖
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud-demo</artifactId>
<groupId>cn.lsy.demo</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>gateway</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
</project>
第二步,编写路由配置及nacos地址
server:
port: 10010 #网关端口
spring:
application:
name: gateway #服务名称
cloud:
nacos:
server-addr: localhot:8848 #nacos地址
gateway:
routes: #网关路由配置
- id: user-service #路由id,自定义,只要唯一即可
uri: lb://userservice #路由的目标地址 lb代表负载均衡 后面跟服务名称 还有一种固定地址的方式http://localhost:8081,不推荐
predicates: #路由断言 也就是判断请求是否符合路由规则的条件
- Path=/user/** #这个是按照路径匹配,只要以/user/开头就符合要求
- id: order-service
uri: lb://orderservice
predicates:
- Path=/order/**
启动服务,可以看到,gateway已经注册到了nacos
接下来,使用网关请求试一下,可以看到,访问成功
路由断言工厂
我们再配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件。例如Path=/user/**是按照路径匹配,这个规则是由org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory类来处理的。这样的断言工厂在SpringCloudGateway中还有十几个,见下图
路由过滤器
路由过滤器GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理。
和路由断言工厂一样,Spring中提供了许多过滤器工厂GatewayFilterFactory,见下图
我们测试一下AddRequestHeader
配置文件添加过滤器,可以指定服务过滤或全局过滤两种,全局过滤对所有服务生效。可以配置多个过滤器,过滤器会链式调用。
server:
port: 10010 #网关端口
spring:
application:
name: gateway #服务名称
cloud:
nacos:
server-addr: localhost:8848 #nacos地址
discovery:
username: nacos
password: nacos
gateway:
routes: #网关路由配置
- id: user-service #路由id,自定义,只要唯一即可
uri: lb://userservice #路由的目标地址 lb代表负载均衡 后面跟服务名称 还有一种固定地址的方式http://localhost:8081,不推荐
predicates: #路由断言 也就是判断请求是否符合路由规则的条件
- Path=/user/** #这个是按照路径匹配,只要以/user/开头就符合要求
filters: #针对指定服务过滤
- AddRequestHeader=myhead,zhangsan
- id: order-service
uri: lb://orderservice
predicates:
- Path=/order/**
default-filters: #全局过滤
- AddRequestHeader=myhead,lisi
获取请求头参数,打印
package cn.lsy.user.web;
import cn.lsy.user.config.PatternProperties;
import cn.lsy.user.pojo.User;
import cn.lsy.user.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@Slf4j
@RestController
@RequestMapping("/user")
//@RefreshScope
public class UserController {
@Autowired
private UserService userService;
@Autowired
private PatternProperties patternProperties;
// @Value("${pattern.dateformat}")
// private String dateformat;
@GetMapping("prop")
public PatternProperties prop() {
return patternProperties;
}
@GetMapping("now")
public String now() {
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(patternProperties.getDateformat()));
}
/**
* @param id 用户id
* @return 用户信息
*/
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id, @RequestHeader(value = "myhead", required = false) String myhead) {
System.out.println("myhead: " + myhead);
return userService.queryById(id);
}
}
测试,这里注意,要通过网关调用http://localhost:10010/user/{id},直接调用localhost:8081服务是无效的
输出zhangsan,说明成功经过了过滤器添加了请求头,并且是以指定服务下的过滤器为准。
全局过滤器
测试一下,自定义一个类实现GlobalFilter,如果请求参数带token,则放行,否则返回401
package cn.lsy.gateway.filter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
//@Order(123)
public class AuthorizeFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
MultiValueMap<String, String> queryParams = request.getQueryParams();
String token = queryParams.getFirst("token");
if(token.equals("admin123")){
return chain.filter(exchange);
}
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
@Override
public int getOrder() {
return 456;
}
}
order用于定义过滤器的顺序,值越小,越早执行,可以添加@Order注解或者实现Ordered接口,重启网关服务测试
错误token:返回401
正确输入token:返回数据
过滤器执行顺序
默认过滤器和路由过滤器都是GatewayFilter,而全局过滤器是GlobalFilter,GlobalFilter会通过一个过滤器适配器GatewayFilterAdapter适配成GatewayFilter。
所以他们可以放到一个过滤器链中排序。当order值不一样时,执行顺序是从小到大。order值一样时,按照defaultFilter->局部路由过滤器->全局过滤器。
跨域问题
跨域主要指的是域名不一致,包括域名不同和端口不同等。跨域问题主要是由浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题。一般是通过CORS解决。gateway网关内部已经替我们做好CORS的内部逻辑了,我们只要做一些配置就可以。
CORS跨域主要配置几个参数:
- 允许哪些域名跨域
- 允许哪些请求头
- 允许哪些请求方式
- 是否允许使用cookie
- 有效期是多久