网站建设 申请报告,seo优化查询,自贡建设监督质量网站,seo怎么给网站做外链大家好#xff0c;我是烤鸭#xff1a; 今天分享 Zuul 1.x 升级 springcloud gateway 2.x 遇到的一点问题。
介绍
zuul 和springcloud gateway 都是比较优秀的网关#xff0c;而 zuul 1.x 采用的是 servlet 模型#xff0c;gate 采用的是 reactor模型#xff0c;效率和资…大家好我是烤鸭 今天分享 Zuul 1.x 升级 springcloud gateway 2.x 遇到的一点问题。
介绍
zuul 和springcloud gateway 都是比较优秀的网关而 zuul 1.x 采用的是 servlet 模型gate 采用的是 reactor模型效率和资源上 gateway 要优秀一些。
zuul 和 springcloud 在 filter 架构上类似都提供了基类 ZuulFilter 和 GlobalFilter只要继承/实现基类就可以自定义拦截器并且有排序。
升级场景
由于原来的网关项目并没有完全脱离业务场景(使用了部分zuul的拦截器和大量的自定义拦截器白名单、签名校验等等)所以改造起来是比较麻烦的。
在全局参数管理上由于是单线程模型zuul1 采用 TheadLocal 维护的 RequestContext而 gateway 需要使用 exchange 传递上下文参数。
zuul的拦截器改造
原网关的配置有 :
zuul.ignored-services*
zuul.ignoredPatterns/abc,/abd
zuul.sensitiveHeaderskey1zuul.ignored-services* zuul有默认的隐射机制 * 表示禁用默认路由外界无法访问未配置路由的服务 zuul.ignoredPatterns/abc/aaa,/abd 如果路由配置了 /abc/**那么abc/aaa会被过滤其他访问正常 zuul.sensitiveHeaderskey1忽略某个敏感的请求头(不向下游传递)
zuul.ignored-services
springcloud 默认是不会转发未配置的路由所以第一个不需要考虑。
zuul.ignoredPatterns/abc/aaa,/abd
过滤请求路径gateway没有现成的filter所以需要自己写一个我用的 spring security
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-security/artifactId
/dependencyWebSecurityConfig
package test.gateway.config;import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.stereotype.Component;RefreshScope
Configuration
public class WebSecurityConfig {/*** 网关入口黑名单*/Value(${gateway.routes.black:/abc/aaa,/abd})private String[] blacklist;/*** 网关入口白名单*/Value(${gateway.routes.white:/*})private String[] whitelist;Beanpublic SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {http.csrf().disable().authorizeExchange()//需要授权使用的特殊接口,黑白名单顺序很重要需要授权的放上面.pathMatchers(blacklist).authenticated()//无需进行权限过滤的请求路径.pathMatchers(whitelist).permitAll().anyExchange().authenticated().and().httpBasic().and().formLogin();return http.build();}}
zuul.sensitiveHeaders
这个其实gateway 有现成的filter只要配置下就可以了RemoveRequestHeaderGatewayFilterFactory
调下游服务之前会把这个header去掉但是我这个场景不符合只能手写一个filter。
原因是要过滤前端传过来的某个key(重要的头参数)正常这个参数会从token中解密获得如果解密成功会覆盖前端传过来的(使用的是 RequestContext.addZuulRequestHeader当然前端也可能没传)。怕的是接口被盗刷随意传重要的头参数而不会走解密(不是所以接口都需要解密)所以需要在前端传了的情况下remove掉。 RemoveRequestHeaderGatewayFilterFactory 只是在调下游服务前过滤会过滤正常的(解密成功覆盖的)。所以需要手写filter顺序在 解密的filter 之前执行。
SensitiveHeadersFilter
package test.gateway.filters.pre;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
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.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;Component
public class SensitiveHeadersFilter implements GlobalFilter, Ordered {private static Logger logger LoggerFactory.getLogger(SensitiveHeadersFilter.class);/*** 过滤请求头*/Value(${gateway.sensitive.headers:key})private String[] sensitiveHeaders;/*** param* return int* Author * Description 只要在 DecryptFilter 之前执行就可以目的是移除配置的请求头* Date 2021/2/7 16:35**/Overridepublic int getOrder() {return HIGHEST_PRECEDENCE 1000;}Overridepublic MonoVoid filter(ServerWebExchange exchange, GatewayFilterChain chain) {HttpHeaders headers exchange.getRequest().getHeaders();if (headers ! null) {for (int i 0; i sensitiveHeaders.length; i) {if(headers.containsKey(sensitiveHeaders[i])){headers.remove(sensitiveHeaders[i]);}}}return chain.filter(exchange);}
}
路由配置
改造如下
#zuul.routes.aaa.path/aaa
#zuul.routes.aaa.urlhttp://www.test.com/
spring.cloud.gateway.routes[0].idaaa
spring.cloud.gateway.routes[0].filters[0].nameStripPrefix
spring.cloud.gateway.routes[0].filters[0].args.parts1
spring.cloud.gateway.routes[0].predicates[0].namePath
spring.cloud.gateway.routes[0].predicates[0].args[pattern]/aaa
spring.cloud.gateway.routes[0].urihttp://www.test.com/#zuul.routes.bbb.path/bbb/**
#zuul.routes.bbb.serviceIdbbb
spring.cloud.gateway.routes[1].idbbb
spring.cloud.gateway.routes[1].filters[0].nameStripPrefix
spring.cloud.gateway.routes[1].filters[0].args.parts1
spring.cloud.gateway.routes[1].predicates[0].namePath
spring.cloud.gateway.routes[1].predicates[0].args[pattern]/bbb/**
spring.cloud.gateway.routes[1].urilb://bbb其他逻辑修改
由于模型不同获取参数的方式等等都不一样除了代码上兼容逻辑上还需要再测试。
就比如如果zuul想获取post请求body体的参数
可以直接使用 (RequestContext)ctx.getRequest().getParameterMap() 调用的是 com.netflix.zuul.http.HttpServletRequestWrapper.parseRequest() 可以直接解析参数后返回map而 gateway 里没有类似的方法的还需要考虑到流读取后再写回的问题。
类似场景可以参考下面这个过滤器在后续需要用到参数的地方 可以通过 exchange.getAttributes(“POST_BODY”) 获取
HttpRequestGlobalFilter
package test.filters.pre;import java.nio.charset.StandardCharsets;import test.ZuulFilterAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;public class HttpRequestGlobalFilter extends ZuulFilterAdapter {private static Logger logger LoggerFactory.getLogger(HttpRequestGlobalFilter.class);Overridepublic MonoVoid filter(ServerWebExchange exchange, GatewayFilterChain chain) {ServerHttpRequest request exchange.getRequest();String requestUrl request.getPath().toString();String requestMethod request.getMethodValue();if (HttpMethod.POST.toString().equals(requestMethod) || HttpMethod.PUT.toString().equals(requestMethod)) {return DataBufferUtils.join(exchange.getRequest().getBody()).flatMap(dataBuffer - {byte[] bytes new byte[dataBuffer.readableByteCount()];dataBuffer.read(bytes);String postRequestBodyStr new String(bytes, StandardCharsets.UTF_8);String contentType request.getHeaders().getFirst(Content-Type);if (requestUrl.contains(/web/api/file) || contentType.startsWith(multipart/form-data)) {logger.info(\n 请求url:{} \n 请求类型{} \n 文件上传, requestUrl, requestMethod);} else {logger.info(\n 请求url:{} \n 请求类型{} \n 请求参数{}, requestUrl, requestMethod, postRequestBodyStr);}exchange.getAttributes().put(POST_BODY, postRequestBodyStr);DataBufferUtils.release(dataBuffer);FluxDataBuffer cachedFlux Flux.defer(() - {DataBuffer buffer exchange.getResponse().bufferFactory().wrap(bytes);return Mono.just(buffer);});// 下面的将请求体再次封装写回到request里传到下一级否则由于请求体已被消费后续的服务将取不到值ServerHttpRequest mutatedRequest new ServerHttpRequestDecorator(exchange.getRequest()) {Overridepublic FluxDataBuffer getBody() {return cachedFlux;}};// 封装request传给下一级return chain.filter(exchange.mutate().request(mutatedRequest).build());});} else if (HttpMethod.GET.toString().equals(requestMethod)|| HttpMethod.DELETE.toString().equals(requestMethod)){MultiValueMapString, String getRequestParams request.getQueryParams();logger.debug(\n 请求url:{} \n 请求类型{} \n 请求参数{}, requestUrl, requestMethod, getRequestParams);return chain.filter(exchange);}return chain.filter(exchange);}
}
总结
目前升级还没有完成坑比想象中的要多… 除了框架本身的问题还有代码年久失修、业务逻辑等等一系列的问题 代码重构 任重道远啊