从零到一搭建nacos微服务之gateway网关【全报文安全处理】(三)

网关的角色是作为一个 API 架构,用来保护、增强和控制对于 API 服务的访问
API 网关是一个处于应用程序或服务(提供 REST API 接口服务)之前的系统,用来管理授权、访问控制和流量限制等,这样 REST API 接口服务就被 API 网关保护起来,对所有的调用者透明。因此,隐藏在 API 网关后面的业务系统就可以专注于创建和管理服务,而不用去处理这些策略性的基础设施.。主要的功能大概下面这几点

  • 性能:API高可用,负载均衡,容错机制。
  • 安全:权限身份认证、脱敏,流量清洗,后端签名
  • 黑名单。
  • 日志:日志记录一旦涉及分布式,全链路跟踪必不可少。
  • 缓存:数据缓存。监控:记录请求响应数据,api耗时分析,性能监控。
  • 限流:流量控制,错峰流控,可以定义多种限流规则。
  • 灰度:线上灰度部署,可以减小风险。
  • 路由:动态路由规则。

这篇文章主要的内容是spring cloud gateway 对请求参数解密,返回参数加密处理以及密钥下发处理

一,搭建基本gateway项目

1.1 maven

         
		
			org.springframework.cloud
			spring-cloud-starter-gateway
		

1.2本地appcation.yml配置

server:
  port: 8087
spring:
  profiles:
    active: dev
  application:
    name: nacos-gateway
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

1.3本地bootstrap.properties配置

因为项目是整合的nacos,所以这里网关的配置也从nacos取

#项目名
spring.application.name=nacos-gateway
#配置中心地址
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
#配置文件尾缀
spring.cloud.nacos.config.file-extension=yml
#配置组
spring.cloud.nacos.config.group=DEV
#命名空间
#spring.cloud.nacos.config.namespace: 7a36049b-ce40-4a3a-97d0-c86858fdcc0f
spring.cloud.nacos.config.refresh-enabled=true



spring.cloud.nacos.config.extension-configs[0].data-id=nacos-gateway-commons.yml
spring.cloud.nacos.config.extension-configs[0].group=DEV
spring.cloud.nacos.config.extension-configs[0].refresh=true

spring.cloud.nacos.config.extension-configs[1].data-id=nacos-gateway-route.yml
spring.cloud.nacos.config.extension-configs[1].group=DEV
spring.cloud.nacos.config.extension-configs[1].refresh=true

这里引入nacos-gateway-route.yml配置如下

nacos-gateway-route.yml

这个是网关路由项目的配置

spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: false  
          #lower-case-service-id: true 
      routes:  #路由数组
        - id: nacos-user  #路由
          uri:  lb://nacos-user #lb 指的是负载均衡 后面是nacos的服务名
          order: 1 #路由优先级  数字越小越大
          predicates:  #断言数组(条件判断,返回值boolean ,转发请求满足条件的)
            - Path=/nacos-user/**
          filters: #过滤器 数组 (在请求传递过程中,对请求做修改)
            - StripPrefix=1 #在请求转发之前去掉 gateway的一层路径
        - id: nacos-order  #路由
          uri:  lb://nacos-order #lb 指的是负载均衡 后面是nacos的服务名
          order: 1 #路由优先级  数字越小越大
          predicates:  #断言数组(条件判断,返回值boolean ,转发请求满足条件的)
            - Path=/nacos-order/**
          filters: #过滤器 数组 (在请求传递过程中,对请求做修改)
            - StripPrefix=1 #在请求转发之前去掉 gateway的一层路径

这个时候访问
localhost:8083/nacos-user/test会转发到nacos-user应用下的/test接口
localhost:8083/nacos-order/test会转发到nacos-order应用下的/test接口

二,请求返回加解密

从零到一搭建nacos微服务之gateway网关【全报文安全处理】(三)_第1张图片

先给大家看一下这样设计的结果

//请求参数
{
	"data": "q9fGLvECb5vDcVh1EKa9Rhf5R56mz6dmRV3G2onljALO7ReKEjqykwBIlL/Jz7wdle7jd03PQhczx0KvrU4JUfMh11YWq5MGkQsid1sENkg=",
	"sign": "AD6A2E46B56FF164476B8A441F45AD77"
}

//返回参数
{
    "data": "S73h3QYbroWrDnnPfRGKKE/lCOWdpOqVe1hQg2vNuDvdLZPe2uHZ94UNGNIm+tjpJ+caeWoToD53cLi0rwEjCF0jPnOQ4h0lvgAeOP6Ov9StU/yTKDZ+YV3490j8XO5wb367fI5TNyp5cpgWFrw+Y2OAbIDKVCjmUHOpkh3m9zv8frhOEtLyw7bavOsrgKeCh6PS5ND8Mk4pxdjDoD1MFKapIxGFv0X+VktRgYmaSfUR/n/zj3KKxHAC6Ear0MRuDvpVZZEe01OepB9wnkGBdNcL3rqnAQspu5zwUJ7WZS0+g3elsqEgn7Ar7yYK75TgWmlzmCNQv9GKN+V4oG4UW1l7SzLRsD+grHRwaGdFLkHqIYPdAlo1XKXPgHWI/nwS+ANVfliZo/76dAru4d889werCOCyuuxHR1f1ML/7b3TfvQ8NffH2y2ELU9/FvTr0L4pAch20UfNtLosDWkqbrmA7PZewWOf7nwmuBPDoXig0+wSI0s7FFHl+eYuDGEoi3NAAakjYggGFoBmg39KsIZcmIwvZWIP6wVyvkO5PKuN5upfDGgQQtcwlzdryQ+12h74aQXpEnIwotESuBNrZbfi3UNnyEyWE2KguHhhZNUGCKx87Sbb0fFOSbrSOXHr0QGtAQxog4EMQvsqzz8NE494ocjjkdhRSGlwxMLqbgBfap0tHeTqCdFkga7WED3YmOiGRXtjmYL/zBXKSl7ScRyVNLT8ETleVrjOJR7WUQEE=",
    "respCode": "0000",
    "respDate": "20201226230745",
    "respMsg": "请求成功"
}

第一步:把请求参数缓存到exchange中

public class ConstantFilter {

    // 设置到 exchange.getAttributes()中的key
    public final static String CACHED_REQUEST_BODY_OBJECT_KEY = "CACHED_REQUEST_BODY_OBJECT_KEY";

    // 设置到 exchange.getAttributes()中的key
    public final static String BG_DEBUG_KEY = "BG_DEBUG";

    // 请求参数,返回参数 加密
    public final static String REQ_RES_ENCRYPT = "0";

    // 请求参出,返回结果 无需加密
    public final static String REQ_RES_NALMORE = "1";
}

GlobalCacheRequestBodyFilter:把请求中的body复制到exchange,便于在之后使用。也可以理解为同一个线程中,多个filter之间共享的域


/**
 * @calssName AppCacheRequestBodyFilter
 * @Description 将 request body 中的内容 copy 一份,记录到 exchange 的一个自定义属性中
 * @Author hl
 * @DATE 2020/9/27 14:42
 */
public class GlobalCacheRequestBodyFilter implements GlobalFilter, Ordered {

    private static final Logger logger = LoggerFactory.getLogger(GlobalCacheRequestBodyFilter.class);

    private int order;

    @Override
    public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        logger.info("GlobalCacheRequestBodyFilter ...");

        // 将 request body 中的内容 copy 一份,记录到 exchange 的一个自定义属性中
        Object cachedRequestBodyObject = exchange.getAttributeOrDefault(ConstantFilter.CACHED_REQUEST_BODY_OBJECT_KEY, null);
        // 如果已经缓存过,略过
        if (cachedRequestBodyObject != null) {
            return chain.filter(exchange);
        }
        // 如果没有缓存过,获取字节数组存入 exchange 的自定义属性中
        return DataBufferUtils.join(exchange.getRequest().getBody())
                .map(dataBuffer -> {
                    byte[] bytes = new byte[dataBuffer.readableByteCount()];
                    dataBuffer.read(bytes);
                    DataBufferUtils.release(dataBuffer);
                    return bytes;
                }).defaultIfEmpty(new byte[0])
                .doOnNext(bytes -> exchange.getAttributes().put(ConstantFilter.CACHED_REQUEST_BODY_OBJECT_KEY, bytes))
                .then(chain.filter(exchange));
    }

    @Override
    public int getOrder() {
        return this.order;
    }

    public GlobalCacheRequestBodyFilter(int order){
        this.order = order;
    }
}

