spring cloud Gateway简单使用
一、引子
2年前有幸使用过一次Spring Cloud (1.5.9),1.*集成的是ZUUL做网关,没有使用Gateway做网关,一直是个小遗憾。终于在2年后的19年底再次使用Spring Cloud,这次果断使用Spring Cloud 全家桶。网关就是原生的Spring Cloud Gateway(项目使用2.1.4)。一个简单的创业项目架构图如下:
二、Gateway设计思想
2.1 官网设计
自从撇开netflex zuul后,spring Cloud速度搜搜的。我开发时还是用2.1.4,目前最新已经到2.2.1,附上官网飞机票
2.1.1 特性
-
Built on Spring Framework 5, Project Reactor and Spring Boot 2.0:基于 Spring Framework 5,Project Reactor 和 Spring Boot 2.0
-
Able to match routes on any request attribute.:能匹配任意请求属性的路由
-
Predicates and filters are specific to routes.:针对特定路由使用匹配策略和过滤器
-
Hystrix Circuit Breaker integration. :集成Hystri断路器
-
Spring Cloud DiscoveryClient integration:集成服务发现(gateway一样可注册到eureka)
-
Easy to write Predicates and Filters:易于写策略(断言)+过滤器
-
Request Rate Limiting 请求限流
-
Path Rewriting:重写path
简单来说就是Route、Predicate、Filter三大核心组件。
2.1.2 流程图
如上图,Gateway Client客户端发送请求在Gateway Handler Mapping中查找是否命中路由策略,命中的话请求转发给Gateway Web Handler来处理。根据定义的多个Filter链,执行顺序:Pre Filter->代理请求->Post Filter。
2.1.3 内置Predicates+Filter
Gateway内置了11个Predicates Factories路由策略(断言)工厂类。
Filter分2类:
- 31个GatewayFilter Factories网关过滤器工厂类
- 10个GlobalFilter 全局过滤器接口
这里就不在过多介绍,建议有需求时可以去官网找找,没有的话再自己开发。
2.2 我们的使用
1.使用Route结合Hystrix实现默认降级策略
2.使用GatewayFilter接口,自定义过滤器类,实现登录态(token)校验
三、Gateway简单使用
3.1 实现微服务的默认降级策略
spring: cloud: gateway: discovery: locator: enabled: false #开启小写验证,默认feign根据服务名查找都是用的全大写 lowerCaseServiceId: true default-filters: - AddResponseHeader=X-Response-Default-Foo, Default-Bar routes: - id: OLOAN-FINANCIAL-PRODUCT-SERVICE # lb代表从注册中心获取服务 uri: lb://OLOAN-FINANCIAL-PRODUCT-SERVICE predicates: # 转发该路径 - Path=/gateway/financialProduct/** # 带前缀 filters: - StripPrefix=1 - name: Hystrix args: name: fallbackcmd fallbackUri: forward:/defaultfallback - id: ADMIN-SERVICE uri: lb://ADMIN-SERVICE predicates: - Path=/gateway/auth/** filters: - StripPrefix=2 - name: Hystrix args: name: fallbackcmd fallbackUri: forward:/defaultfallback
如上图,我们开启了2个微服务route路由。
- 1)前端请求时path带/gateway/,在gateway层使用StripPrefix=1,去掉gateway,最终微服务上的path不带"/gateway/".
- 2)使用Hystrix实现默认降级策略,降级接口实现如下:
@Slf4j @RestController public class DefaultHystrixController { @RequestMapping("/defaultfallback") public ApiResult defaultfallback(){ log.info("服务降级中"); return ApiResult.failure("服务异常"); } }
3.2 实现登录态(token)校验
3.2.1 自定义过滤器
自定义过滤器,实现GatewayFilter, Ordered 2个接口。
import com.*.auth.UserTokenTools; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.core.Ordered; 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; /** * @author denny.zhang * @Description token过滤器 * @date 2019/12/12 13:55 */ @Slf4j @Component public class LoginTokenFilter implements GatewayFilter, Ordered { private static final String AUTHORIZE_TOKEN = "Authorization"; private static final String BEARER = "Bearer "; /** * token过滤 * * @param exchange * @param chain * @return */ @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { log.info("当前环境已开启token校验"); ServerHttpRequest request = exchange.getRequest(); HttpHeaders headers = request.getHeaders(); ServerHttpResponse response = exchange.getResponse(); // 取Authorization String tokenHeader = headers.getFirst(AUTHORIZE_TOKEN); log.info("tokenHeader=" + tokenHeader); // token不存在 if (StringUtils.isEmpty(tokenHeader)) { response.setStatusCode(HttpStatus.UNAUTHORIZED); return response.setComplete(); } // 取token String token = this.getToken(tokenHeader); log.info("token=" + token); // token不存在 if (StringUtils.isEmpty(token)) { log.info("token不存在"); response.setStatusCode(HttpStatus.UNAUTHORIZED); return response.setComplete(); } // 校验 token是否失效 if (UserTokenTools.isTokenExpired(token, null)) { log.info("token失效"); response.setStatusCode(HttpStatus.UNAUTHORIZED); return response.setComplete(); } // 校验 token是否正确 if (!UserTokenTools.checkToken(token, null)) { response.setStatusCode(HttpStatus.UNAUTHORIZED); return response.setComplete(); } // //有token 这里可根据具体情况,看是否需要在gateway直接把解析出来的用户信息塞进请求中,我们最终没有使用 // UserTokenInfo userTokenInfo = UserTokenTools.getUserTokenInfo(token); // log.info("token={},userTokenInfo={}",token,userTokenInfo); // request.getQueryParams().add("token",token); //request.getHeaders().set("token", token); return chain.filter(exchange); } @Override public int getOrder() { return -10; } /** * 解析Token */ public String getToken(String requestHeader) { //2.Cookie中没有从header中获取 if (requestHeader != null && requestHeader.startsWith(BEARER)) { return requestHeader.substring(7); } return ""; } }
上图中,UserTokenTools是我们自定义的一个JWT工具类,用来生成token,校验token过期、正确等。
3.2.2 配置路由
大家可根据具体情况,如果只有一套登录态,那就用一个filter即可。
import com.*.gateway.filter.AuthorizeGatewayFilter; import com.*.gateway.filter.LoginTokenFilter; import org.springframework.cloud.gateway.route.RouteLocator; import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class GatewayConfig { @Bean public RouteLocator getRouteLocator(RouteLocatorBuilder builder) { return builder.routes() // token校验1 .route(predicateSpec -> predicateSpec .path("/gateway/pay/card/**", "/gateway/app/**") .filters(gatewayFilterSpec -> gatewayFilterSpec.stripPrefix(1).filter(new AuthorizeGatewayFilter())) .uri("lb://OLOAN-PAY-SERVICE") .id("OLOAN-PAY-SERVICE-token")) // token校验2 .route(predicateSpec -> predicateSpec .path("/gateway/order-audit/**", "/gateway/order/**", "/gateway/order-payment/**") .filters(gatewayFilterSpec -> gatewayFilterSpec.stripPrefix(1).filter(new LoginTokenFilter())) .uri("lb://OLOAN-ORDER-SERVICE") .id("OLOAN-ORDER-ORDER-token")) .build(); } }
四、总结
4.1.WebFlux
Spring Cloud Gateway使用WebFlux,和spring boot web包冲突,使用时一定记得pom中排除原来老WEB那一套(servlet)相关jar,否则会报错。
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> <exclusions> <exclusion> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> </exclusion> </exclusions> </dependency>
4.2.Gateway Filter
Gateway Filter 自带的源码支撑错误码response.setStatusCode(HttpStatus.UNAUTHORIZED);并不是那么的友好。错误码枚举使用的是spring自带框架的枚举类:
org.springframework.http.HttpStatus:
BAD_REQUEST(400, "Bad Request"),
。这样请求返回的结构体和一般定义的(code message data)不同。当然官方也是提供了解决方案。后续再去优化吧。
4.3 限流
gateway默认实现了几个简单的限流策略(依赖redis),后续可以使用一下。
- SpringCloud SpringBoot mybatis 分布式微服务(九)Spring Boot中使用Spring-data-jpa让数据访问更简单
- SpringCloud远程调用方式及restTemplate简单使用
- springcloud+hystrix+turbine 的简单使用查看仪表盘
- Spring Cloud Gateway使用Token验证详解
- SpringCloud之使用nginx做简单的反向代理和负载均衡
- spring cloud 简单使用
- Spring Cloud Gateway使用
- springboot+spring cloud实现简单的gateway注册服务
- Spring Cloud gateway 七 Sentinel 注解方式使用
- Spring Cloud Gateway 使用
- Api Gateway Zuul简介及SpringCloud中的使用
- Spring Cloud Feign简单使用
- Spring Cloud Feign简单使用详解
- 简单尝试Spring Cloud Gateway
- 最新Spring Cloud (Finchley.RELEASE版本)Eureka + Ribbon/Feign+Hystrix+Zuul的简单使用
- springcloud【基于springboot1.x】的简单发布服务和使用
- Spring Cloud Gateway使用简介
- 简单使用spring-cloud
- spring-cloud-task(1)简单使用
- SpringCloud之consul (服务注册发现)客户端的安装与简单使用|第十三章-yellowcong