自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Spring Cloud Gateway 數(shù)字簽名、URL動(dòng)態(tài)加密這樣設(shè)計(jì)真優(yōu)雅!

安全 數(shù)據(jù)安全
在網(wǎng)絡(luò)傳遞數(shù)據(jù)的時(shí)候,為了防止數(shù)據(jù)被篡改,我們會(huì)選擇對(duì)數(shù)據(jù)進(jìn)行加密,數(shù)據(jù)加密分為對(duì)稱加密和非對(duì)稱加密。其中RSA和AES,TLS等加密算法是比較常用的。

在網(wǎng)絡(luò)傳遞數(shù)據(jù)的時(shí)候,為了防止數(shù)據(jù)被篡改,我們會(huì)選擇對(duì)數(shù)據(jù)進(jìn)行加密,數(shù)據(jù)加密分為對(duì)稱加密和非對(duì)稱加密。其中RSA和AES,TLS等加密算法是比較常用的。

對(duì)稱加密

對(duì)稱加密是指加密和解密使用相同的密鑰的加密方法。其基本流程包括以下步驟

  1. 密鑰生成:雙方協(xié)商生成一個(gè)共享密鑰或由一方生成密鑰并安全地傳輸給另一方。
  2. 加密:使用共享密鑰對(duì)原始數(shù)據(jù)進(jìn)行加密,得到加密后的數(shù)據(jù)。
  3. 傳輸:將加密后的數(shù)據(jù)傳輸給另一方。
  4. 解密:接收方使用相同的共享密鑰對(duì)加密數(shù)據(jù)進(jìn)行解密,得到原始數(shù)據(jù)。

非對(duì)稱加密

非對(duì)稱加密是指加密和解密使用不同的密鑰的加密方法,通常稱為公鑰和私鑰。其基本流程包括以下步驟:

  1. 密鑰對(duì)生成:生成一對(duì)密鑰,一個(gè)是公鑰,另一個(gè)是私鑰。公鑰可以公開(kāi),而私鑰需要保密。
  2. 公鑰分發(fā):將公鑰發(fā)送給需要加密數(shù)據(jù)的一方。
  3. 加密:使用公鑰對(duì)原始數(shù)據(jù)進(jìn)行加密,得到加密后的數(shù)據(jù)。
  4. 傳輸:將加密后的數(shù)據(jù)傳輸給另一方。
  5. 解密:接收方使用私鑰對(duì)加密數(shù)據(jù)進(jìn)行解密,得到原始數(shù)據(jù)。

結(jié)合使用:

在實(shí)際應(yīng)用中,對(duì)稱加密和非對(duì)稱加密通常會(huì)結(jié)合使用以達(dá)到安全和效率的平衡。例如:

  1. 使用非對(duì)稱加密交換對(duì)稱密鑰。
  2. 使用對(duì)稱密鑰進(jìn)行數(shù)據(jù)加密和解密。

什么是數(shù)字簽名

再上面我們了解了RSA對(duì)稱加密,那么當(dāng)我們進(jìn)行數(shù)據(jù)交換的時(shí)候,如下:

假設(shè)有AB兩個(gè)人,假設(shè)前面他們已經(jīng)交換完畢了公鑰。

那么此時(shí)當(dāng)A使用B的公鑰加密原始數(shù)據(jù)然后發(fā)送數(shù)據(jù)給B的時(shí)候,它可以再數(shù)據(jù)的后面再攜帶上一個(gè)原始數(shù)據(jù)hash計(jì)算之后得到的hash值,然后用自己的私鑰進(jìn)行加密。

