美文网首页
springboot gateway 接口加密

springboot gateway 接口加密

作者: 咖啡机an | 来源:发表于2023-12-28 16:21 被阅读0次

网关加密/解密拦截器

使用aes加密,加密工具使用Hutool

  • 解密接口的url参数
  • 解密接口的body参数
  • 加密接口返回数据

1.yml配置

  #接口加密
config:
  crypto:
    #是否启动加密
    enabled: true
    #加密信息字段
    cryptoParam: cryptoParam
    #AES加密key
    appkey: 123456
logging:
  level:
    #日志级别 debug:输出加密过程 info:不输出
    com.ty.filter.CryptoFilter: info

2.配置类 工具类

@Data
@Configuration
@ConfigurationProperties(prefix = "config.crypto")
public class CryptoConfigProperties {
    /**
     * 加密字段,用哪个字段显示加密的数据
     */
    private String cryptoParam;

    /**
     * AES加密key
     */
    private String appkey;
}
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class CryptoVo {
    private String body;
    /**
     * 前端是否加密,加密则响应体也加密 1:url加密 2:body加密
     */
    private Integer encryptFlag;

    private MediaType contentType;
}
@Slf4j
public class CryptoUtil {

 private static final Base64.Decoder BASE64_DECODER = Base64.getDecoder();

    private static final Base64.Encoder BASE64_ENCODER = Base64.getEncoder();

    public static final int REQ_URL = 0;
    public static final int REQ_BODY = 1;
    public static final int RES_DATA = 2;

    /**
     * 解密
     * 6ase64 + aes
     *
     * @param data
     * @param key
     * @return
     */
    public static String decrypt(String data, String key) throws Exception {
        if (StringUtils.isEmpty(data) || StringUtils.isEmpty(key)) {
            return data;
        }
        String data64 = new String(BASE64_DECODER.decode(data), StandardCharsets.UTF_8);
        String result = AESForJsUtil.decryptAES(data64, key);
        if (StringUtils.isEmpty(result)) {
            throw new RuntimeException("解密失败," + data);
        }
        return result;
    }

