0.代码

https://github.com/fengdaizang/OpenAPI

1.引入相关依赖

pom文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<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>OpenAPI</artifactId>
<groupId>com.fdzang.microservice</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion> <artifactId>api-gateway</artifactId> <dependencies>
     <!-- 公共模块引入了web模块,会与gateway产生冲突,故排除 -->
<dependency>
<groupId>com.fdzang.microservice</groupId>
<artifactId>api-common</artifactId>
<version>1.0-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</exclusion>
</exclusions>
</dependency> <dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency> <dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
</dependency> <dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency> <dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency> <dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency> <dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency> <dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
     <!-- 引入gateway模块 -->    
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>${spring.cloud.starter.version}</version>
</dependency>

     <!-- 引入eureka模块 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>${spring.cloud.starter.version}</version>
</dependency>

     <!-- 引入openfeign模块,这里不要用feign,Springboot2.0已弃用 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>${spring.cloud.starter.version}</version>
</dependency> <dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>${spring.cloud.starter.version}</version>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>${spring.boot.version}</version>
<optional>true</optional>
</dependency>
</dependencies>
</project>

2.配置Gateway

server:
port: 7000

#注册到eureka
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:7003/eureka/

#配置gateway拦截规则
spring:
application:
name: api-gateway
cloud:
gateway:
discovery:
locator:
enabled: true
routes:
- id: gateway
uri: http://www.baidu.com
predicates:
- Path=/**

#这里定义了鉴权的服务名,以及白名单
auth:
service-id: api-auth-v1
gateway:
white:
- /login #这里是id生成器的配置,Twitter-Snowflake
IdWorker:
workerId: 122
datacenterId: 1231

3.过滤器

3.1.ID生成拦截

对每个请求生成一个唯一的请求id

package com.fdzang.microservice.gateway.gateway;

import com.fdzang.microservice.gateway.util.GatewayConstant;
import com.fdzang.microservice.gateway.util.IdWorker;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono; /**
* 生成一个请求的特定id
* @author tanghu
* @Date: 2019/11/5 18:42
*/
@Slf4j
@Component
public class SerialNoFilter implements GlobalFilter, Ordered { @Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest(); String requestId= request.getHeaders().getFirst(GatewayConstant.REQUEST_TRACE_ID);
if (StringUtils.isEmpty(requestId)) {
Object attribute = exchange.getAttribute(GatewayConstant.REQUEST_TRACE_ID);
if (attribute == null) {
requestId = String.valueOf(IdWorker.getWorkerId());
exchange.getAttributes().put(GatewayConstant.REQUEST_TRACE_ID,requestId);
}
}else{
exchange.getAttributes().put(GatewayConstant.REQUEST_TRACE_ID,requestId);
} return chain.filter(exchange);
} @Override
public int getOrder() {
return GatewayConstant.Order.SERIAL_NO_ORDER;
}
}

3.2.鉴权拦截

获取请求头中的鉴权信息,对信息校验,这里暂时没有做(AuthResult authService.auth(AuthRequest request)),这里需求请求其他模块对请求信息进行校验,返回校验结果

package com.fdzang.microservice.gateway.gateway;