A將數(shù)據(jù)發(fā)送到B之后,由于數(shù)據(jù)使用的是B的公鑰加密,B可以用私鑰解密之后,得到A發(fā)送消息的原本內(nèi)容,然后,B可以使用A的公鑰對(duì)額外的數(shù)字簽名進(jìn)行校驗(yàn),因?yàn)樗僭O(shè)這個(gè)數(shù)據(jù)是A發(fā)送的,那么用A的公鑰就應(yīng)該可以解密成功,所以如果數(shù)據(jù)解密成功之后與A發(fā)送的原始消息經(jīng)過(guò)一樣的Hash運(yùn)算之后相等,那么說(shuō)明沒(méi)有被篡改,而如果不一致,那么就說(shuō)明被篡改了。因?yàn)榈谌绞遣恢繟的私鑰信息的,所以他是用自己的私鑰去加密,得到的hash會(huì)與A進(jìn)行hash之后的值不同,從而判斷數(shù)據(jù)被篡改了。關(guān)注公眾號(hào):碼猿技術(shù)專欄,回復(fù)關(guān)鍵詞:1111 獲取阿里內(nèi)部Java性能調(diào)優(yōu)手冊(cè)!

HTTPS與CA

https其實(shí)不是一個(gè)單獨(dú)的協(xié)議,而是數(shù)據(jù)傳輸?shù)臅r(shí)候使用TLS/SSL進(jìn)行了加密而已。而TLS就是一個(gè)非常典型的非對(duì)稱加密,其兼顧了AES和RSA的安全性和速度。

上面我們已經(jīng)聊完了一個(gè)加密數(shù)據(jù)的交換過(guò)程,那么如果有些人就是偽造了一些域名讓你去訪問(wèn)怎么辦呢?

HTTPS (HTTP Secure) 是一個(gè)安全的 HTTP 通道,它通過(guò) SSL/TLS 協(xié)議來(lái)保證數(shù)據(jù)的安全傳輸。在 HTTPS 請(qǐng)求的過(guò)程中,證書(shū)頒發(fā)機(jī)構(gòu) (CA, Certificate Authority) 扮演了重要的角色。以下是 CA 在保證 HTTPS 請(qǐng)求過(guò)程中數(shù)據(jù)安全交換的方式:

  1. 證書(shū)頒發(fā):CA 為服務(wù)器頒發(fā)一個(gè)數(shù)字證書(shū)。這個(gè)證書(shū)包含了服務(wù)器的公鑰和一些識(shí)別服務(wù)器身份的信息。數(shù)字證書(shū)是由 CA 簽名的,以驗(yàn)證證書(shū)的真實(shí)性和完整性。
  2. 建立安全連接:當(dāng)客戶端第一次連接到服務(wù)器時(shí),服務(wù)器會(huì)發(fā)送其數(shù)字證書(shū)給客戶端??蛻舳藭?huì)驗(yàn)證數(shù)字證書(shū)的合法性,比如檢查證書(shū)是否由一個(gè)受信任的 CA 簽名,檢查證書(shū)是否在有效期內(nèi)等。一旦證書(shū)驗(yàn)證通過(guò),客戶端就能確認(rèn)它是與正確的服務(wù)器進(jìn)行通信,而不是被中間人攻擊。
  3. 密鑰交換:客戶端和服務(wù)器會(huì)使用 SSL/TLS 協(xié)議中的密鑰交換機(jī)制來(lái)協(xié)商一個(gè)會(huì)話密鑰(通常是一個(gè)對(duì)稱密鑰)。一種常見(jiàn)的方法是客戶端生成一個(gè)隨機(jī)的對(duì)稱密鑰,然后用服務(wù)器的公鑰加密它,再發(fā)送給服務(wù)器。服務(wù)器用自己的私鑰解密得到對(duì)稱密鑰。
  4. 數(shù)據(jù)加密和傳輸:一旦會(huì)話密鑰被協(xié)商好,客戶端和服務(wù)器就會(huì)用這個(gè)密鑰來(lái)加密和解密傳輸?shù)臄?shù)據(jù)。這樣,即使數(shù)據(jù)在傳輸過(guò)程中被截獲,攻擊者也無(wú)法解讀數(shù)據(jù)的內(nèi)容,因?yàn)樗麄儧](méi)有會(huì)話密鑰。
  5. 完整性校驗(yàn):SSL/TLS 協(xié)議還提供了數(shù)據(jù)完整性校驗(yàn)。它會(huì)為傳輸?shù)臄?shù)據(jù)生成一個(gè) MAC (Message Authentication Code),以確保數(shù)據(jù)在傳輸過(guò)程中沒(méi)有被篡改。

