ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

spring cloud gateway 网关路由转发及request、response的加解密处理

2021-09-23 13:30:06  阅读:620  来源: 互联网

标签:body 网关 return exchange spring 加解密 class new public


sygateway的介绍和使用原理请自行查阅和学习,本章内容需在对spring cloud gateway的基本的了解和一定的知识基础上进行的。附上一个可供学习的博文:SpringCloud gateway (史上最全) - 疯狂创客圈 - 博客园

需求场景

前端对post请求的数据进行加密(主流的加密方式),后端需要解密,将接口的响应数据进行加密返回,前端解密并展示或其他操作等。出于安全性的考虑和可扩展性故使用了网关来做。

涉及到的问题

1、request的body数据流读取只能读取一次,controller层获取不到抛异常问题等

2、加解密采用对称加密算法是否前后端通用包括:ios、android、js

设计思路:(尽量使网关通用切可配置减少网关的代码修改和上线)

1、框架为springcloud+nacos+gateway+feign 后期可以加入hystrix等

2、在网关层进行对token的验证

3、在网关层进行统一解密request数据,并将header中添加特定数据,业务系统拦截器中进行对header的数据进行识别处理

4、业务系统返回数据后在网管层中对response数据进行加密返回,前端进行解密

直接上代码:(对token的验证)

启动类很普通,注册发现nacos相关注解

@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}
server:
  port: 8080 #网关端口号

spring:
  application:
    name: k_men-gateway
    active: dev
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #本地nacos需要启动默认8848端口
      config:
        server-addr: localhost:8848 #本地nacos需要启动默认8848端口
        file-extension: yaml
        group: DEFAULT_GROUP
    gateway:
      discovery:
        locator:
          enabled: true #开启动态路由的功能

对token验证的自定义filter(代码中涉及到自定义的返回体或者返回错误码之类的自行替换)

@Slf4j
public class AuthGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthGatewayFilterFactory.Config> {

    @Autowired
    private RedisUtils redisUtils;

    public AuthGatewayFilterFactory() {
        super(Config.class);
    }

    @Autowired
    private ModifyResponseBodyGatewayFilterFactory modifyResponseBodyGatewayFilterFactory;

    @Autowired
    private ObjectMapper objectMapper;

    /**
     * 具体的过滤逻辑
     * 1.如果没有登录令牌token,直接返回没有授权的信息
     * 2.如果有登录令牌token
     * 2.1通过令牌校验,继续向下执行
     * 2.2没有通过校验,返回没有通过校验的原因
     *
     * @param config 自定义配置类
     * @return 网关过滤器
     */
    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            String token = exchange.getRequest().getHeaders().getFirst("Authorization");
            ServerHttpResponse resp = exchange.getResponse();
            resp.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
            try {
                if (!exchange.getRequest().getPath().toString().contains("admin/login")){
                    if (StrUtil.isBlank(token) || JwtUtils.getAppTokenKey(token) == null || !token.equals(redisUtils.get(JwtUtils.getAppTokenKey(token)))) {
                        String result = JSONUtil.parseObj(R.fail(ErrorCodeEnum.TOKEN_ERROR.getCode(), "token error")).toStringPretty();
                        DataBuffer buffer = resp.bufferFactory().wrap(result.getBytes(StandardCharsets.UTF_8));
                        return resp.writeWith(Flux.just(buffer));
                    }
                }
                return chain.filter(exchange.mutate().build());
            } catch (Exception e) {
                log.error(e.getMessage(), e);
                String result = JSONUtil.parseObj(R.fail(ErrorCodeEnum.TOKEN_ERROR.getCode(), e.getMessage())).toStringPretty();
                DataBuffer buffer = resp.bufferFactory().wrap(result.getBytes(StandardCharsets.UTF_8));
                return resp.writeWith(Flux.just(buffer));
            }
        };
    }

    /**
     * 自定义配置类
     */
    public static class Config {
    }

}
@Configuration
public class GatewayConfig {