import com.fdzang.microservice.common.entity.auth.AuthCode;
import com.fdzang.microservice.common.entity.auth.AuthRequest;
import com.fdzang.microservice.common.entity.auth.AuthResult;
import com.fdzang.microservice.gateway.service.AuthService;
import com.fdzang.microservice.gateway.util.GatewayConstant;
import com.fdzang.microservice.gateway.util.WhiteUrl;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono; import java.util.List;
import java.util.Map;
import java.util.TreeMap; /**
* 权限校验
* @author tanghu
* @Date: 2019/10/22 18:00
*/
@Slf4j
@Component
public class AuthFilter implements GlobalFilter, Ordered { @Autowired
private AuthService authService; @Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String requestId = exchange.getAttribute(GatewayConstant.REQUEST_TRACE_ID);
String url = exchange.getRequest().getURI().getPath(); ServerHttpRequest request = exchange.getRequest(); //跳过白名单
if(null != WhiteUrl.getWhite() && WhiteUrl.getWhite().contains(url)){
return chain.filter(exchange);
} //获取权限校验部分
//Authorization: gateway:{AccessId}:{Signature}
String authHeader = exchange.getRequest().getHeaders().getFirst(GatewayConstant.AUTH_HEADER);
if(StringUtils.isBlank(authHeader)){
log.warn("request has no authorization header, uuid:{}, request:{}",requestId, url); throw new IllegalArgumentException("bad request");
} List<String> auths = Splitter.on(":").trimResults().omitEmptyStrings().splitToList(authHeader);
if(CollectionUtils.isEmpty(auths) || auths.size() != 3 || !GatewayConstant.AUTH_LABLE.equals(auths.get(0))){
log.warn("bad authorization header, uuid:{}, request:[{}], header:{}",
requestId, url, authHeader); throw new IllegalArgumentException("bad request");
} //校验时间戳是否合法
String timestamp = exchange.getRequest().getHeaders().getFirst(GatewayConstant.TIMESTAMP_HEADER);
if (StringUtils.isBlank(timestamp) || isTimestampExpired(timestamp)) {
log.warn("wrong timestamp:{}, uuid:{}, request:{}",
timestamp, requestId, url);
} String accessId = auths.get(1);
String sign = auths.get(2); String stringToSign = getStringToSign(request, timestamp); AuthRequest authRequest = new AuthRequest();
authRequest.setAccessId(accessId);
authRequest.setSign(sign);
authRequest.setStringToSign(stringToSign);
authRequest.setHttpMethod(request.getMethodValue());
authRequest.setUri(url); AuthResult authResult = authService.auth(authRequest); if (authResult.getStatus() != AuthCode.SUCEESS.getAuthCode()) {
log.warn("checkSign failed, uuid:{}, accessId:{}, request:[{}], error:{}",
requestId, accessId, url, authResult.getDescription());
throw new RuntimeException(authResult.getDescription());
} log.info("request auth finished, uuid:{}, orgCode:{}, userName:{}, accessId:{}, request:{}, serviceName:{}",
requestId, authResult.getOrgCode(),
authResult.getUsername(), accessId,
url, authResult.getServiceName()); exchange.getAttributes().put(GatewayConstant.SERVICE_NAME,authResult.getServiceName()); return chain.filter(exchange);
} /**
* 获取原始字符串(签名前)
* @param request
* @param timestamp
* @return
*/
private String getStringToSign(ServerHttpRequest request, String timestamp){
// headers
TreeMap<String, String> headersInSign = new TreeMap<>();
HttpHeaders headers = request.getHeaders();
for (Map.Entry<String,List<String>> header:headers.entrySet()) {
String key = header.getKey();
if (key.startsWith(GatewayConstant.AUTH_HEADER_PREFIX)) {
headersInSign.put(key, header.getValue().get(0));
}
} StringBuilder headerStringBuilder = new StringBuilder();
for (Map.Entry<String, String> entry : headersInSign.entrySet()) {
headerStringBuilder.append(entry.getKey()).append(":").append(entry.getValue()).append("\n");
}
String headerString = null;
if (headerStringBuilder.length() != 0) {
headerString = headerStringBuilder.deleteCharAt(headerStringBuilder.length()-1).toString();
} // Url_String
TreeMap<String, String> paramsInSign = new TreeMap<>();
MultiValueMap<String, String> parameterMap = request.getQueryParams();
if (MapUtils.isNotEmpty(parameterMap)) {
for (Map.Entry<String, List<String>> entry : parameterMap.entrySet()) {
paramsInSign.put(entry.getKey(), entry.getValue().get(0));
}
} // 原始url
String originalUrl = request.getURI().getPath(); StringBuilder uriStringBuilder = new StringBuilder(originalUrl);
if (!parameterMap.isEmpty()) {
uriStringBuilder.append("?");
for (Map.Entry<String, String> entry : paramsInSign.entrySet()) {
uriStringBuilder.append(entry.getKey());
if (StringUtils.isNotBlank(entry.getValue())) {
uriStringBuilder.append("=").append(entry.getValue());
}
uriStringBuilder.append("&");
}
uriStringBuilder.deleteCharAt(uriStringBuilder.length()-1);
} String uriString = uriStringBuilder.toString(); String contentType = headers.getFirst(HttpHeaders.CONTENT_TYPE); //这里可以对请求参数进行MD5校验,暂时不做
String contentMd5 = headers.getFirst(GatewayConstant.CONTENTE_MD5); String[] parts = {
request.getMethodValue(),
StringUtils.isNotBlank(contentMd5) ? contentMd5 : "",
StringUtils.isNotBlank(contentType) ? contentType : "",
timestamp,
headerString,
uriString
}; return Joiner.on(GatewayConstant.STRING_TO_SIGN_DELIM).skipNulls().join(parts);
} /**
* 校验时间戳是否超时
* @param timestamp
* @return
*/
private boolean isTimestampExpired(String timestamp){
long l = NumberUtils.toLong(timestamp, 0L);
if (l == 0) {
return true;
} return Math.abs(System.currentTimeMillis() - l) > GatewayConstant.EXPIRE_TIME_SECONDS *1000;
} @Override
public int getOrder() {
return GatewayConstant.Order.AUTH_ORDER;
}
}

