SpringCloudGateway实现数字签名与URL动态加密

文章目录

  • 对称加密
  • 非对称加密
  • 什么是数字签名
  • HTTPS与CA
  • ⭐Gateway网关的过滤器链
  • 如何对自己的路径传输设定一个数字签名?
    • 前端获取RSA公钥
    • 发送加密后对称密钥
    • 后端接收当前会话对称密钥并保存
    • 前端发送AES加密请求
    • 验证请求
  • 如何实现URL的动态加密?

再网络传递数据的时候,为了防止数据被篡改,我们会选择对数据进行加密,数据加密分为对称加密和非对称加密。其中RSA和AES,TLS等加密算法是比较常用的。

对称加密

对称加密是指加密和解密使用相同的密钥的加密方法。其基本流程包括以下步骤:

  1. 密钥生成
    • 双方协商生成一个共享密钥或由一方生成密钥并安全地传输给另一方。
  1. 加密
    • 使用共享密钥对原始数据进行加密,得到加密后的数据。
  1. 传输
    • 将加密后的数据传输给另一方。
  1. 解密
    • 接收方使用相同的共享密钥对加密数据进行解密,得到原始数据。

非对称加密

非对称加密是指加密和解密使用不同的密钥的加密方法,通常称为公钥和私钥。其基本流程包括以下步骤:

  1. 密钥对生成
    • 生成一对密钥,一个是公钥,另一个是私钥。公钥可以公开,而私钥需要保密。
  1. 公钥分发
    • 将公钥发送给需要加密数据的一方。
  1. 加密
    • 使用公钥对原始数据进行加密,得到加密后的数据。
  1. 传输
    • 将加密后的数据传输给另一方。
  1. 解密
    • 接收方使用私钥对加密数据进行解密,得到原始数据。

结合使用:

在实际应用中,对称加密和非对称加密通常会结合使用以达到安全和效率的平衡。例如:

  1. 使用非对称加密交换对称密钥。
  2. 使用对称密钥进行数据加密和解密。

什么是数字签名

再上面我们了解了RSA对称加密,那么当我们进行数据交换的时候,如下:

假设有AB两个人,假设前面他们已经交换完毕了公钥。

那么此时当A使用B的公钥加密原始数据然后发送数据给B的时候,它可以再数据的后面再携带上一个原始数据hash计算之后得到的hash值,然后用自己的私钥进行加密。

A将数据发送到B之后,由于数据使用的是B的公钥加密,B可以用私钥解密之后,得到A发送消息的原本内容,然后,B可以使用A的公钥对额外的数字签名进行校验,因为它假设这个数据是A发送的,那么用A的公钥就应该可以解密成功,所以如果数据解密成功之后与A发送的原始消息经过一样的Hash运算之后相等,那么说明没有被篡改,而如果不一致,那么就说明被篡改了。因为第三方是不知道A的私钥信息的,所以他是用自己的私钥去加密,得到的hash会与A进行hash之后的值不同,从而判断数据被篡改了。

HTTPS与CA

https其实不是一个单独的协议,而是数据传输的时候使用TLS/SSL进行了加密而已。而TLS就是一个非常典型的非对称加密,其兼顾了AES和RSA的安全性和速度。

上面我们已经聊完了一个加密数据的交换过程,那么如果有些人就是伪造了一些域名让你去访问怎么办呢?