圖片圖片

其實(shí)前面的第一和第二隨機(jī)數(shù)都是正常傳輸,預(yù)主密鑰的得到就是使用RSA了,此時(shí)只有客戶端和服務(wù)端知道預(yù)主密鑰,之后,對(duì)第一和第二隨機(jī)數(shù)使用預(yù)主密鑰的加密,就可以得到會(huì)話密鑰,此時(shí)加密交互完成。

并且會(huì)話密鑰只應(yīng)用在當(dāng)前會(huì)話,每個(gè)會(huì)話都會(huì)新生成一個(gè),所以安全性大大增加。

只有前面的得到預(yù)主密鑰的過(guò)程用RSA,其他地方都是AES,因?yàn)椋菍?duì)稱實(shí)在太慢了。

Gateway網(wǎng)關(guān)的過(guò)濾器鏈

我們知道,我們可以再Gateway網(wǎng)關(guān)中自定義過(guò)濾器,并且實(shí)現(xiàn)Ordered接口來(lái)對(duì)過(guò)濾器的執(zhí)行順序進(jìn)行排序。如下圖我實(shí)現(xiàn)了三個(gè)自定義的全局過(guò)濾器。

圖片圖片

并且,當(dāng)你實(shí)現(xiàn)全局過(guò)濾器接口的時(shí)候,你必須實(shí)現(xiàn)如下方法

public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain)

其中exchange參數(shù)非常重要,他就是你的請(qǐng)求以及對(duì)你請(qǐng)求的響應(yīng)。而chain就是上面的過(guò)濾器鏈條。

圖片圖片

而我們的過(guò)濾器鏈的作用,其實(shí)就是對(duì)request和response這兩個(gè)重要的類進(jìn)行操作。

比如我可以使用exchange.mutate方法來(lái)對(duì)request和response進(jìn)行修改。

exchange = exchange.mutate().request(build -> {
            try {
                build.uri(
                        new URI("http://localhost:8080/v1/product?productId=1"))
                        .build();
            } catch (URISyntaxException e) {
                throw new RuntimeException(e);
            }
        }
).build();

如下是我修改request之前的請(qǐng)求體內(nèi)容。

圖片圖片

如下就是我修改URI之后的request請(qǐng)求體的內(nèi)容。

圖片圖片

在這里我們把這個(gè)reqeust給他修改有著重大意義,這意味著只要對(duì)加密后的數(shù)據(jù)進(jìn)行解密后,去修改這個(gè)request中的內(nèi)容,我們就能再一次成功的將我們的請(qǐng)求路由到我們指定的路徑。

圖片圖片

而之后,我們最終路由到的請(qǐng)求路徑位置,保存在了DefaultServerWebExchange的attributes中。