第二步:请求参数解密处理

  • gateway中我们并不能直接对请求参数进行修改,那么如何才能将加密的请求一解密后的方式路由到下游的服务中去呢?这里我们需要在filter中根据原来的请求对参数解密后创建新的请求。需要注意的是:解密后对请求头CONTENT_LENGTH需要重置,都在会出现读取的参数不完整的情况。
  • 如果需要对参数进行解密处理,所以对于客户端的请求统一使用POST的请求方式。
  • 加密解密的方法根据你的需要处理。(此处我使用的是RSA,AES结合的方式-之后会继续分享此种加密方式)
public class AppReqDecryptFilter implements GlobalFilter, Ordered {

    private static final Logger logger = LoggerFactory.getLogger(AppReqDecryptFilter.class);
	@Autowired
	private CheckService checkService;
    private int order;

    @Override
    public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    	 logger.info("请求参数系解密...");
        // 设置是否加密标识
        List bgDebugHeaders = exchange.getRequest().getHeaders().get("bg-debug");
        String bgDebug = bgDebugHeaders != null ? bgDebugHeaders.get(0) : ConstantFilter.REQ_RES_ENCRYPT;
        exchange.getAttributes().put(ConstantFilter.BG_DEBUG_KEY, bgDebug);

        // 获取请求的方法
        ServerHttpRequest oldRequest = exchange.getRequest();
        String method = oldRequest.getMethodValue();
        URI uri = oldRequest.getURI();

        if ("POST".equals(method)){
            // 尝试从 exchange 的自定义属性中取出缓存到的 body
            Object cachedRequestBodyObject = exchange.getAttributeOrDefault(ConstantFilter.CACHED_REQUEST_BODY_OBJECT_KEY, null);
            if (cachedRequestBodyObject != null) {
                byte[] decrypBytes;
                try {
                    byte[] body = (byte[]) cachedRequestBodyObject;
                    String rootData = new String(body); // 客户端传过来的数据
                    decrypBytes = body;

                    if(ConstantFilter.REQ_RES_ENCRYPT.equals(bgDebug)) {
                    	//获取密钥
                		ServerHttpRequest request = exchange.getRequest();
            	        HttpHeaders headers = request.getHeaders();
            	        String token = headers.getFirst(Params.AUTHORIZE_TOKEN);
            	        //此处获取自己配置的
            	        SecretIv secretIv = checkService.getKey(exchange, ResType.REQ, token);
            	        logger.info("获取密钥:{}", secretIv);
                		//报文处理解密
            	        JSONObject data = JSONObject.parseObject(new String(decrypBytes));
                		Map map = AESUtils.decryptAESRequest(data.getString(Params.AUTHORIZE_DATA), secretIv.getReqSecret(), secretIv.getReqIv());
                		//验签
                		checkService.checkSign(map, secretIv.getReqSecret(), data.getString(Params.AUTHORIZE_SIGN));
                		//下放报文
                		decrypBytes =  JSONHelper.map2json(map).getBytes();
                    }
                }catch (RuntimeException e){
                    logger.info("客户端数据解析异常:{}", e);
                    throw new RuntimeException(e.getMessage());
                }catch (Exception e){
                    logger.info("客户端数据解析异常:{}", e);
                    throw new RuntimeException("系统异常");
                }
               
                // 根据解密后的参数重新构建请求
                DataBufferFactory dataBufferFactory = exchange.getResponse().bufferFactory();
                Flux bodyFlux = Flux.just(dataBufferFactory.wrap(decrypBytes));
                ServerHttpRequest newRequest = oldRequest.mutate().uri(uri).build();
                newRequest = new ServerHttpRequestDecorator(newRequest) {
                    @Override
                    public Flux getBody() {
                        return bodyFlux;
                    }
                };

                // 构建新的请求头
                HttpHeaders headers = new HttpHeaders();
                headers.putAll(exchange.getRequest().getHeaders());
                // 由于修改了传递参数,需要重新设置CONTENT_LENGTH,长度是字节长度,不是字符串长度
                int length = decrypBytes.length;
                headers.remove(HttpHeaders.CONTENT_LENGTH);
                headers.setContentLength(length);
                // headers.set(HttpHeaders.CONTENT_TYPE, "application/json");
                newRequest = new ServerHttpRequestDecorator(newRequest) {
                    @Override
                    public HttpHeaders getHeaders() {
                        return headers;
                    }
                };

                // 把解密后的数据重置到exchange自定义属性中,在之后的日志GlobalLogFilter从此处获取请求参数打印日志
                exchange.getAttributes().put(ConstantFilter.CACHED_REQUEST_BODY_OBJECT_KEY, decrypBytes);
                return chain.filter(exchange.mutate().request(newRequest).build());
            }
        }else if("GET".equals(method)){ // todo 暂不处理
            Map requestQueryParams = oldRequest.getQueryParams();
            return chain.filter(exchange);
        }
        return chain.filter(exchange);
    }


    @Override
    public int getOrder() {
        return this.order;
    }


    public AppReqDecryptFilter(int order){
        this.order = order;
    }
    public AppReqDecryptFilter(){
    }

