liu5645849 2025-05-10 13:07 采纳率: 0%
浏览 23

springCloud-gateway过滤器加解密问题

用springcloud-gateway写了一个过滤器,用于解密前端请求的加密参数,但是如果请求post或put请求后自动加载列表接口就会报错400 Bad Request,后端报错实在列表接口对应的模块;请求get或delete请求后自动加载列表接口正常;但是如果是F5刷新页面之后第一次请求put或post之后的自动加载能成功!被这个问题问题困扰几天了还没解决!!!

以下是过滤器代码

package com.example.gateway.filter;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.ObjectUtil;
import com.example.gateway.filter.decorator.CachedBodyServerHttpRequestDecorator;
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.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.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;

import com.example.gateway.filter.decorator.ResponseEncryptDecorator;
import com.example.gateway.filter.utils.CryptoUtils;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;

@Slf4j
@Component
public class CustomCryptoFilter implements GlobalFilter, Ordered {

    /**
     * 自定义加密过滤器的处理方法
     * 此方法用于处理进入的请求,检查是否需要解密请求体,并在需要时加密响应
     * 它首先检查请求头以确定是否需要解密请求体如果不需要解密,则直接继续执行链中的下一个过滤器
     * 如果需要解密,它会解密请求体,设置响应头以指示响应将被加密,然后继续执行链中的下一个过滤器
     * 此外,它还尝试解密请求参数中的'params',并在成功解密后重建URI
     *
     * @param exchange 服务器网页交换对象,包含请求和响应
     * @param chain 网关过滤器链,用于执行下一个过滤器
     * @return 返回一个Mono<Void>,表示异步处理完成
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        // 检查是否启用加密通信
        HttpHeaders headers = request.getHeaders();
        boolean shouldDecrypt = ObjectUtil.isNotEmpty(headers.get("X-Request-Encrypt")) &&
                "AES".equals(headers.getFirst("X-Request-Encrypt"));

        if (!shouldDecrypt) {
            return chain.filter(exchange);
        }
        // 使用 ResponseEncryptDecorator 装饰响应对象
//        ServerHttpResponse decoratedResponse = new ResponseEncryptDecorator(response);

        String method = request.getMethodValue();

        // 是否是需要解密 body 的方法(POST/PUT)
        boolean isBodyMethod = "POST".equalsIgnoreCase(method) || "PUT".equalsIgnoreCase(method);

        // 异步处理 body 解密(仅限 POST/PUT)
        Mono<ServerHttpRequest> decoratedRequestMono;
        if (isBodyMethod) {
            decoratedRequestMono = DataBufferUtils.join(request.getBody())
                    .flatMap(dataBuffer -> {
                        byte[] bytes = new byte[dataBuffer.readableByteCount()];
                        dataBuffer.read(bytes);
                        DataBufferUtils.release(dataBuffer); // ✅ 释放 buffer 资源
                        String body = new String(bytes, StandardCharsets.UTF_8);

                        if (ObjectUtil.isNotEmpty(body)) {
                            try {
                                body = URLDecoder.decode(body, StandardCharsets.UTF_8.name());

                                String decryptedBody = CryptoUtils.decrypt(body);
                                decryptedBody = URLDecoder.decode(decryptedBody, StandardCharsets.UTF_8.name());

                                return Mono.just(new CachedBodyServerHttpRequestDecorator(
                                        exchange.getRequest(), decryptedBody.getBytes(StandardCharsets.UTF_8)));
                            } catch (Exception e) {
                                log.error("Failed to decrypt request body", e);
                                return Mono.just(exchange.getRequest()); // ⚠️ 解密失败仍继续执行
                            }
                        } else {
                            return Mono.just(exchange.getRequest());
                        }
                    })
                    .onErrorResume(ex -> {
                        log.warn("Error reading or decrypting body, using original request", ex);
                        return Mono.just(exchange.getRequest());
                    });
        } else {
            // 非 body 方法直接返回原始请求
            decoratedRequestMono = Mono.just(exchange.getRequest());

            // 对于 GET/DELETE 请求,强制清空 body
//            decoratedRequestMono = Mono.just(new EmptyBodyRequestDecorator(exchange.getRequest()));
        }

        // 不管什么方法,只要存在 params 就尝试解密
        Mono<URI> newUriMono = Mono.fromSupplier(() -> {
            MultiValueMap<String, String> valueMap = request.getQueryParams();
            if (ObjectUtil.isEmpty(valueMap)) {
                return UriComponentsBuilder.fromUri(request.getURI()).build().toUri();
            }

            String encrypted = valueMap.getFirst("params");
            if (ObjectUtil.isEmpty(encrypted)) {
                return UriComponentsBuilder.fromUri(request.getURI()).build().toUri();
            }

            try {
                encrypted = URLDecoder.decode(encrypted, StandardCharsets.UTF_8.name());
                encrypted = Base64.decodeStr(encrypted);
                String decrypted = CryptoUtils.decrypt(encrypted);
                decrypted = URLDecoder.decode(decrypted, StandardCharsets.UTF_8.name());

                return UriComponentsBuilder.fromUri(request.getURI())
                        .replaceQuery(rebuildUri(decrypted).build().getQuery())
                        .build()
                        .toUri();
            } catch (Exception e) {
                log.warn("Failed to decrypt query params", e);
                return UriComponentsBuilder.fromUri(request.getURI()).build().toUri();
            }
        });

        return Mono.zip(
                        decoratedRequestMono.defaultIfEmpty(exchange.getRequest()),
                        newUriMono.defaultIfEmpty(UriComponentsBuilder.fromUri(request.getURI()).build().toUri())
                )
                .map(tuple -> {
                    ServerHttpRequest decoratedRequest = tuple.getT1();
                    URI newUri = tuple.getT2();
                    ServerWebExchange ec = 
                    exchange.mutate()
                            .request(newUri != null ? decoratedRequest.mutate().uri(newUri).build() : decoratedRequest)
//                            .response(decoratedResponse)
                            .build();
                            ServerHttpRequest re = ec.getRequest();
                            HttpHeaders hds = re.getHeaders();
                            log.info("---------------请求头-------------: {}", hds);
                            log.info("---------------请求uri-------------: {}", re.getURI());
                    return ec;
                })
                .flatMap(mutatedExchange -> chain.filter(mutatedExchange));
    }
 


    /**
     * 构建新的 URI 查询参数
     */
    private UriComponentsBuilder rebuildUri(String decrypted) {
        UriComponentsBuilder queryBuilder = UriComponentsBuilder.newInstance();
        for (String param : decrypted.split("&")) {
            if (param.contains("=")) {
                String[] kv = param.split("=", 2);
                String key = kv[0];
                String value = kv.length > 1 ? kv[1] : "";
                if (!value.isEmpty()) {
                    queryBuilder.queryParam(key, value);
                }
            }
        }
        return queryBuilder;
    }