3.3.服务分发

根据鉴权后的结果能得到服务名,然后重写路由以及请求,对该次请求进行转发

package com.fdzang.microservice.gateway.gateway;

import com.fdzang.microservice.gateway.util.GatewayConstant;
import com.fdzang.microservice.gateway.util.WhiteUrl;
import com.google.common.base.Splitter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono; import java.util.List; /**
* @author tanghu
* @Date: 2019/11/6 15:39
*/
@Slf4j
@Component
public class ModifyRequestFilter implements GlobalFilter, Ordered { @Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String url = exchange.getRequest().getURI().getPath();
ServerHttpRequest request = exchange.getRequest(); //跳过白名单
if(null != WhiteUrl.getWhite() && WhiteUrl.getWhite().contains(url)){
return chain.filter(exchange);
} String serviceName = exchange.getAttribute(GatewayConstant.SERVICE_NAME); //修改路由
Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
Route newRoute = Route.async()
.asyncPredicate(route.getPredicate())
.filters(route.getFilters())
.id(route.getId())
.order(route.getOrder())
.uri(GatewayConstant.URI.LOAD_BALANCE+serviceName).build(); exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR,newRoute); //修改请求路径
List<String> strings = Splitter.on("/").omitEmptyStrings().trimResults().limit(3).splitToList(url);
String newServletPath = "/" + strings.get(2); ServerHttpRequest newRequest = request.mutate().path(newServletPath).build(); return chain.filter(exchange.mutate().request(newRequest).build());
} @Override
public int getOrder() {
return GatewayConstant.Order.MODIFY_REQUEST_ORDER;
}
}

3.4.统一响应

对响应进行统一封装

package com.fdzang.microservice.gateway.gateway;

