springboot gateway 接口加密

網(wǎng)關(guān)加密/解密攔截器

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

  • 解密接口的url參數(shù)
  • 解密接口的body參數(shù)
  • 加密接口返回?cái)?shù)據(jù)

1.yml配置

  #接口加密
config:
  crypto:
    #是否啟動(dòng)加密
    enabled: true
    #加密信息字段
    cryptoParam: cryptoParam
    #AES加密key
    appkey: 123456
logging:
  level:
    #日志級(jí)別 debug:輸出加密過程 info:不輸出
    com.ty.filter.CryptoFilter: info

2.配置類 工具類

@Data
@Configuration
@ConfigurationProperties(prefix = "config.crypto")
public class CryptoConfigProperties {
    /**
     * 加密字段盾饮,用哪個(gè)字段顯示加密的數(shù)據(jù)
     */
    private String cryptoParam;

    /**
     * AES加密key
     */
    private String appkey;
}
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class CryptoVo {
    private String body;
    /**
     * 前端是否加密硝清,加密則響應(yīng)體也加密 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參數(shù)
 * 2.解密接口的body參數(shù)
 * 3.加密接口返回?cái)?shù)據(jù)
 *
 * @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);
        //對(duì)url的參數(shù)進(jìn)行解密
        updateUrlParam(exchange, cryptoVo);
        //不存在body則直接返回
        if (HttpMethod.GET.equals(method) || !MediaType.APPLICATION_JSON.isCompatibleWith(headers.getContentType())) {
            if (cryptoVo.getEncryptFlag() > 0) {
                //響應(yīng)體加密
                exchange = exchange.mutate().response(serverHttpResponseDecorator(response, cryptoVo)).build();
            }
            return chain.filter(exchange);
        }
        //application/json對(duì)body的參數(shù)解密
        Flux<DataBuffer> body = request.getBody();
        Mono<DataBuffer> dataBufferMono = DataBufferUtils.join(body);
        ServerWebExchange finalExchange = exchange;
        return dataBufferMono.flatMap(dataBuffer -> {
            try {
                // 獲取請求參數(shù)
                String originalRequestBody = getOriginalRequestBody(dataBuffer);
                cryptoVo.setBody(originalRequestBody);
                // 解密請求參數(shù)
                String decryptRequestBody = decryptRequest(request, cryptoVo);
                // 重寫請求和響應(yīng)方式
                return getEnResponseBody(finalExchange, chain, request, decryptRequestBody, response, cryptoVo);
            } catch (Exception e) {
                log.error("密文過濾器加解密錯(cuò)誤", e);
                return Mono.error(e);
            } finally {
                DataBufferUtils.release(dataBuffer);
            }
        });
    }

    /**
     * 返回加密響應(yīng)體
     *
     * @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);
        //加密響應(yīng)體
        ServerHttpResponseDecorator serverHttpResponseDecorator = serverHttpResponseDecorator(response, cryptoVo);
        ServerWebExchange serverWebExchange = exchange.mutate().request(requestDecorator).response(serverHttpResponseDecorator).build();
        // 使用新的請求和響應(yīng)轉(zhuǎn)發(fā)
        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解密失敗】路徑:{},參數(shù):{}", 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));
            }
        };
    }


    /**
     * 新建響應(yīng)體
     *
     * @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 前端加密彼棍,響應(yīng)體也加密
                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());
                            //將響應(yīng)體加密后 包裝成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;
            }
        };
    }

    /**
     * 獲取原始的請求參數(shù)
     *
     * @param dataBuffer 數(shù)據(jù)緩沖
     * @return 原始的請求參數(shù)
     */
    private String getOriginalRequestBody(DataBuffer dataBuffer) {
        byte[] bytes = new byte[dataBuffer.readableByteCount()];
        dataBuffer.read(bytes);
        return new String(bytes, StandardCharsets.UTF_8);
    }

    /**
     * 對(duì)url的參數(shù)進(jìn)行解密
     */
    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解密失敗】路徑:{},參數(shù):{}", request.getPath(), param);
                throw new RuntimeException(e.getMessage(), e);
            }
        }
    }

    /**
     * 輸出詳細(xì)日志
     *
     * @param path
     * @param before
     * @param after
     * @param type   0:請求url 1請求body 2 響應(yīng)
     */
    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("【響應(yīng)加密】加密前:{},加密后:{}", before, after);
                break;
            default:
                break;
        }
    }


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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子子寓,更是在濱河造成了極大的恐慌,老刑警劉巖辜羊,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件踏兜,死亡現(xiàn)場離奇詭異,居然都是意外死亡八秃,警方通過查閱死者的電腦和手機(jī)碱妆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來昔驱,“玉大人疹尾,你說我怎么就攤上這事≈韪兀” “怎么了纳本?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長腋颠。 經(jīng)常有香客問我繁成,道長,這世上最難降的妖魔是什么淑玫? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任巾腕,我火速辦了婚禮面睛,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘尊搬。我一直安慰自己叁鉴,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布佛寿。 她就那樣靜靜地躺著幌墓,像睡著了一般。 火紅的嫁衣襯著肌膚如雪狗准。 梳的紋絲不亂的頭發(fā)上克锣,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音腔长,去河邊找鬼袭祟。 笑死,一個(gè)胖子當(dāng)著我的面吹牛捞附,可吹牛的內(nèi)容都是我干的巾乳。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼鸟召,長吁一口氣:“原來是場噩夢啊……” “哼胆绊!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起欧募,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤压状,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后跟继,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體种冬,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年舔糖,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了娱两。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡金吗,死狀恐怖十兢,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情摇庙,我是刑警寧澤旱物,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站卫袒,受9級(jí)特大地震影響异袄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜玛臂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一烤蜕、第九天 我趴在偏房一處隱蔽的房頂上張望封孙。 院中可真熱鬧,春花似錦讽营、人聲如沸虎忌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽膜蠢。三九已至,卻和暖如春莉兰,著一層夾襖步出監(jiān)牢的瞬間挑围,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工糖荒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留杉辙,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓捶朵,卻偏偏與公主長得像蜘矢,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子综看,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345