![ttps://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a9bf57e6a521475fb8e403526290533e~tplv-k3u1fbpfcp-jj-mark:3024:0:0:0:q75.awebp#?w=1766&h=1273&s=401141&e=png&b=f9f5f5)

最后只要進(jìn)入到ForwardRoutingFilter

圖片圖片

在這里請(qǐng)求完成了最終的處理,然后進(jìn)行轉(zhuǎn)發(fā),發(fā)送到對(duì)應(yīng)的處理類去處理。

這時(shí)候我們看提供遠(yuǎn)程服務(wù)調(diào)用的類的調(diào)用棧即可。

圖片圖片

如何對(duì)自己的路徑傳輸設(shè)定一個(gè)數(shù)字簽名?

上面我們已經(jīng)聊到了,先使用RSA的方式傳遞對(duì)稱密鑰,然后之后的請(qǐng)求使用AES來(lái)進(jìn)行加密解密。這樣子既保證了安全性也保證了請(qǐng)求的速度。

我就按照上面的說(shuō)法,簡(jiǎn)單的實(shí)現(xiàn)了一個(gè)數(shù)字簽名,大概方式如下:

公鑰獲?。?客戶端首先通過(guò)一個(gè)特定的接口從服務(wù)器獲取RSA公鑰。

對(duì)稱密鑰加密:
客戶端生成一個(gè)隨機(jī)的對(duì)稱密鑰,然后使用服務(wù)器的RSA公鑰對(duì)這個(gè)對(duì)稱密鑰進(jìn)行加密。

發(fā)送加密的對(duì)稱密鑰:
客戶端將加密后的對(duì)稱密鑰發(fā)送到服務(wù)器。

對(duì)稱密鑰解密:
服務(wù)器使用自己的RSA私鑰解密客戶端發(fā)送的加密對(duì)稱密鑰,從而得到原始的對(duì)稱密鑰。

加密通信:
從現(xiàn)在開(kāi)始,客戶端和服務(wù)器都會(huì)使用這個(gè)對(duì)稱密鑰來(lái)加密和解密他們之間的通信。這包括URL的動(dòng)態(tài)加密、請(qǐng)求和響應(yīng)的加密解密,以及數(shù)字簽名的驗(yàn)證等。

數(shù)字簽名:
為了確保數(shù)據(jù)的完整性和非否認(rèn)性,客戶端和/或服務(wù)器可以使用對(duì)稱密鑰來(lái)生成和驗(yàn)證數(shù)字簽名。
這樣,雙方都可以確信接收到的數(shù)據(jù)沒(méi)有被篡改,并且確實(shí)來(lái)自預(yù)期的發(fā)送方。 

URL動(dòng)態(tài)加密:
使用對(duì)稱密鑰對(duì)URL進(jìn)行動(dòng)態(tài)加密,以保護(hù)URL中的敏感信息,并防止未經(jīng)授權(quán)的訪問(wèn)。
這個(gè)流程確保了客戶端和服務(wù)器之間的通信安全,防止數(shù)據(jù)被截獲或篡改,同時(shí)也提供了一個(gè)有效的機(jī)制來(lái)驗(yàn)證通信雙方的身份。


具體流程如下:
我們首先需要做的第一步是提供一個(gè)接口讓前端客戶端去訪問(wèn),
并且獲得到我們的公開(kāi)的RSA公鑰,
然后前端拿到這個(gè)RSA公鑰之后加密自己的對(duì)稱密鑰,
然后再一次發(fā)送一個(gè)請(qǐng)求,
這個(gè)請(qǐng)求攜帶的是通過(guò)RSA公鑰加密過(guò)后的對(duì)稱密鑰,
然后服務(wù)端收到這個(gè)對(duì)稱密鑰之后,
通過(guò)RSA私鑰解密可以得到原本的前端發(fā)送的對(duì)稱密鑰。
此時(shí),之后的URL動(dòng)態(tài)加密所需要使用到的密鑰,
以及之后請(qǐng)求的數(shù)字簽名的加密,
都使用AES的方式,
并且使用這個(gè)解密后的對(duì)稱密鑰進(jìn)行加密解密

前端獲取RSA公鑰

我們首先在gateway網(wǎng)關(guān)提供一個(gè)接口用于提供給前端獲取RSA公鑰。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;

import javax.annotation.PostConstruct;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

/**
 * @author: 公眾號(hào):碼猿技術(shù)專欄
 * @date: 2023/10/2 15:13
 * SecurityConfig的作用是返回公鑰
 */
@Configuration
publicclass SecurityConfig {
    private KeyPair keyPair;

    @PostConstruct
    public void init() {
        // Generate RSA key pair
        KeyPairGenerator keyGen;
        try {
            keyGen = KeyPairGenerator.getInstance("RSA");
            keyGen.initialize(2048);
            keyPair = keyGen.genKeyPair();
        } catch (NoSuchAlgorithmException e) {
            thrownew RuntimeException("Failed to generate RSA key pair", e);
        }
    }

    /**
     * 提供給前端獲取RSA公鑰
     * @return
     */
    @Bean
    public RouterFunction<ServerResponse> publicKeyEndpoint() {
        return RouterFunctions.route()
                .GET("/public-key", req -> {
                    String publicKey = Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded());
                    return ServerResponse.ok().bodyValue(publicKey);
                })
                .build();
    }

    public KeyPair getKeyPair() {
        return keyPair;
    }

}

圖片圖片

發(fā)送加密后對(duì)稱密鑰