    /**
     * 加密
     * aes + 6ase64
     *
     * @param data
     * @param key
     * @return
     */
    public static String encrypt(String data, String key) throws Exception {
        if (StringUtils.isEmpty(data) || StringUtils.isEmpty(key)) {
            return data;
        }
        String result = AESForJsUtil.encryptAES(data, key);
        result = new String(BASE64_ENCODER.encode(result.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
        if (StringUtils.isEmpty(result)) {
            throw new RuntimeException("加密失败," + data);
        }
        return result;
    }

    public static void main(String[] args) throws Exception {

    }

3.拦截器

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.reactivestreams.Publisher;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Configuration;
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.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.RequestPath;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.net.URI;
import java.nio.charset.StandardCharsets;

/**
 * 加密接口-解密请求拦截器
 * 注意:get请求,body不能传值
 * 1.解密接口的url参数
 * 2.解密接口的body参数
 * 3.加密接口返回数据
 *
 * @date 2023-12-28
 */
@Slf4j
@Configuration
@ConditionalOnProperty(value = "config.crypto.enabled", havingValue = "true", matchIfMissing = false)
public class CryptoFilter implements GlobalFilter, Ordered {

    /**
     * 加密配置属性
     */
    @Resource
    private CryptoConfigProperties cryptoConfigProperties;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        HttpHeaders headers = request.getHeaders();
        CryptoVo cryptoVo = new CryptoVo();
        cryptoVo.setEncryptFlag(0);
        //对url的参数进行解密
        updateUrlParam(exchange, cryptoVo);
        //不存在body则直接返回
        if (HttpMethod.GET.equals(method) || !MediaType.APPLICATION_JSON.isCompatibleWith(headers.getContentType())) {
            if (cryptoVo.getEncryptFlag() > 0) {
                //响应体加密
                exchange = exchange.mutate().response(serverHttpResponseDecorator(response, cryptoVo)).build();
            }
            return chain.filter(exchange);
        }
        //application/json对body的参数解密
        Flux<DataBuffer> body = request.getBody();
        Mono<DataBuffer> dataBufferMono = DataBufferUtils.join(body);
        ServerWebExchange finalExchange = exchange;
        return dataBufferMono.flatMap(dataBuffer -> {
            try {
                // 获取请求参数
                String originalRequestBody = getOriginalRequestBody(dataBuffer);
                cryptoVo.setBody(originalRequestBody);
                // 解密请求参数
                String decryptRequestBody = decryptRequest(request, cryptoVo);
                // 重写请求和响应方式
                return getEnResponseBody(finalExchange, chain, request, decryptRequestBody, response, cryptoVo);
            } catch (Exception e) {
                log.error("密文过滤器加解密错误", e);
                return Mono.error(e);
            } finally {
                DataBufferUtils.release(dataBuffer);
            }
        });
    }

    /**
     * 返回加密响应体
     *
     * @param exchange
     * @param chain
     * @param request
     * @param decryptRequestBody
     * @param response
     * @param cryptoVo
     * @return
     */
    private Mono<Void> getEnResponseBody(ServerWebExchange exchange, GatewayFilterChain chain, ServerHttpRequest request, String decryptRequestBody, ServerHttpResponse response, CryptoVo cryptoVo) {
        // 新的请求体
        ServerHttpRequestDecorator requestDecorator = serverHttpRequestDecorator(request, decryptRequestBody);
        //加密响应体
        ServerHttpResponseDecorator serverHttpResponseDecorator = serverHttpResponseDecorator(response, cryptoVo);
        ServerWebExchange serverWebExchange = exchange.mutate().request(requestDecorator).response(serverHttpResponseDecorator).build();
        // 使用新的请求和响应转发
        return chain.filter(serverWebExchange);
    }

    /**
     * body解密
     *
     * @param request
     * @param cryptoVo
     * @return
     */
    private String decryptRequest(ServerHttpRequest request, CryptoVo cryptoVo) {
        String body = cryptoVo.getBody();
        if (!JSON.isValid(body)) {
            return body;
        }
        JSONObject jsonObject = JSON.parseObject(body);
        String cryptoParam = jsonObject.getString(cryptoConfigProperties.getCryptoParam());
        if (!StringUtils.isEmpty(cryptoParam)) {
            cryptoVo.setEncryptFlag(2);
            try {
                body = CryptoUtil.decrypt(cryptoParam, cryptoConfigProperties.getAppkey());
                cryptorDetail(request.getPath(), cryptoParam, body, CryptoUtil.REQ_BODY);
            } catch (Exception e) {
                log.error("【body解密失败】路径:{},参数:{}", request.getPath(), body, e);
                throw new RuntimeException(e.getMessage(), e);
            }
        }
        return body;
    }

    /**
     * 新建请求体
     *
     * @param originalRequest
     * @param decryptRequestBody
     * @return
     */
    private ServerHttpRequestDecorator serverHttpRequestDecorator(ServerHttpRequest originalRequest, String decryptRequestBody) {
        return new ServerHttpRequestDecorator(originalRequest) {
            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.putAll(super.getHeaders());
                httpHeaders.remove(HttpHeaders.CONTENT_LENGTH);
                httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
                return httpHeaders;
            }

            @Override
            public Flux<DataBuffer> getBody() {
   if (decryptRequestBody == null) {
                    return super.getBody();
                }
                byte[] bytes = decryptRequestBody.getBytes(StandardCharsets.UTF_8);
                return Flux.just(new DefaultDataBufferFactory().wrap(bytes));
            }
        };
    }


    /**
     * 新建响应体
     *
     * @param originalResponse
     * @param cryptoVo
     * @return
     */
    private ServerHttpResponseDecorator serverHttpResponseDecorator(ServerHttpResponse originalResponse, CryptoVo cryptoVo) {
        DataBufferFactory dataBufferFactory = originalResponse.bufferFactory();
        return new ServerHttpResponseDecorator(originalResponse) {
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                //encryptFlag 前端加密,响应体也加密
                if (body instanceof Flux && cryptoVo.getEncryptFlag() > 0) {
                    Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
                    return super.writeWith(fluxBody.buffer().handle((dataBuffers, sink) -> {
                        DataBuffer join = dataBufferFactory.join(dataBuffers);
                        byte[] byteArray = new byte[join.readableByteCount()];
                        join.read(byteArray);
                        DataBufferUtils.release(join);
                        String originalResponseBody = new String(byteArray, StandardCharsets.UTF_8);
                        byte[] encryptedByteArray;
                        try {
                            String encryptResult = CryptoUtil.encrypt(originalResponseBody, cryptoConfigProperties.getAppkey());
                            //将响应体加密后 包装成json格式
                            JSONObject jsonObject = new JSONObject();
                            jsonObject.put(cryptoConfigProperties.getCryptoParam(), encryptResult);
                            encryptedByteArray = jsonObject.toJSONString().getBytes(StandardCharsets.UTF_8);
                            cryptorDetail(null, originalResponseBody, encryptResult, CryptoUtil.RES_DATA);
                        } catch (Exception e) {
                            log.error("加密失败,{}", originalResponseBody, e);
                            sink.error(new RuntimeException(e.getMessage(), e));
                            return;
                        }
                        originalResponse.getHeaders().setContentLength(encryptedByteArray.length);
                        originalResponse.getHeaders().setContentType(MediaType.APPLICATION_JSON);
                        sink.next(dataBufferFactory.wrap(encryptedByteArray));
                    }));
                }
                return super.writeWith(body);
            }

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

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                headers.putAll(originalResponse.getHeaders());
                return headers;
            }
        };
    }

    /**
     * 获取原始的请求参数
     *
     * @param dataBuffer 数据缓冲
     * @return 原始的请求参数
     */
    private String getOriginalRequestBody(DataBuffer dataBuffer) {
        byte[] bytes = new byte[dataBuffer.readableByteCount()];
        dataBuffer.read(bytes);
        return new String(bytes, StandardCharsets.UTF_8);
    }

    /**
     * 对url的参数进行解密
     */
    private void updateUrlParam(ServerWebExchange exchange, CryptoVo encryptFlag) {
        ServerHttpRequest request = exchange.getRequest();
        URI uri = request.getURI();
        String query = uri.getQuery();
        String param = "";
        if (!StringUtils.isEmpty(query) && query.contains(cryptoConfigProperties.getCryptoParam())) {
            encryptFlag.setEncryptFlag(1);
            try {
                String[] split = query.split("=");
                String dataSec = split[1];
                param = CryptoUtil.decrypt(dataSec, cryptoConfigProperties.getAppkey());
                Field targetQuery = uri.getClass().getDeclaredField("query");
                targetQuery.setAccessible(true);
                targetQuery.set(uri, param);
                cryptorDetail(request.getPath(), dataSec, param, CryptoUtil.REQ_URL);
            } catch (Exception e) {
                log.error("【url解密失败】路径:{},参数:{}", request.getPath(), param);
                throw new RuntimeException(e.getMessage(), e);
            }
        }
    }

    /**
     * 输出详细日志
     *
     * @param path
     * @param before
     * @param after
     * @param type   0:请求url 1请求body 2 响应
     */
    private void cryptorDetail(RequestPath path, String before, String after, Integer type) {
        if (!log.isDebugEnabled()) {
            return;
        }
        switch (type) {
            case CryptoUtil.REQ_URL:
                log.info("【请求url解密】路径:{},解密前:{},解密后:{}", path, before, after);
                break;
            case CryptoUtil.REQ_BODY:
                log.info("【请求body解密】路径:{},解密前:{},解密后:{}", path, before, after);
                break;
            case CryptoUtil.RES_DATA:
                log.info("【响应加密】加密前:{},加密后:{}", before, after);
                break;
            default:
                break;
        }
    }


    @Override
    public int getOrder() {
        return HIGHEST_PRECEDENCE;
    }
}

相关文章

网友评论

      本文标题:springboot gateway 接口加密

      本文链接:https://www.haomeiwen.com/subject/odkrndtx.html