SpringCloud?Gateway實現(xiàn)請求解密和響應(yīng)加密的過程解析
前言
本文環(huán)境使用比較新的 Java 17 和 SpringBoot 3.1.5,對應(yīng)到Spring的版本是 6.0.13
使用到的三方插件有:
- lombok
- gson
- hutool
本文注重實現(xiàn)請求的解密和響應(yīng)的加密,加解密使用的是 Hutool 中的工具類,加解密算法目前提供了AES的方式,其余方式也可兼容擴展。
完整代碼倉庫:https://gitee.com/fengsoshuai/springcloud-gateway-feng-demo
借用網(wǎng)關(guān)中的過濾器GlobalFilter
來實現(xiàn)這一功能。
本文只粘貼一些重點文件內(nèi)容。
正文
一、項目簡介
在聚合項目中,有兩個核心模塊,feng-server提供了 rest 接口,供網(wǎng)關(guān)使用。
feng-gateway 是核心實現(xiàn)的網(wǎng)關(guān)項目,實現(xiàn)了自定義過濾器,以及增加了一些基本配置功能。本文重心是網(wǎng)關(guān)項目。
二、核心代碼
2.1 自定義過濾器
package org.feng.fenggateway.filters; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.feng.fenggateway.config.SecureProperties; import org.feng.fenggateway.dto.ResponseDto; import org.feng.fenggateway.secure.SecureComponent; import org.feng.fenggateway.secure.SecureComponentFactory; import org.feng.fenggateway.util.GsonUtil; 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.core.io.buffer.DefaultDataBufferFactory; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; 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.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.nio.charset.StandardCharsets; import java.util.Objects; import java.util.Set; /** * 自定義密文過濾器 * * @author feng */ @Slf4j @Component public class CustomCipherTextFilter implements GlobalFilter, Ordered { @Resource private SecureProperties secureProperties; private SecureComponent secureComponent; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 獲取請求體 ServerHttpRequest request = exchange.getRequest(); // 獲取響應(yīng)體 ServerHttpResponse response = exchange.getResponse(); // 請求頭 HttpHeaders headers = request.getHeaders(); // 請求方法 HttpMethod method = request.getMethod(); // 滿足條件,進行過濾 if (isNeedFilterMethod(method) && isNeedFilterContentType(headers.getContentType())) { return DataBufferUtils.join(request.getBody()) .flatMap(dataBuffer -> { try { // 獲取請求參數(shù) String originalRequestBody = getOriginalRequestBody(dataBuffer); // 解密請求參數(shù) String decryptRequestBody = decryptRequest(originalRequestBody); // 裝飾新的請求體 ServerHttpRequestDecorator requestDecorator = serverHttpRequestDecorator(request, decryptRequestBody); // 裝飾新的響應(yīng)體 ServerHttpResponseDecorator responseDecorator = serverHttpResponseDecorator(response); // 使用新的請求和響應(yīng)轉(zhuǎn)發(fā) ServerWebExchange serverWebExchange = exchange.mutate().request(requestDecorator).response(responseDecorator).build(); // 放行攔截 return chain.filter(serverWebExchange); } catch (Exception e) { log.error("密文過濾器加解密錯誤", e); return Mono.empty(); } finally { DataBufferUtils.release(dataBuffer); } }); } return chain.filter(exchange); } private String decryptRequest(String originalRequestBody) { if (!secureProperties.enableDecryptRequestParam()) { log.info("請求參數(shù)解密,跳過"); return originalRequestBody; } log.info("請求參數(shù)解密,原文:{}", originalRequestBody); String decrypted = getSecureComponent().decrypt(originalRequestBody); log.info("請求參數(shù)解密,明文:{}", decrypted); return decrypted; } private String encryptResponse(String originalResponseBody) { if (!secureProperties.enableEncryptResponseParam()) { log.info("響應(yīng)結(jié)果加密,跳過"); return originalResponseBody; } ResponseDto responseDto = GsonUtil.fromJson(originalResponseBody, ResponseDto.class); // 只對data字段進行加密處理 Object data = responseDto.getData(); if (Objects.nonNull(data)) { responseDto.setData(getSecureComponent().encrypt(data.toString())); } log.info("響應(yīng)結(jié)果加密,原文:{}", originalResponseBody); String result = GsonUtil.toJson(responseDto); log.info("響應(yīng)結(jié)果加密,密文:{}", result); return result; } /** * 獲取原始的請求參數(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); } private boolean isNeedFilterMethod(HttpMethod method) { return NEED_FILTER_METHOD_SET.contains(method); } private boolean isNeedFilterContentType(MediaType mediaType) { return NEED_FILTER_MEDIA_TYPE_SET.contains(mediaType) || "json".equals(mediaType.getSubtype()); } 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() { byte[] bytes = decryptRequestBody.getBytes(StandardCharsets.UTF_8); return Flux.just(new DefaultDataBufferFactory().wrap(bytes)); } }; } private ServerHttpResponseDecorator serverHttpResponseDecorator(ServerHttpResponse originalResponse) { DataBufferFactory dataBufferFactory = originalResponse.bufferFactory(); return new ServerHttpResponseDecorator(originalResponse) { @Override public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) { if (body instanceof Flux<? extends DataBuffer> fluxBody) { return super.writeWith(fluxBody.buffer().map(dataBuffers -> { 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 = encryptResponse(originalResponseBody).getBytes(StandardCharsets.UTF_8); originalResponse.getHeaders().setContentLength(encryptedByteArray.length); originalResponse.getHeaders().setContentType(MediaType.APPLICATION_JSON_UTF8); return 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; } }; } private static final Set<HttpMethod> NEED_FILTER_METHOD_SET = Set.of(HttpMethod.GET, HttpMethod.POST, HttpMethod.PUT); private static final Set<MediaType> NEED_FILTER_MEDIA_TYPE_SET = Set.of(MediaType.APPLICATION_JSON); @Override public int getOrder() { return -1; } public SecureComponent getSecureComponent() { if (Objects.isNull(secureComponent)) { secureComponent = SecureComponentFactory.get(secureProperties.getAlgorithm()); } return secureComponent; } }
2.2 網(wǎng)關(guān)配置
server: port: 10010 # 網(wǎng)關(guān)端口 spring: application: name: gateway # 服務(wù)名稱 cloud: gateway: routes: # 網(wǎng)關(guān)路由配置 - id: feng-server1 # 路由id,自定義,只要唯一即可 uri: http://127.0.0.1:8081 # 路由的目標地址 http就是固定地址 predicates: # 路由斷言,也就是判斷請求是否符合路由規(guī)則的條件 - Path=/server/list/server1/** # 這個是按照路徑匹配,只要以/user/開頭就符合要求 - id: feng-server2 uri: http://127.0.0.1:8082 predicates: - Path=/server/list/server2/** # 自定義配置 feng: gateway: secure: request-switch: enable: false response-switch: enable: true algorithm: aes
2.3 自定義配置類
package org.feng.fenggateway.config; import cn.hutool.crypto.symmetric.SymmetricAlgorithm; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.feng.fenggateway.secure.SecureComponentFactory; import org.springframework.boot.context.properties.ConfigurationProperties; import java.util.Objects; /** * 加解密屬性配置 * * @author feng */ @Slf4j @Data @ConfigurationProperties(prefix = SecureProperties.SECURE_PROPERTIES_PREFIX) public class SecureProperties { public static final String SECURE_PROPERTIES_PREFIX = "feng.gateway.secure"; /** * 算法 */ private SymmetricAlgorithm algorithm; /** * 請求開關(guān) */ private SecureSwitch requestSwitch; /** * 響應(yīng)開關(guān) */ private SecureSwitch responseSwitch; public void checkSupportedAlgorithm() { log.info("校驗是否支持算法:{}", algorithm); if (Objects.isNull(algorithm)) { return; } boolean supportedAlgorithm = SecureComponentFactory.isSupportedAlgorithm(algorithm); if (!supportedAlgorithm) { throw new UnsupportedOperationException("不支持的算法"); } log.info("校驗是否支持算法:校驗通過"); } /** * 是否啟用解密請求參數(shù) * * @return 默認為否,其他情況看配置 */ public boolean enableDecryptRequestParam() { if (Objects.isNull(requestSwitch)) { return false; } return requestSwitch.getEnable(); } /** * 是否啟用加密響應(yīng)參數(shù) * * @return 默認為否,其他情況看配置 */ public boolean enableEncryptResponseParam() { if (Objects.isNull(responseSwitch)) { return false; } return responseSwitch.getEnable(); } }
2.4 加密組件接口
這個可以用來擴展支持其他加密算法,目前實現(xiàn)類只有AES。
package org.feng.fenggateway.secure; import cn.hutool.crypto.symmetric.SymmetricAlgorithm; import jakarta.annotation.PostConstruct; /** * 加解密組件 * * @author feng */ public interface SecureComponent { /** * 加密 * * @param originalText 原文 * @return 密文 */ String encrypt(String originalText); /** * 解密 * * @param encryptedText 密文 * @return 解密后的明文 */ String decrypt(String encryptedText); /** * 獲取加解密算法類型 * * @return 加解密算法類型 */ SymmetricAlgorithm getAlgorithmType(); @PostConstruct default void registerToFactory() { SecureComponentFactory.registerBean(this); } }
2.5 加密組件實現(xiàn),AES算法
package org.feng.fenggateway.secure; import cn.hutool.crypto.Mode; import cn.hutool.crypto.Padding; import cn.hutool.crypto.symmetric.AES; import cn.hutool.crypto.symmetric.SymmetricAlgorithm; import org.springframework.stereotype.Component; import java.nio.charset.StandardCharsets; /** * AES加解密組件 * * @author feng */ @Component public class SecureAESComponent implements SecureComponent { /** * 生成密鑰,16、24、32位都行 */ private final static byte[] SECURE_KEY = "r4oz0f3kfk5tgyui".getBytes(StandardCharsets.UTF_8); /** * 偏移量,必須16位 */ private final static String IV = "r21g95kdsd423gy6"; private final static AES AES_INSTANCE = new AES(Mode.CTS, Padding.PKCS5Padding, SECURE_KEY, IV.getBytes(StandardCharsets.UTF_8)); @Override public String encrypt(String originalText) { return AES_INSTANCE.encryptHex(originalText); } @Override public String decrypt(String encryptedText) { return AES_INSTANCE.decryptStr(encryptedText); } @Override public SymmetricAlgorithm getAlgorithmType() { return SymmetricAlgorithm.AES; } }
2.6 啟動類,校驗支持的算法配置
package org.feng.fenggateway; import jakarta.annotation.Resource; import org.feng.fenggateway.config.SecureProperties; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.ConfigurationPropertiesScan; @ConfigurationPropertiesScan @SpringBootApplication public class FengGatewayApplication implements CommandLineRunner { @Resource private SecureProperties secureProperties; public static void main(String[] args) { SpringApplication.run(FengGatewayApplication.class, args); } @Override public void run(String... args) { secureProperties.checkSupportedAlgorithm(); } }
三、請求報文示例
POST http://localhost:10010/server/list/server2/user?authorization=feng Content-Type: application/json;charset=UTF-8 { "username": "fbb" }
四、測試結(jié)果
4.1 網(wǎng)關(guān)項目啟動時
校驗結(jié)果正常:
4.2 發(fā)生請求時
可以看到data字段已經(jīng)加密響應(yīng)了。
請求和響應(yīng)結(jié)果:
到此這篇關(guān)于SpringCloud Gateway實現(xiàn)請求解密和響應(yīng)加密的文章就介紹到這了,更多相關(guān)SpringCloud Gateway內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中System.setProperty()用法與實際應(yīng)用場景
System.setProperty是Java中用于設(shè)置系統(tǒng)屬性的方法,它允許我們在運行時為Java虛擬機(JVM)或應(yīng)用程序設(shè)置一些全局的系統(tǒng)屬性,下面這篇文章主要給大家介紹了關(guān)于Java中System.setProperty()用法與實際應(yīng)用場景的相關(guān)資料,需要的朋友可以參考下2024-04-04Spring security權(quán)限配置與使用大全
Spring Security 本質(zhì)上是借助一系列的 Servlet Filter來提供各種安全性功能,但這并不需要我們手動去添加或者創(chuàng)建多個Filter,本文重點給大家介紹spring-security的配置與使用及實現(xiàn)方式,感興趣的朋友一起看看吧2021-09-09SpringBoot詳解實現(xiàn)自定義異常處理頁面方法
SpringBoot是Spring全家桶的成員之一,是一種整合Spring技術(shù)棧的方式(或者說是框架),同時也是簡化Spring的一種快速開發(fā)的腳手架2022-06-06解決@Autowired報錯Could not autowire. No bea
介紹了在IDEA中使用@Autowired報錯Couldnot autowire. No beans of 'XXX' type found的解決方法,原因是@Autowired在注入service時,由于service接口沒有實現(xiàn)類,而mybatis僅需提供Dao接口,導致@Autowired無法識別2024-12-12Spring的循環(huán)依賴、三級緩存解決方案源碼詳細解析
這篇文章主要介紹了Spring的循環(huán)依賴、三級緩存解決方案源碼詳細解析,在Spring中,由于IOC的控制反轉(zhuǎn),創(chuàng)建對象不再是簡單的new出來,而是交給Spring去創(chuàng)建,會經(jīng)歷一系列Bean的生命周期才創(chuàng)建出相應(yīng)的對象,需要的朋友可以參考下2024-01-01