前端使用得到的公鑰對(duì)自己的對(duì)稱密鑰進(jìn)行加密,代碼如下:

package blossom.star.project.product;

import org.junit.jupiter.api.Test;

import javax.crypto.Cipher;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

//@SpringBootTest
class RSA {

    @Test
    void contextLoads() {
    }


    public static void main(String[] args) throws Exception {
        //TODO 2:這里得到的是獲取rsa的公鑰之后,對(duì)對(duì)稱密鑰進(jìn)行加密,之后就是使用這個(gè)對(duì)稱密鑰進(jìn)行
        //數(shù)據(jù)的加解密
        // Replace with your RSA public key
        String publicKeyPEM = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvXBSqSyOPb01/uOnhnFN8Hvaz1IQbXnxFzGp9rWBxRAI2p6o67Elr1+SW68JnXx4swq7+z0U+YZSuszsoqwIrn8XF75bpJ+NKLkH7Bpe5A+If78zTihsCoPs+x74FIaJTSiVCzWP9mCaDSVO2bPTwOvqMwQ7xlmTmN9QShCIJ6uBXaggB5aWdpkh/IsIsZXIlzFB5HxA8AYj3u0AyWZO+pNS1fwq2Q7GPwWG7Zl7bCrUjIbG40k/Ef1BjdJBhQakMUq3Zqx+LJP37Tk4FzW47bwD9AiSL4DAXT+sc+Hw1fNspd2qFZBN94h5Pxkxoc9ZBMWB2bFBdRb6zkEg0/2OwwIDAQAB" ;

        // Replace with your symmetric key
        String symmetricKey = "zhangjinbiao6666";

        // Converting PEM to PublicKey
        byte[] decoded = Base64.getDecoder().decode(publicKeyPEM);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decoded);
        PublicKey publicKey = keyFactory.generatePublic(keySpec);

        // Encrypting symmetric key
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        byte[] encryptedSymmetricKey = cipher.doFinal(symmetricKey.getBytes());
        String encryptedSymmetricKeyBase64 = Base64.getEncoder().encodeToString(encryptedSymmetricKey);

        // Printing encrypted symmetric key
        System.out.println(encryptedSymmetricKeyBase64);
    }

}

后端接收當(dāng)前會(huì)話對(duì)稱密鑰并保存

這里由于我沒(méi)有前端,不好操作,我就直接暫時(shí)寫(xiě)死了,但是具體的實(shí)現(xiàn)邏輯就是與前端制定一個(gè)唯一的會(huì)話id,然后之后只要是同一個(gè)會(huì)話就可以使用同一個(gè)對(duì)稱密鑰,這樣子才能進(jìn)一步保證安全,而不是一直使用同一個(gè)對(duì)稱密鑰。

package blossom.star.project.gateway.filter;

import blossom.star.framework.common.constant.HttpStatus;
import blossom.star.project.gateway.config.SecurityConfig;
import blossom.star.project.gateway.util.GatewayUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.util.Base64;


/**
 * @author 公眾號(hào):碼猿技術(shù)專欄
 * 對(duì)稱密鑰保存過(guò)濾器
 * 當(dāng)前過(guò)濾器首先會(huì)先獲取請(qǐng)求頭中的對(duì)稱密鑰
 * 如果有,那么獲取對(duì)稱密鑰并且保存到Redis中
 */