HTTPS (HTTP Secure) 是一个安全的 HTTP 通道,它通过 SSL/TLS 协议来保证数据的安全传输。在 HTTPS 请求的过程中,证书颁发机构 (CA, Certificate Authority) 扮演了重要的角色。以下是 CA 在保证 HTTPS 请求过程中数据安全交换的方式:

  1. 证书颁发:
    • CA 为服务器颁发一个数字证书。这个证书包含了服务器的公钥和一些识别服务器身份的信息。
    • 数字证书是由 CA 签名的,以验证证书的真实性和完整性。
  1. 建立安全连接:
    • 当客户端第一次连接到服务器时,服务器会发送其数字证书给客户端。
    • 客户端会验证数字证书的合法性,比如检查证书是否由一个受信任的 CA 签名,检查证书是否在有效期内等。
    • 一旦证书验证通过,客户端就能确认它是与正确的服务器进行通信,而不是被中间人攻击。
  1. 密钥交换:
    • 客户端和服务器会使用 SSL/TLS 协议中的密钥交换机制来协商一个会话密钥(通常是一个对称密钥)。
    • 一种常见的方法是客户端生成一个随机的对称密钥,然后用服务器的公钥加密它,再发送给服务器。服务器用自己的私钥解密得到对称密钥。
  1. 数据加密和传输:
    • 一旦会话密钥被协商好,客户端和服务器就会用这个密钥来加密和解密传输的数据。
    • 这样,即使数据在传输过程中被截获,攻击者也无法解读数据的内容,因为他们没有会话密钥。
  1. 完整性校验:
    • SSL/TLS 协议还提供了数据完整性校验。它会为传输的数据生成一个 MAC (Message Authentication Code),以确保数据在传输过程中没有被篡改。

其实前面的第一和第二随机数都是正常传输,预主密钥的得到就是使用RSA了,此时只有客户端和服务端知道预主密钥,之后,对第一和第二随机数使用预主密钥的加密,就可以得到会话密钥,此时加密交互完成。

并且会话密钥只应用在当前会话,每个会话都会新生成一个,所以安全性大大增加。

只有前面的得到预主密钥的过程用RSA,其他地方都是AES,因为,非对称实在太慢了。

⭐Gateway网关的过滤器链

我们知道,我们可以再Gateway网关中自定义过滤器,并且实现Ordered接口来对过滤器的执行顺序进行排序。如下图我实现了三个自定义的全局过滤器。

并且,当你实现全局过滤器接口的时候,你必须实现如下方法

public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain)

其中exchange参数非常重要,他就是你的请求以及对你请求的响应。而chain就是上面的过滤器链条。

而我们的过滤器链的作用,其实就是对request和response这两个重要的类进行操作。

比如我可以使用exchange.mutate方法来对request和response进行修改。

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之前的请求体内容。

如下就是我修改URI之后的request请求体的内容。

在这里我们把这个reqeust给他修改有着重大意义,这意味着只要对加密后的数据进行解密后,去修改这个request中的内容,我们就能再一次成功的将我们的请求路由到我们指定的路径。

而之后,我们最终路由到的请求路径位置,保存在了DefaultServerWebExchange的attributes中。

最后只要进入到ForwardRoutingFilter

在这里请求完成了最终的处理,然后进行转发,发送到对应的处理类去处理。

这时候我们看提供远程服务调用的类的调用栈即可。

如何对自己的路径传输设定一个数字签名?

上面我们已经聊到了,先使用RSA的方式传递对称密钥,然后之后的请求使用AES来进行加密解密。这样子既保证了安全性也保证了请求的速度。

我就按照上面的说法,简单的实现了一个数字签名,大概方式如下:

公钥获取:
客户端首先通过一个特定的接口从服务器获取RSA公钥。对称密钥加密:
客户端生成一个随机的对称密钥,然后使用服务器的RSA公钥对这个对称密钥进行加密。发送加密的对称密钥:
客户端将加密后的对称密钥发送到服务器。对称密钥解密:
服务器使用自己的RSA私钥解密客户端发送的加密对称密钥,从而得到原始的对称密钥。加密通信:
从现在开始,客户端和服务器都会使用这个对称密钥来加密和解密他们之间的通信。这包括URL的动态加密、请求和响应的加密解密,以及数字签名的验证等。数字签名:
为了确保数据的完整性和非否认性,客户端和/或服务器可以使用对称密钥来生成和验证数字签名。
这样,双方都可以确信接收到的数据没有被篡改,并且确实来自预期的发送方。 URL动态加密:
使用对称密钥对URL进行动态加密,以保护URL中的敏感信息,并防止未经授权的访问。
这个流程确保了客户端和服务器之间的通信安全,防止数据被截获或篡改,同时也提供了一个有效的机制来验证通信双方的身份。具体流程如下:
我们首先需要做的第一步是提供一个接口让前端客户端去访问,
并且获得到我们的公开的RSA公钥,
然后前端拿到这个RSA公钥之后加密自己的对称密钥,
然后再一次发送一个请求,
这个请求携带的是通过RSA公钥加密过后的对称密钥,
然后服务端收到这个对称密钥之后,
通过RSA私钥解密可以得到原本的前端发送的对称密钥。
此时,之后的URL动态加密所需要使用到的密钥,
以及之后请求的数字签名的加密,
都使用AES的方式,
并且使用这个解密后的对称密钥进行加密解密