import com.alibaba.fastjson.JSON;
import com.fdzang.microservice.common.entity.ApiResult;
import com.fdzang.microservice.gateway.entity.GatewayError;
import com.fdzang.microservice.gateway.entity.GatewayResult;
import com.fdzang.microservice.gateway.entity.GatewayResultEnums;
import com.fdzang.microservice.gateway.util.GatewayConstant;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono; import java.nio.charset.Charset; /**
* @author tanghu
* @Date: 2019/11/7 8:58
*/
@Slf4j
@Component
public class ModifyResponseFilter implements GlobalFilter, Ordered { @Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String requestId = exchange.getAttribute(GatewayConstant.REQUEST_TRACE_ID);
ServerHttpResponse originalResponse = exchange.getResponse();
DataBufferFactory bufferFactory = originalResponse.bufferFactory();
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
if (body instanceof Flux) {
Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
return super.writeWith(fluxBody.map(dataBuffer -> {
byte[] content = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(content);
//释放掉内存
DataBufferUtils.release(dataBuffer); String originalbody = new String(content, Charset.forName("UTF-8"));
String finalBody = originalbody; ApiResult apiResult = JSON.parseObject(originalbody,ApiResult.class); GatewayResult result = new GatewayResult();
result.setCode(GatewayResultEnums.SUCC.getCode());
result.setMsg(GatewayResultEnums.SUCC.getMsg());
result.setReq_id(requestId);
if (apiResult.getCode() == null && apiResult.getMsg() == null) {
// 尝试解析body为网关的错误信息
GatewayError gatewayError = JSON.parseObject(originalbody,GatewayError.class);
result.setSub_code(gatewayError.getStatus());
result.setSub_msg(gatewayError.getMessage());
} else {
result.setSub_code(apiResult.getCode());
result.setSub_msg(apiResult.getMsg());
} result.setData(apiResult.getData()); finalBody = JSON.toJSONString(result); return bufferFactory.wrap(finalBody.getBytes());
}));
} return super.writeWith(body);
}
};
return chain.filter(exchange.mutate().response(decoratedResponse).build());
} @Override
public int getOrder() {
return GatewayConstant.Order.MODIFY_RESPONSE_ORDER;
}
}

4.测试

10:25:54.961 [main] INFO  c.f.microservice.mock.util.SignUtil - StringToSign:
GET 1573093554201
/v2/base/zuul/tag/getMostUsedTags?from=2017-11-25 00:00:00&plate_num=部A11110&to=2017-11-30 00:00:00
10:25:54.979 [main] INFO c.f.microservice.mock.util.HttpUtil - sign:Y+usbpHlwOw4F2sq4b0pNjgXGDAXoYgs1syOOPxPFAE=
10:25:59.868 [main] INFO com.fdzang.microservice.mock.Demo - {"code":0,"data":[{"tagPublishedRefCount":3,"tagTitle":"Solo","id":"1533101769023","tagReferenceCount":3},{"tagPublishedRefCount":1,"tagTitle":"tetet","id":"1559285894006","tagReferenceCount":1}],"msg":"succ","req_id":"2627469547766022144","sub_code":0,"sub_msg":"ok"} Process finished with exit code 0

由返回结果,可知此次请求完成。

5.注意事项

转发的目标服务需要跟网关注册在同一个注册中心下,路由uri配置为 lb://service_name,则会转发到对应的服务下,并且gateway会自动采用负载均衡机制

响应请求的顺序需要小于 NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER 该值为-1

其他拦截器的顺序无固定要求,值越小越先执行