//@Component
publicclass SymmetricKeyFilter implements GlobalFilter, Ordered {

    @Autowired
    private SecurityConfig securityConfig;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;


    //TODO 3:這里會(huì)把加密好的對(duì)稱密鑰 解密 然后放入到redis中
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String encryptedSymmetricKey = exchange.getRequest().getHeaders().getFirst("X-Encrypted-Symmetric-Key");
        if (encryptedSymmetricKey != null) {
            try {
                Cipher cipher = Cipher.getInstance("RSA");
                cipher.init(Cipher.DECRYPT_MODE, securityConfig.getKeyPair().getPrivate());
                byte[] decryptedKeyBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedSymmetricKey));
                //得到對(duì)稱密鑰
                String symmetricKey = new String(decryptedKeyBytes, StandardCharsets.UTF_8);

                ////在非阻塞上下文中阻塞調(diào)用可能會(huì)導(dǎo)致線程饑餓
                ////TODO 需要優(yōu)化一下這里 來(lái)確保每個(gè)請(qǐng)求可以唯一對(duì)應(yīng)一個(gè)加密密鑰
                //String sessionId = exchange.getSession().block().getId();
                //stringRedisTemplate.opsForValue().set(sessionId, symmetricKey);
                String redisSymmetricKey = "symmetric:key:"+1;
                stringRedisTemplate.opsForValue().set(redisSymmetricKey, symmetricKey);

            } catch (Exception e) {
                e.printStackTrace();
                String responseBody = "there are something wrong occurs when decrypt your key!!!";
                GatewayUtil.responseMessage(exchange,responseBody);

                // 獲取響應(yīng)對(duì)象
                //ServerHttpResponse response = exchange.getResponse();
                ////處理對(duì)稱密鑰出現(xiàn)了問(wèn)題
                //response.setRawStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
                //response.getHeaders().setContentType(MediaType.TEXT_PLAIN);
                //
                //// 返回你想要的字符串
                //return response.writeWith(
                //        Mono.just(response.bufferFactory().wrap(responseBody.getBytes())));
            }
        }
        return chain.filter(exchange);
    }

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

}

前端發(fā)送AES加密請(qǐng)求

比如這里的請(qǐng)求參數(shù)為productId=1,然后我們額外發(fā)送一個(gè)signature=wHYOLLkTn00DVrcmuCFzFQ==,signature的值就是對(duì)這個(gè)參數(shù)productId=1進(jìn)行AES加密之后得到的數(shù)據(jù)。

然后我們?cè)僖淮螌?duì)String plaintext = "productId=1&signature=wHYOLLkTn00DVrcmuCFzFQ==";來(lái)進(jìn)行加密,然后發(fā)送的請(qǐng)求以這個(gè)為參數(shù)。

也就是發(fā)送:

http://localhost:8080/v1/product/encrypt/8lPoJ5k/aHpfgKlxB5A9eUXqZ4MvgpFqN/SwDBVwDbERjBkQw62kfAmfsDW2Bngm

只要后端檢測(cè)到這個(gè)路徑有任何一點(diǎn)不對(duì)勁,就會(huì)直接報(bào)錯(cuò)返回。

package blossom.star.project.product;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

/**
 * @author: 公眾號(hào):碼猿技術(shù)專欄
 * @date: 2023/10/2 17:32
 * AES類
 */