    @Override
    public int getOrder() {
        return -10; // 确保在大部分 Filter 之前执行
    }
}
  • 写回答

3条回答 默认 最新

  • 阿里嘎多学长 2025-05-10 13:07
    关注

    阿里嘎多学长整理AIGC生成,因移动端显示问题导致当前答案未能完全显示,请使用PC端查看更加详细的解答过程

    问题解答

    你使用 Spring Cloud Gateway 写了一个过滤器,用于解密前端请求的加密参数,但是遇到了问题:当请求是 POST 或 PUT 请求后,自动加载列表接口就会报错 400 Bad Request,后端报错是在列表接口对应的。

    可能的原因是:Spring Cloud Gateway 在处理请求时,会将请求体解析成一个 HttpEntity 对象,然后将其传递给下一个过滤器或目标服务。如果你的过滤器在解密请求参数时,导致请求体的内容被修改,那么可能会导致后端接口无法正确处理请求。

    解决方案:

    1. 在过滤器中,使用 GatewayFilterChain 对象的 filter 方法来处理请求,而不是直接修改请求体。
    2. 在过滤器中,使用 WebUtils 工具类来获取请求体的原始内容,然后在解密参数时使用原始内容。
    3. 如果你需要在解密参数时修改请求体的内容,可以使用 GatewayFilterChain 对象的 filter 方法来重新构建请求体。

    例如:

    @Component
    public class DecryptFilter implements GatewayFilter {
    
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            // 获取请求体的原始内容
            HttpEntity<String> entity = WebUtils.getRequestBody(exchange);
            // 解密参数
            String decryptedBody = decrypt(entity.getBody());
            // 重新构建请求体
            HttpEntity<String> newEntity = new HttpEntity<>(decryptedBody);
            // 重新构建请求
            ServerWebExchange newExchange = exchange.mutate().request(newRequest(newEntity)).build();
            // 将请求传递给下一个过滤器或目标服务
            return chain.filter(newExchange);
        }
    }
    

    希望这个解决方案能够帮助你解决问题!

    评论

报告相同问题?

问题事件

  • 创建了问题 5月10日