SpringCloud:搭建基于Gateway的微服务网关(二)的更多相关文章

  1. SpringCloud:搭建基于Gateway的微服务网关(一)

    1.需求 最近在尝试着写一个开放平台,于是先搭建网关. 作用:统一的请求入口,完成对请求的跟踪,限流(未做),鉴权,分发,封装响应 2.工作原理 2.1.请求 在开放平台中申请对接口的使用,申请通过后 ...

  2. 小D课堂 - 新版本微服务springcloud+Docker教程_6-06 zuul微服务网关集群搭建

    笔记 6.Zuul微服务网关集群搭建     简介:微服务网关Zull集群搭建 1.nginx+lvs+keepalive      https://www.cnblogs.com/liuyisai/ ...

  3. 【SpringCloud构建微服务系列】微服务网关Zuul

    一.为什么要用微服务网关 在微服务架构中,一般不同的微服务有不同的网络地址,而外部客户端(如手机APP)可能需要调用多个接口才能完成一次业务需求.例如一个电影购票的手机APP,可能会调用多个微服务的接 ...

  4. 基于SpringBoot-Dubbo的微服务快速开发框架

    简介: 基于Dubbo的分布式/微服务基础框架,为前端提供脚手架开发服务,结合前一篇--Web AP快速开发基础框架,可快速上手基于Dubbo的分布式服务开发,项目代码: https://github ...

  5. SpringCloud Gateway微服务网关实战与源码分析-上

    概述 定义 Spring Cloud Gateway 官网地址 https://spring.io/projects/spring-cloud-gateway/ 最新版本3.1.3 Spring Cl ...

  6. springcloud(十四):搭建Zuul微服务网关

    springcloud(十四):搭建Zuul微服务网关 1. 2. 3. 4.

  7. 使用 Node.js 搭建微服务网关

    目录 Node.js 是什么 安装 node.js Node.js 入门 Node.js 应用场景 npm 镜像 使用 Node.js 搭建微服务网关 什么是微服务架构 使用 Node.js 实现反向 ...

  8. 微服务网关 Spring Cloud Gateway

    1.  为什么是Spring Cloud Gateway 一句话,Spring Cloud已经放弃Netflix Zuul了.现在Spring Cloud中引用的还是Zuul 1.x版本,而这个版本是 ...

  9. 微服务网关实战——Spring Cloud Gateway

    导读 作为Netflix Zuul的替代者,Spring Cloud Gateway是一款非常实用的微服务网关,在Spring Cloud微服务架构体系中发挥非常大的作用.本文对Spring Clou ...

随机推荐

  1. scp 基本用法(提高scp传输速度)

    Outline spc 可以帮你实现: Linux Server 之间互传数据: Linux Server 和 Windows Server 之间互传数据: 参考: https://www.cnblo ...

  2. EXT.NET Combox下拉Grid

    <ext:ComboBox ID="cmbCategory" runat="server" TypeAhead="true" Forc ...

  3. vue条形码生成插件vue-barcode

    更详细的请查阅官方文档 https://github.com/lindell/vue-barcode vue-barcode是JsBarcode的一个简单包装.所以在使用时的配置属性需要在JsBarc ...

  4. renren-fast后端源码参考-配置和对应工具

    1. renren-fast后端源码参考-配置和对应工具 1.1. 前言 renren-fast是个开源的前后端分离快速开放平台,没有自己框架的同学可以直接使用它的,而我打算浏览一遍它的代码,提取一些 ...

  5. Qt使用QPainter绘制矢量图并保存为svg文件

    位图和矢量图: Bitmap: Usually a larger file size Cannot be enlarged into a higher resolution as the image ...

  6. python爬虫爬取天气数据并图形化显示

    前言 使用python进行网页数据的爬取现在已经很常见了,而对天气数据的爬取更是入门级的新手操作,很多人学习爬虫都从天气开始,本文便是介绍了从中国天气网爬取天气数据,能够实现输入想要查询的城市,返回该 ...

  7. /bin/false 和 /usr/sbin/nologin

    比较常用的用法: #添加一个不能登录的用户 useradd -d /usr/local/apache -g apache -s /bin/false apache 要拒绝系统用户登录,可以将其shel ...

  8. 【干货】gitlab-11.10.4版本汉化

    目录 1.YUM安装gitlab-11.10.4 2.gitlab汉化技能 1.YUM安装gitlab-11.10.4 下载gitlab-ce的repo [root@localhost ~]# cur ...

  9. 【Linux】Linux下查找JDK默认安装路径

    一.查找效果图 二.查找步骤 1.首先确认是否已按照JDKjava -version [root@iZ2ze3zda3caeyx6pn7c5zZ base-eureka]# java -version ...

  10. Odoo字段类型详解

    转载请注明原文地址:https://www.cnblogs.com/ygj0930/p/10826099.html   一:基本字段类型 Binary:二进制类型,用于保存图片.视频.文件.附件等,在 ...