第三部:返回结果加密操作

public class AppRespEncryptFilter implements GlobalFilter, Ordered {

	 private static final Logger logger = LoggerFactory.getLogger(AppReqDecryptFilter.class);

	@Autowired
	private CheckService checkService;
	 
    private int order;
    
    @Autowired
	CommonKeys commonKeys;

    @Override
    public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    	logger.info("返回参数加密...");
        String bgDebug = exchange.getAttributeOrDefault(ConstantFilter.BG_DEBUG_KEY, ConstantFilter.REQ_RES_ENCRYPT);

        ServerHttpResponse originalResponse = exchange.getResponse();
        DataBufferFactory bufferFactory = originalResponse.bufferFactory();
        ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
            @Override
            public Mono writeWith(Publisher body) {
                if (body instanceof Flux) {
                    Flux fluxBody = (Flux) body;
                    return super.writeWith(fluxBody.buffer().map(dataBuffer -> {

                        DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
                        DataBuffer join = dataBufferFactory.join(dataBuffer);

                        byte[] content = new byte[join.readableByteCount()];
                        join.read(content);
                        //释放掉内存
                        DataBufferUtils.release(join);
                        // 正常返回的数据
                        String rootData = new String(content, Charset.forName("UTF-8"));
                        byte[] respData = rootData.getBytes();

                        if(ConstantFilter.REQ_RES_ENCRYPT.equals(bgDebug)){
                    		try {
                    			//登录成功的下发密钥
                    			ResultData res = checkService.getNewSecretIv(exchange, commonKeys, rootData);
                    			rootData = res.getData() + "";
                    	        ServerHttpRequest request = exchange.getRequest();
                    	        HttpHeaders headers = request.getHeaders();
                    	        String token = headers.getFirst(Params.AUTHORIZE_TOKEN);
                    	        SecretIv secretIv = checkService.getKey(exchange, ResType.RSP, token);
                    	      
                    			//报文加密
								respData = JSON.toJSONString(ResultData.result(CodeMsg.GATEWAY_SUCCESS,AESUtils.encryptAESRequest(rootData, secretIv.getRspSecret(), secretIv.getRspIv(), false))).getBytes();
							} catch (Exception e) {
								e.printStackTrace();
								respData = JSON.toJSONString(ResultData.result(CodeMsg.GATEWAY_ERROR)).getBytes();
							}
                        }
                        // 加密后的数据返回给客户端
                        byte[] uppedContent = new String(respData, Charset.forName("UTF-8")).getBytes();
                        return bufferFactory.wrap(uppedContent);
                    }));
                }
                return super.writeWith(body);
            }
        };
        return chain.filter(exchange.mutate().response(decoratedResponse).build());
    }

    @Override
    public int getOrder() {
        return this.order;
    }

    public AppRespEncryptFilter(int order){
        this.order = order;
    }
    
    public AppRespEncryptFilter(){
    }

 
  

第四步:在配置到指定的路由中去

如果所有的路由都需要,则自定义过滤器中可以直接实现全局过滤器,就无需配置到指定路由中去了,如:GlobalCacheRequestBodyFilter

@Configuration
public class GatewayConfig {

    /**
     * 全局过滤器:缓存请求body
     */
    @Bean
    public GlobalCacheRequestBodyFilter globalCacheRequestBodyFilter(){
        return new GlobalCacheRequestBodyFilter(-30);
    }

    @Bean
    public AppRespEncryptFilter globalAppRespEncryptFilter(){
        return new AppRespEncryptFilter(-22);
    }
    
    @Bean
    public AppReqDecryptFilter globalAppReqDecryptFilter(){
        return new AppReqDecryptFilter(-25);
    }
}

你可能感兴趣的:(爬坑记录,网关,微服务架构,过滤器)