前端获取RSA公钥

我们首先在gateway网关提供一个接口用于提供给前端获取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: 张锦标* @date: 2023/10/2 15:13* SecurityConfig的作用是返回公钥*/
@Configuration
public class SecurityConfig {private KeyPair keyPair;@PostConstructpublic void init() {// Generate RSA key pairKeyPairGenerator keyGen;try {keyGen = KeyPairGenerator.getInstance("RSA");keyGen.initialize(2048);keyPair = keyGen.genKeyPair();} catch (NoSuchAlgorithmException e) {throw new RuntimeException("Failed to generate RSA key pair", e);}}/*** 提供给前端获取RSA公钥* @return*/@Beanpublic 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;}}

发送加密后对称密钥

前端使用得到的公钥对自己的对称密钥进行加密,代码如下:

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 {@Testvoid contextLoads() {}public static void main(String[] args) throws Exception {//TODO 2:这里得到的是获取rsa的公钥之后,对对称密钥进行加密,之后就是使用这个对称密钥进行//数据的加解密// Replace with your RSA public keyString publicKeyPEM = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvXBSqSyOPb01/uOnhnFN8Hvaz1IQbXnxFzGp9rWBxRAI2p6o67Elr1+SW68JnXx4swq7+z0U+YZSuszsoqwIrn8XF75bpJ+NKLkH7Bpe5A+If78zTihsCoPs+x74FIaJTSiVCzWP9mCaDSVO2bPTwOvqMwQ7xlmTmN9QShCIJ6uBXaggB5aWdpkh/IsIsZXIlzFB5HxA8AYj3u0AyWZO+pNS1fwq2Q7GPwWG7Zl7bCrUjIbG40k/Ef1BjdJBhQakMUq3Zqx+LJP37Tk4FzW47bwD9AiSL4DAXT+sc+Hw1fNspd2qFZBN94h5Pxkxoc9ZBMWB2bFBdRb6zkEg0/2OwwIDAQAB" ;// Replace with your symmetric keyString symmetricKey = "zhangjinbiao6666";// Converting PEM to PublicKeybyte[] decoded = Base64.getDecoder().decode(publicKeyPEM);KeyFactory keyFactory = KeyFactory.getInstance("RSA");X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decoded);PublicKey publicKey = keyFactory.generatePublic(keySpec);// Encrypting symmetric keyCipher 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 keySystem.out.println(encryptedSymmetricKeyBase64);}}

后端接收当前会话对称密钥并保存

这里由于我没有前端,不好操作,我就直接暂时写死了,但是具体的实现逻辑就是与前端制定一个唯一的会话id,然后之后只要是同一个会话就可以使用同一个对称密钥,这样子才能进一步保证安全,而不是一直使用同一个对称密钥。

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 张锦标* 对称密钥保存过滤器* 当前过滤器首先会先获取请求头中的对称密钥* 如果有,那么获取对称密钥并且保存到Redis中*/
//@Component
public class SymmetricKeyFilter implements GlobalFilter, Ordered {@Autowiredprivate SecurityConfig securityConfig;@Autowiredprivate StringRedisTemplate stringRedisTemplate;//TODO 3:这里会把加密好的对称密钥 解密 然后放入到redis中@Overridepublic 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));//得到对称密钥String symmetricKey = new String(decryptedKeyBytes, StandardCharsets.UTF_8);在非阻塞上下文中阻塞调用可能会导致线程饥饿TODO 需要优化一下这里 来确保每个请求可以唯一对应一个加密密钥//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);// 获取响应对象//ServerHttpResponse response = exchange.getResponse();处理对称密钥出现了问题//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);}@Overridepublic int getOrder() {return -300;}}

前端发送AES加密请求

比如这里的请求参数为productId=1,然后我们额外发送一个signature=wHYOLLkTn00DVrcmuCFzFQ==,

signature的值就是对这个参数productId=1进行AES加密之后得到的数据。

然后我们再一次对String plaintext = “productId=1&signature=wHYOLLkTn00DVrcmuCFzFQ==”;来进行加密,然后发送的请求以这个为参数。

也就是发送http://localhost:8080/v1/product/encrypt/8lPoJ5k/aHpfgKlxB5A9eUXqZ4MvgpFqN/SwDBVwDbERjBkQw62kfAmfsDW2Bngm

只要后端检测到这个路径有任何一点不对劲,就会直接报错返回。

package blossom.star.project.product;import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;/*** @author: 张锦标* @date: 2023/10/2 17:32* AES类*/
public class AES {//1:首先让前端对请求路径传输进行AES的加密 密钥已经传递//比如productId=1 ---》wHYOLLkTn00DVrcmuCFzFQ==//如果有多个 就直接 & 的方式进行拼接然后AES加密即可//2:signature=wHYOLLkTn00DVrcmuCFzFQ==//3:然后在对整个URL进行加密传输,传输方式为 /encrypt +// /5s7/98nWOXAJKujQ7nj66ZhohFdur/pPBzd3Y9kZqeIrZmPvTegG8// +OYwY6IMr9dXtK9vmZvJoEEsWZT+LLBCQ==//其中 + 后面的就是我们aes加密后的url ,/encrypt用于表示进行前端的路由public static void main(String[] args) throws Exception {//TODO 1:首先设定一下加密的内容 这里直接用java代码加密String plaintext = "productId=1";//String plaintext = "productId=1&signature=wHYOLLkTn00DVrcmuCFzFQ==";String symmetricKey = "zhangjinbiao6666";  // Ensure this key has 16 bytesString 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);}}

验证请求

而如果请求的参数被篡改了,比如上面的productId=2,那么有如下图情况

此时验证请求是否被修改的方法就会报错

下面再验证请求是否被篡改的过程中,代码写的可能有一点丑陋。

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 张锦标* 当前类首先会解析加密后的URL* 当前类用于解析参数 如果参数解密后和signature不一样则返回* 并且会重新设定路由路径*/
@Component
public class CryptoFilter implements GlobalFilter, Ordered {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Autowiredprivate CryptoHelper cryptoHelper;//TODO 4:在这里对加密的URL进行解密//并且会得到路径的参数//然后对参数进行加密之后和signature比较判断是否被修改@Overridepublic 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动态加密  数字签名 signature//如果URL已加密,则解密该URL//path:/v1/product/encrypt/WyYSV30Cor8QX/eWGsQ7yPD3EvNRRS0HF845UOb+KAdwHPKZByMa3250J/z2S4at//uri:http://localhost:8080/v1/product/encrypt/WyYSV30Cor8QX/eWGsQ7yPD3EvNRRS0HF845UOb+KAdwHPKZByMa3250J/z2S4atString 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);//这个方法直接修改的是exchange里面的requestexchange = exchange.mutate().request(build -> {try {build.uri(new URI(decryptedUri));} catch (URISyntaxException e) {throw new RuntimeException(e);}}).build();//TODO 需要前端这里首先按照前后端约定的加密方式进行一次加密//然后得到一个signature,放在请求的末尾//然后对整个URL进行加密请求// 解析解密后的URL以获取解密的查询参数UriComponents uriComponents = UriComponentsBuilder.fromUriString(decryptedUri).build();MultiValueMap<String, String> decryptedQueryParams = uriComponents.getQueryParams();// 验证请求参数的签名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);}@Overridepublic 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 张锦标* 密码学工具包*/
@Configuration
public class 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));return new String(decryptedBytes, StandardCharsets.UTF_8);}//解析路径参数并且加密,后判断是否和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()) {//将签名本身从要验证的数据中排除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);}
}

如果请求的过程中,请求的数据并没有被修改,那么可以正确解析,如下

如何实现URL的动态加密?

动态加密其实在上面就已经说了。

可以发现我们发送的实际请求是下面这个,/encrypt/后面的就是我们约定好的加密参数。

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

实际再处理过程中会去掉/encrypt,他只是用于标识具体的加密参数位置而已。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/93617.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于Python3搭建qt开发环境

Python可视化编程相信大部分刚接触都是tkinter&#xff0c;tkinter是Python自带的库&#xff0c;不需要安装第三方库即可使用&#xff0c;在我的Python专栏中也有很多基于tkinter来设计的可视化界面。本篇文章将尝试另外一个Python的可视化编程库(pyqt)&#xff0c;与tkinter编…

深度学习 二:COVID 19 Cases Prediction (Regression)

Deep Learning 1. 回归算法思路2. 代码2.1 基础操作2.2 定义相关函数2.3.1 定义图像绘制函数2.3.2 数据集加载及预处理2.3.3 构造数据加载器2.3.4 构建前馈神经网络&#xff08;Feedforward Neural Network&#xff09;模型2.3.5 神经网络的训练过程2.3.6 模型评估2.3.7 模型测…

【考研数学】高等数学第七模块 —— 曲线积分与曲面积分 | 3. 对面积的曲面积分(第一类曲面积分)

文章目录 二、曲面积分2.1 对面积的曲面积分&#xff08;第一类曲面积分&#xff09;2.1.1 问题引入 —— 曲面的质量2.1.2 对面积的曲面积分定义及性质2.1.3 对面积的曲面积分的计算法 写在最后 二、曲面积分 2.1 对面积的曲面积分&#xff08;第一类曲面积分&#xff09; 2…

YTM32的电源管理与低功耗系统详解

YTM32的电源管理与低功耗系统详解 苏勇&#xff0c;2023年10月 文章目录 YTM32的电源管理与低功耗系统详解缘起原理与机制电源管理模型的功耗模式正常模式&#xff08;Normal&#xff09;休眠模式&#xff08;Sleep&#xff09;深度休眠模式&#xff08;DeepSleep&#xff09;…

Flutter+SpringBoot实现ChatGPT流实输出

FlutterSpringBoot实现ChatGPT流式输出、上下文了连续对话 最终实现Flutter的流式输出上下文连续对话。 这里就是提供一个简单版的工具类和使用案例&#xff0c;此处页面仅参考。 服务端 这里直接封装提供工具类&#xff0c;修改自己的apiKey即可使用&#xff0c;支持连续…

Python使用词云图展示

网上看到一个txt文本信息&#xff0c;共2351条饭否记录&#xff0c;据说是微信之父每天发的饭否记录&#xff0c;其实我不知道什么是饭否。我读取这个文本内容&#xff0c;展示到词语图上。之前也使用过&#xff0c;但是好久没有玩Python了&#xff0c;称假期空闲&#xff0c;练…

Spring的注解开发-注解方式整合MyBatis代码实现

之前使用xml方式整合了MyBatis&#xff0c;文章导航&#xff1a;Spring整合第三方框架-MyBatis整合Spring实现-CSDN博客 现在使用注解的方式无非是就是将xml标签替换为注解&#xff0c;将xml配置文件替换为配置类而已。 非自定义配置类 package com.example.Configure;import c…

黑马头条项目环境搭建

注册中心网关配置 spring:cloud:gateway:globalcors:add-to-simple-url-handler-mapping: truecorsConfigurations:[/**]:allowedHeaders: "*"allowedOrigins: "*"allowedMethods:- GET- POST- DELETE- PUT- OPTIONroutes:# 平台管理- id: useruri: lb://…

c# 委托 事件 lambda表达式

委托 C/C中的函数指针实例&#xff1a; typedef int (*Calc)(int a, int b); //这里必须加括号 int Add(int a, int b) {return a b; } int Sub(int a, int b) {return a - b; } int main() {int x 100;int y 200;int z 0;Calc funcPoint1 &Add;Calc funcPoint2 &am…

Android学习之路(19) ListView详解

一.ListView简介 在Android开发中&#xff0c;ListView是一个比较常用的控件。它以列表的形式 展示具体数据内容&#xff0c;并且能够根据数据的长度自适应屏幕显示。 二.ListView简单用法 代码部分 1.布局界面 activity_main.xml 代码&#xff1a; <?xml version"…

新手学习笔记-----编译和链接

目录 1. 翻译环境和运⾏环境 2. 翻译环境&#xff1a;预编译编译汇编链接 2.1 预处理 2.2 编译 2.2.1 词法分析 2.2.2 语法分析 2.2.3 语义分析 2.3 汇编 2.4 链接 3. 运⾏环境 1. 翻译环境和运⾏环境 在ANSI C的任何⼀种实现中&#xff0c;存在两个不同的环境。 第…

LSTM+CRF模型

今天讲讲LSTM和CRF模型&#xff0c;LSTM&#xff08;长短期记忆&#xff09;是一种特殊的循环神经网络&#xff08;RNN&#xff09;模型&#xff0c;用于处理序列数据、时间序列数据和文本数据等。LSTM通过引入门控机制&#xff0c;解决了传统RNN模型在处理长期依赖关系时的困难…

win10 关闭病毒防护

windows10彻底关闭Windows Defender的4种方法 - 知乎

华为鸿蒙手表开发之动态生成二维码

华为鸿蒙手表开发之动态生成二维码 前言&#xff1a; 最近入职新公司&#xff0c;由于之前的哥们临时离职&#xff0c;走得很突然&#xff0c;所以没有任何交接和文档&#xff0c;临时顶上公司手表应用的上架&#xff0c;更换了新的密钥和key之后重新测试功能和流程&#xff…

from PIL import Image,文字成图,ImageFont import jieba分词,input优雅python绘制图片

开始的代码 import os from PIL import Image, ImageDraw, ImageFont import jiebadef generate_image_with_white_bg(text, font_path, output_path):# 设置图片大小和背景颜色image_width 800image_height 600bg_color (255, 255, 255) # 白色# 创建图片对象image Imag…

正点原子嵌入式linux驱动开发——TF-A初探

上一篇笔记中&#xff0c;正点原子的文档简单讲解了一下什么是TF-A&#xff0c;并且也学习了如何编译TF-A。但是TF-A是如何运行的&#xff0c;它的一个运行流程并未涉及。TF-A的详细运行过程是很复杂的&#xff0c;涉及到很多ARM处理器底层知识&#xff0c;所以这一篇笔记的内容…

竞赛选题 机器视觉人体跌倒检测系统 - opencv python

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 机器视觉人体跌倒检测系统 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xff01; &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&…

654.最大二叉树

力扣题目地址(opens new window) 给定一个不含重复元素的整数数组。一个以此数组构建的最大二叉树定义如下&#xff1a; 二叉树的根是数组中的最大元素。左子树是通过数组中最大值左边部分构造出的最大二叉树。右子树是通过数组中最大值右边部分构造出的最大二叉树。 通过给…

如何在 Windows 上安装 ONLYOFFICE 协作空间社区版

ONLYOFFICE 协作空间是一个在线协作平台&#xff0c;帮助您更好地与客户、业务合作伙伴、承包商及第三方进行文档协作。今天我们来介绍一下&#xff0c;如何在 Windows 上安装协作空间的自托管版。 ONLYOFFICE 协作空间主要功能 使用 ONLYOFFICE 协作空间&#xff0c;您可以&a…

PowerPoint如何设置密码?

PowerPoint&#xff0c;也就是PPT&#xff0c;是很多人工作中经常用的办公软件&#xff0c;而PPT和Word、Excel等一样可以设置密码保护。 PPT可以设置两种密码&#xff0c;一种是“打开密码”&#xff0c;也就是需要密码才能打开PPT&#xff1b;还有一种是设置成有密码的“只读…