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.rhkb.cn/news/151029.html

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

相关文章

京东数据分析平台:9月中上旬白酒消费市场数据分析

9月份&#xff0c;围绕白酒的热点不断。9月5日&#xff0c;瑞幸咖啡官微发布消息称&#xff0c;瑞幸与贵州茅台跨界合作推出的酱香拿铁刷新单品纪录&#xff0c;首日销量突破542万杯&#xff0c;销售额破1亿元。9月14日&#xff0c;贵州茅台官微发布消息称与德芙推出联名产品“…

【MySQL】基本查询(三)聚合函数+group by

文章目录 一. 聚合函数二. group by子句结束语 建立如下表 //创建表结构 mysql> create table exam_result(-> id int unsigned primary key auto_increment,-> name varchar(20) not null comment 同学姓名,-> chinese float default 0.0 comment 语文成绩,->…

NPM 常用命令(九)

目录 1、npm link 1.1 使用语法 1.2 描述 2、npm login 2.1 描述 3、npm logout 3.1 描述 4、npm ls 4.1 使用语法 4.2 描述 5、npm org 5.1 使用语法 5.2 示例&#xff1a; 6、npm outdated 6.1 使用语法 6.2 描述 6.3 示例 7、npm owner 7.1 使用语法 7.2…

BUUCTF-WEB-刷题记录

题目地址 https://buuoj.cn/challenges[HITCON 2017]SSRFme 代码理解 进入主页后发现是代码审计/ escapeshellarg — 把字符串转码为可以在 shell 命令里使用的参数— 抑制错误输出 mkdir — 创建目录 chdir 更改目录 shell_exec — 通过 shell 环境执行命令&#x…

关于JDK于JRE路径配置问题

今天在配置tomcat时发现&#xff0c;无法找到jre的路径&#xff0c;在网上找了半天&#xff0c;才知道&#xff0c;JDK11版本之后&#xff0c;jre的路径默认和JDK路径一致&#xff0c;JDK11之后的文件夹中不再包含jre文件夹&#xff0c;由此在配置JRE环境变量时&#xff0c;只需…

多个excel合并

目的&#xff1a;将同一个文件下的多个 “京东差评.xlsx” 合并为一个&#xff1a;“京东汇总.xlsx" 代码如下&#xff1a; # -*- coding: utf-8 -*- """ Created on Wed Oct 4 12:52:32 2023author: 64884 """import pandas as pd impor…

2023年中国喷头受益于技术创新,功能不断提升[图]

喷头行业是一个专注于生产和供应各种类型喷头的产业。喷头是一种用于将液体、气体或粉末等物质喷射或喷洒的装置&#xff0c;广泛应用于不同领域&#xff0c;包括工业、农业、家用、医疗等。 喷头行业分类 资料来源&#xff1a;共研产业咨询&#xff08;共研网&#xff09; 随…

stm32之手动创建keil工程--HAL库

用CubeMx创建了好多stm32的工程&#xff0c;这里记录下手动创建keil工程的过程。完整工程在最后。 一、准备工作 1.1、下载对应的HAL库&#xff0c; 这里使用的是stm32f103c8t6, 下载地址stm32HAL库 在页面中输入对应型号点击进行二级页面进行下载 下载的之后的目录如下&am…

阿里云ECS服务器无法发送邮件问题解决方案

这篇文章分享一下自己把项目部署在阿里云ECS上之后&#xff0c;登录邮件提醒时的邮件发送失败问题&#xff0c;无法连接发送邮箱的服务器。 博主使用的springboot提供的发送邮件服务&#xff0c;如下所示&#xff0c;为了实现异步的效果&#xff0c;新开了一个线程来发送邮件。…

蔡司光学:儿童近视眼镜的匠心之选

如今我们正处于“信息爆炸”的时代&#xff0c;生活的方方面面都离不开手机、平板和电脑等各种电子设备&#xff0c;加上不正确的用眼习惯&#xff0c;也使青少年及儿童的近视率呈现逐年攀升的态势&#xff0c;为了及时预防儿童近视&#xff0c;业内著名眼视光品牌蔡司光学积极…