publicclass AES {
    //1:首先讓前端對(duì)請(qǐng)求路徑傳輸進(jìn)行AES的加密 密鑰已經(jīng)傳遞
    //比如productId=1 ---》wHYOLLkTn00DVrcmuCFzFQ==
    //如果有多個(gè) 就直接 & 的方式進(jìn)行拼接然后AES加密即可
    //2:signature=wHYOLLkTn00DVrcmuCFzFQ==
    //3:然后在對(duì)整個(gè)URL進(jìn)行加密傳輸,傳輸方式為 /encrypt +
    // /5s7/98nWOXAJKujQ7nj66ZhohFdur/pPBzd3Y9kZqeIrZmPvTegG8
    // +OYwY6IMr9dXtK9vmZvJoEEsWZT+LLBCQ==
    //其中 + 后面的就是我們aes加密后的url ,/encrypt用于表示進(jìn)行前端的路由

    public static void main(String[] args) throws Exception {
        //TODO 1:首先設(shè)定一下加密的內(nèi)容 這里直接用java代碼加密
        String plaintext = "productId=1";
        //String plaintext = "productId=1&signature=wHYOLLkTn00DVrcmuCFzFQ==";
        String symmetricKey = "zhangjinbiao6666";  // Ensure this key has 16 bytes

        String encryptedText = encryptUrl(plaintext, symmetricKey);
        System.out.println(encryptedText);
    }

    public static String encryptUrl(String url, String symmetricKey) throws Exception {
        SecretKeySpec keySpec = new SecretKeySpec(symmetricKey.getBytes(StandardCharsets.UTF_8), "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.ENCRYPT_MODE, keySpec);
        byte[] encryptedBytes = cipher.doFinal(url.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(encryptedBytes);
    }

}

驗(yàn)證請(qǐng)求

而如果請(qǐng)求的參數(shù)被篡改了,比如上面的productId=2,那么有如下圖情況:

圖片圖片

此時(shí)驗(yàn)證請(qǐng)求是否被修改的方法就會(huì)報(bào)錯(cuò)。

圖片圖片

下面再驗(yàn)證請(qǐng)求是否被篡改的過(guò)程中,代碼寫(xiě)的可能有一點(diǎn)丑陋。

package blossom.star.project.gateway.filter;

import blossom.star.framework.common.constant.HttpStatus;
import blossom.star.project.gateway.util.CryptoHelper;
import blossom.star.project.gateway.util.GatewayUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.net.URISyntaxException;

/**
 * @author 公眾號(hào):碼猿技術(shù)專欄
 * 當(dāng)前類首先會(huì)解析加密后的URL
 * 當(dāng)前類用于解析參數(shù) 如果參數(shù)解密后和signature不一樣則返回
 * 并且會(huì)重新設(shè)定路由路徑
 */
@Component
publicclass CryptoFilter implements GlobalFilter, Ordered {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private CryptoHelper cryptoHelper;


    //TODO 4:在這里對(duì)加密的URL進(jìn)行解密
    //并且會(huì)得到路徑的參數(shù)
    //然后對(duì)參數(shù)進(jìn)行加密之后和signature比較判斷是否被修改
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //String sessionId = exchange.getSession().block().getId();
        String redisSymmetricKey = "symmetric:key:" + 1;
        //String symmetricKey = stringRedisTemplate.opsForValue().get(sessionId);
        String symmetricKey = stringRedisTemplate.opsForValue().get(redisSymmetricKey);
        if (symmetricKey == null) {
            return GatewayUtil.responseMessage(exchange, "this session has not symmetricKey!!!");
        }

        try {
            //URL動(dòng)態(tài)加密  數(shù)字簽名 signature
            //如果URL已加密,則解密該URL
            //path:/v1/product/encrypt/WyYSV30Cor8QX/eWGsQ7yPD3EvNRRS0HF845UOb+KAdwHPKZByMa3250J/z2S4at
            //uri:http://localhost:8080/v1/product/encrypt/WyYSV30Cor8QX/eWGsQ7yPD3EvNRRS0HF845UOb+KAdwHPKZByMa3250J/z2S4at
            String encryptedUrl = exchange.getRequest().getURI().toString();
            String path = exchange.getRequest().getURI().getPath();
            String encryptPathParam = path.substring(path.indexOf("/encrypt/") + 9);
            String decryptedPathParam = cryptoHelper.decryptUrl(encryptPathParam, symmetricKey);
            String decryptedUri =
                    encryptedUrl.substring(0, encryptedUrl.indexOf("/encrypt/"))
                            .concat("?").concat(decryptedPathParam);
            //這個(gè)方法直接修改的是exchange里面的request
            exchange = exchange.mutate().request(build -> {
                try {
                    build.uri(new URI(decryptedUri));
                } catch (URISyntaxException e) {
                    thrownew RuntimeException(e);
                }
            }).build();
            //TODO 需要前端這里首先按照前后端約定的加密方式進(jìn)行一次加密
            //然后得到一個(gè)signature,放在請(qǐng)求的末尾
            //然后對(duì)整個(gè)URL進(jìn)行加密請(qǐng)求
            // 解析解密后的URL以獲取解密的查詢參數(shù)
            UriComponents uriComponents = UriComponentsBuilder.fromUriString(decryptedUri).build();
            MultiValueMap<String, String> decryptedQueryParams = uriComponents.getQueryParams();

            // 驗(yàn)證請(qǐng)求參數(shù)的簽名
            String signature = decryptedQueryParams.getFirst("signature");
            if (!cryptoHelper.verifySignature(decryptedQueryParams, signature, symmetricKey)) {

                return GatewayUtil.responseMessage(exchange,
                        "the param has something wrong!!!");
            }

        } catch (Exception e) {
            return GatewayUtil.responseMessage(exchange,
                    "the internal server occurs an error!!!");
        }

        return chain.filter(exchange);
    }


    @Override
    public int getOrder() {
        return -200;
    }
}
package blossom.star.project.gateway.util;

import org.springframework.context.annotation.Configuration;
import org.springframework.util.MultiValueMap;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.List;
import java.util.Map;


/**
 * @author 公眾號(hào):碼猿技術(shù)專欄
 * 密碼學(xué)工具包
 */
@Configuration
publicclass CryptoHelper {

    public String decryptUrl(String encryptedUrl, String symmetricKey) throws Exception {
        SecretKeySpec keySpec = new SecretKeySpec(symmetricKey.getBytes(StandardCharsets.UTF_8), "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.DECRYPT_MODE, keySpec);
        byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedUrl));
        returnnew String(decryptedBytes, StandardCharsets.UTF_8);
    }

    //解析路徑參數(shù)并且加密,后判斷是否和signature一樣
    public boolean verifySignature(MultiValueMap<String, String> queryParams, String signature, String symmetricKey) throws Exception {
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, List<String>> entry : queryParams.entrySet()) {
            //將簽名本身從要驗(yàn)證的數(shù)據(jù)中排除
            if (!"signature".equals(entry.getKey())) {
                sb.append(entry.getKey()).append("=").append(String.join(",", entry.getValue())).append("&");
            }
        }
        sb.setLength(sb.length()-1);
        String computedSignature = encryptRequestParam(sb.toString(), symmetricKey);
        return computedSignature.equals(signature);
    }


    public static String encryptRequestParam(String requestParam, String symmetricKey) throws Exception {
        SecretKeySpec keySpec = new SecretKeySpec(symmetricKey.getBytes(StandardCharsets.UTF_8), "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.ENCRYPT_MODE, keySpec);
        byte[] encryptedBytes = cipher.doFinal(requestParam.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(encryptedBytes);
    }
}

如果請(qǐng)求的過(guò)程中,請(qǐng)求的數(shù)據(jù)并沒(méi)有被修改,那么可以正確解析,如下:

圖片圖片

如何實(shí)現(xiàn)URL的動(dòng)態(tài)加密?

動(dòng)態(tài)加密其實(shí)在上面就已經(jīng)說(shuō)了。

可以發(fā)現(xiàn)我們發(fā)送的實(shí)際請(qǐng)求是下面這個(gè),/encrypt/后面的就是我們約定好的加密參數(shù)。

http://localhost:8080/v1/product/encrypt/WLB8EDs2LNTsUJpS/aANt0XqZ4MvgpFqN/SwDBVwDbERjBkQw62kfAmfsDW2Bngm

實(shí)際再處理過(guò)程中會(huì)去掉/encrypt,他只是用于標(biāo)識(shí)具體的加密參數(shù)位置而已。

責(zé)任編輯:武曉燕 來(lái)源: 碼猿技術(shù)專欄
相關(guān)推薦

2021-12-28 13:54:52

加密密鑰Java

2016-11-10 23:51:41

2009-03-02 16:42:33

2009-08-14 13:40:17

數(shù)字簽名電子簽名安全體系結(jié)構(gòu)

2011-08-29 10:27:38

IT技術(shù)數(shù)字簽名數(shù)字證書(shū)

2010-09-17 20:20:14

2010-06-04 09:21:51

身份認(rèn)證數(shù)字簽名

2019-04-18 15:00:36

2010-10-08 21:14:08

2019-08-22 09:55:17

RedisAPI數(shù)據(jù)

2013-04-22 16:07:45

2010-09-02 21:10:13

2009-06-29 13:26:44

Java編程入門(mén)加密

2009-07-19 21:44:39

2011-01-19 17:27:21

Sylpheed

2011-06-20 15:12:48

微軟木馬

2020-08-13 18:40:51

OpenSSL密碼學(xué)Linux

2022-01-07 07:29:08

Rbac權(quán)限模型

2023-03-08 09:03:55

2022-07-28 10:27:58

量子計(jì)算
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)