    /**
     * 注入自定义授权网关过滤器工厂
     */
    @Bean
    public AuthGatewayFilterFactory authGatewayFilterFactory() {
        return new AuthGatewayFilterFactory();
    }
}
spring:
  cloud:
    gateway:
      routes:
      - id: k_men_cp_routh_auth
        uri: lb://k_men-cp-api-test-local
        predicates:
        - Path=/**
        filters:
        - Auth

实现方式:(对header添加数据上面链接中有,下面重点讲对request和response的处理基于上述部分代码)

一、使用java代码进行配置

优点:简单,容易理解,代码量少

缺点:编码频繁,不能配置于yaml文件进行热更新,新增服务需要进行编码配置,缺少灵活性

废话不多说直接上代码:

@Slf4j
public class RequestBodyRewrite implements RewriteFunction<String, String> {

    public RequestBodyRewrite() {
    }

    @Override
    public Publisher<String> apply(ServerWebExchange exchange, String body) {
        try {
            String data = DESedeUtils.decrypt(body);//加密代码处理
            return Mono.just(data);
        } catch (Exception ex) {
            log.error("1. json process fail", ex);
            return Mono.error(new Exception("1. json process fail", ex));
        }
    }
}
@Slf4j
public class ResponseBodyRewrite implements RewriteFunction<String, String> {
    public ResponseBodyRewrite() {
    }

    @Override
    public Publisher<String> apply(ServerWebExchange exchange, String body) {
        try {
            String data = DESedeUtils.encrypt(body);//加密代码处理
            return Mono.just(data);
        } catch (Exception ex) {
            log.error("2. json process fail", ex);
            return Mono.error(new Exception("2. json process fail", ex));
        }
    }
}
    @Bean
    public RouteLocator routes(RouteLocatorBuilder builder, ObjectMapper objectMapper) {
        return builder
                .routes()
                .route("path_route_change",
                        r -> r.path("/**")
                                .filters(f -> f
                                        .modifyRequestBody(String.class,String.class,new RequestBodyRewrite())
                                        .modifyResponseBody(String.class, String.class, new ResponseBodyRewrite())
                                ).uri("lb://k_men-cp-api-test")).build();
    }

二、自定义filter及配置文件方式

优点:支持yaml配置,配置文件热更新生效,灵活通用,可扩展性好,减少网关的频繁上线

缺点:代码稍微难以理解,代码多等

废话不多说直接上代码:

启动类上加动态刷新@RefreshScope 注解

配置文件放在nacos上使用配合上面的配置文件中的名字 k_men-gateway-dev.yaml

spring:
  cloud:
    gateway:
      routes:
      - id: lc_cp_routh
        uri: lb://k_men-cp-api-test-local #需要转发到的服务在nacos上的name
        predicates:
        - Path=/**
        filters:
        - DecryptRequestBody
        - EncryptResponseBody

对request的body的修改解密处理 

@Slf4j
public class DecryptRequestBodyGatewayFilterFactory extends AbstractGatewayFilterFactory implements Ordered {

    private final List<HttpMessageReader<?>> messageReaders;

    public DecryptRequestBodyGatewayFilterFactory() {
        this.messageReaders = HandlerStrategies.withDefaults().messageReaders();
    }

    @Override
    @SuppressWarnings("unchecked")
    public GatewayFilter apply(Object config) {
        return (exchange, chain) -> {
            ServerRequest serverRequest = ServerRequest.create(exchange,
                    this.messageReaders);

            Mono<String> modifiedBody = serverRequest.bodyToMono(String.class)
                    .flatMap(originalBody -> modifyBody().apply(exchange,Mono.just(originalBody)));

            BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody,String.class);
            HttpHeaders headers = new HttpHeaders();
            headers.putAll(exchange.getRequest().getHeaders());
            headers.remove(HttpHeaders.CONTENT_LENGTH);
            CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange,headers);

            return bodyInserter.insert(outputMessage, new BodyInserterContext())
                    .then(Mono.defer(() -> {
                        ServerHttpRequest decorator = decorate(exchange, headers,outputMessage);
                        return chain.filter(exchange.mutate().request(decorator).build());
                    }));
        };
    }

    private BiFunction<ServerWebExchange, Mono<String>, Mono<String>> modifyBody() {
        return (exchange, body) -> {
            try {
                AtomicReference<String> result = new AtomicReference<>();
                body.subscribe(value -> result.set(EncryptUtils.decryptStr(value)),
                        e -> log.error(e.getMessage(), e)
                );
                return Mono.just(result.get());
            } catch (Exception e) {
                log.error("gateway parameter decryption exception", e);
                throw new DecryptParamException("Parameter decryption exception");
            }
        };
    }

    private ServerHttpRequestDecorator decorate(ServerWebExchange exchange, HttpHeaders headers,
                                                CachedBodyOutputMessage outputMessage) {
        return new ServerHttpRequestDecorator(exchange.getRequest()) {
            @Override
            public HttpHeaders getHeaders() {
                long contentLength = headers.getContentLength();
                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.putAll(super.getHeaders());
                if (contentLength > 0) {
                    httpHeaders.setContentLength(contentLength);
                }
                else {
                    httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
                }
                return httpHeaders;
            }

            @Override
            public Flux<DataBuffer> getBody() {
                return outputMessage.getBody();
            }
        };

    }

    @Override
    public int getOrder() {
        return -2;
    }
}

对response的body的修改加密处理

@Slf4j
public class EncryptResponseBodyGatewayFilterFactory extends AbstractGatewayFilterFactory{


    public EncryptResponseBodyGatewayFilterFactory() {
    }

    @Override
    public GatewayFilter apply(Object config) {
        return new EncryptResponseGatewayFilter();
    }

    public class EncryptResponseGatewayFilter implements GatewayFilter,Ordered{
        @Override
        public int getOrder() {
            return -2;
        }

        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            return chain.filter(exchange.mutate().response(decorate(exchange)).build());
        }

        @SuppressWarnings("unchecked")
        private ServerHttpResponse decorate(ServerWebExchange exchange) {
            return new ServerHttpResponseDecorator(exchange.getResponse()) {

                @Override
                public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {

                    String originalResponseContentType = exchange
                            .getAttribute(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
                    HttpHeaders httpHeaders = new HttpHeaders();
                    httpHeaders.add(HttpHeaders.CONTENT_TYPE,
                            originalResponseContentType);

                    ClientResponse clientResponse = ClientResponse
                            .create(exchange.getResponse().getStatusCode())
                            .headers(headers -> headers.putAll(httpHeaders))
                            .body(Flux.from(body)).build();

                    //修改body
                    Mono<String> modifiedBody = clientResponse.bodyToMono(String.class)
                            .flatMap(originalBody -> modifyBody()
                                    .apply(exchange,Mono.just(originalBody)));

                    BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody,
                            String.class);
                    CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(
                            exchange, exchange.getResponse().getHeaders());
                    return bodyInserter.insert(outputMessage, new BodyInserterContext())
                            .then(Mono.defer(() -> {
                                Flux<DataBuffer> messageBody = outputMessage.getBody();
                                HttpHeaders headers = getDelegate().getHeaders();
                                if (!headers.containsKey(HttpHeaders.TRANSFER_ENCODING)) {
                                    messageBody = messageBody.doOnNext(data -> headers
                                            .setContentLength(data.readableByteCount()));
                                }
                                return getDelegate().writeWith(messageBody);
                            }));
                }

                /**
                 * 修改body
                 * @return apply 返回Mono<String>,数据是修改后的body
                 */
                private BiFunction<ServerWebExchange,Mono<String>,Mono<String>> modifyBody(){
                    return (exchange,json)-> {
                        AtomicReference<String> result = new AtomicReference<>();
                        json.subscribe(
                                value -> result.set(EncryptUtils.encryptHex(value)),
                                Throwable::printStackTrace
                        );
                        return Mono.just(result.get());
                    };
                }

                @Override
                public Mono<Void> writeAndFlushWith(
                        Publisher<? extends Publisher<? extends DataBuffer>> body) {
                    return writeWith(Flux.from(body).flatMapSequential(p -> p));
                }
            };
        }
    }
}
@Configuration
public class GatewayConfig {

    /**
     * 注入自定义授权网关过滤器工厂
     */
    @Bean
    public AuthGatewayFilterFactory authGatewayFilterFactory() {
        return new AuthGatewayFilterFactory();
    }

    /**
     * request body解密
     */
    @Bean
    public DecryptRequestBodyGatewayFilterFactory decryptRequestBodyGatewayFilterFactory() {
        return new DecryptRequestBodyGatewayFilterFactory();
    }

    /**
     * response body加密
     */
    @Bean
    public EncryptResponseBodyGatewayFilterFactory encryptResponseBodyGatewayFilterFactory() {
        return new EncryptResponseBodyGatewayFilterFactory();
    }

}

标签:body,网关,return,exchange,spring,加解密,class,new,public
来源: https://blog.csdn.net/K_Men/article/details/120429241

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有