基于复旦微JFM7K325T FPGA的高性能PCIe总线数据预处理载板(100%国产化)

PCIE711是一款基于PCIE总线架构的高性能数据预处理FMC载板&#xff0c;板卡采用复旦微的JFM7K325T FPGA作为实时处理器&#xff0c;实现各个接口之间的互联。该板卡可以实现100%国产化。 板卡具有1个FMC&#xff08;HPC&#xff09;接口&#xff0c;1路PCIe x8主机接口&#x…

Linux安装 spark 教程详解

目录 一 准备安装包 二 安装 scala 三 修改配置文件 1&#xff09;修改 workers 文件 2&#xff09;修改 spark-env.sh文件 四 进入 spark 交互式平台 一 准备安装包 可以自行去 spark 官网下载想要的版本 这里准备了 spark3.1.2的网盘资源 链接: https://pan.baidu.com…

OpenCV 13(模版匹配和霍夫变换)

一、模版匹配 所谓的模板匹配&#xff0c;就是在给定的图片中查找和模板最相似的区域&#xff0c;该算法的输入包括模板和图片&#xff0c;整个任务的思路就是按照滑窗的思路不断的移动模板图片&#xff0c;计算其与图像中对应区域的匹配度&#xff0c;最终将匹配度最高的区域…

html 高性能 简易轮播图

目标 实现简易轮播图动画效果 设计理念 无论有多少个轮播图&#xff0c;仅使用常数个轮播图tab&#xff0c;通过js替换更新dom内容&#xff0c;实现性能优化&#xff1b;使用bfc避免回流&#xff0c;&#xff08;重绘是基本上无法避免&#xff0c;不在考虑&#xff09;&#…

C++——多态底层原理

虚函数表 先来看这个问题&#xff1a; class Base { public: virtual void Func1() { cout << "Func1()" << endl; } private: int _b 1; }; sizeof(Base)是多少&#xff1f; 答案是&#xff1a;8 因为Base中除了成员变量_b,还有一个虚函数表_vfp…

【WinRAR】去除请购买WinRAR许可

新建rarreg.key文件 在WinRAR安装目录新建rarreg.key文件&#xff0c;文件内容如下: RAR registration datawncnUnlimited Company LicenseUID1b064ef8b57de3ae9b5264122122509b52e35fd885373b214a4a64cc2fc1284b77ed14fa2066ebfca6509f9813b32960fce6cb5ffde62890079861be57…

JDBC-day02(使用PreparedStatement实现CRUD操作)

所需的数据库数据要导入到自己的数据库库中 三&#xff1a;使用PreparedStatement实现CRUD操作 数据库连接被用于向数据库服务器发送命令和 SQL 语句&#xff0c;并接受数据库服务器返回的结果。其实一个数据库连接就是一个Socket连接。CRUD操作&#xff1a;根据返回值的有无…

HDLbits: Edgedetect

module top_module (input clk,input [7:0] in,output [7:0] pedge );reg [7:0] in_old;always(posedge clk)beginin_old < in; end assign pedge < in & ~in_old; endmodule 对于边缘检测而言&#xff0c;若是0→1和1→0都检测则为in^in_old&#xf…

java实验(头歌)--面向对象封装继承和多态

文章目录 第一题第二题第三题第四题第五题第六题第七题第八题 快速完成实验的方法&#xff1a; 把对应题目的主函数替换&#xff0c;其他复制粘贴。 第一题 public class TestPersonDemo {public static void main(String[] args) {/********* begin *********/// 声明并实例化…

练[FBCTF2019]RCEService

[FBCTF2019]RCEService 文章目录 [FBCTF2019]RCEService掌握知识解题思路关键paylaod 掌握知识 ​ json字符串格式&#xff0c;命令失效(修改环境变量)–绝对路径使用linux命令&#xff0c;%0a绕过preg_match函数&#xff0c;代码审计 解题思路 打开题目链接&#xff0c;发现…