SpringCloud Gateway API接口安全设计(加密 签名)
Posted 你携秋月揽星河丶
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringCloud Gateway API接口安全设计(加密 签名)相关的知识,希望对你有一定的参考价值。
1 防止数据抓包窃取
1.1 风险简述
简述:当用户登录时,恶意攻击者可以用抓包工具可以拿到用户提交的表单信息,可以获取用户的账号密码,进而可以恶意访问网站。
1.2 RSA 非对称加密
1.2.1 RSA简介
RSA加密算法是一种非对称加密算法。在公开密钥加密和电子商业中RSA被广泛使用。RSA是1977年由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)一起提出的。当时他们三人都在麻省理工学院工作。RSA就是他们三人姓氏开头字母拼在一起组成的。
1973年,在英国政府通讯总部工作的数学家克利福德·柯克斯(Clifford Cocks)在一个内部文件中提出了一个相同的算法,但他的发现被列入机密,一直到1997年才被发表。对极大整数做因数分解的难度决定了RSA算法的可靠性。换言之,对一极大整数做因数分解愈困难,RSA算法愈可靠。假如有人找到一种快速因数分解的算法的话,那么用RSA加密的信息的可靠性就肯定会极度下降。但找到这样的算法的可能性是非常小的。今天只有短的RSA钥匙才可能被强力方式解破。到目前为止,世界上还没有任何可靠的攻击RSA算法的方式。只要其钥匙的长度足够长,用RSA加密的信息实际上是不能被解破的。
1983年麻省理工学院在美国为RSA算法申请了专利。这个专利2000年9月21日失效。由于该算法在申请专利前就已经被发表了,在世界上大多数其它地区这个专利权不被承认。
1.2.2 RSA应用过程
非对称算法的在应用的过程如下:
(1) 接收方生成公钥和私钥,公钥公开,私钥保留;
(2) 发送方将要发送的消息采用公钥加密,得到密文,然后将密文发送给接收方;
(3) 接收方收到密文后,用自己的私钥进行解密,获得明文。
1.2.3 RSA工具类
package com.demo.utils;
import com.demo.excepiton.RsaException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.SecureRandom;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
@Slf4j
public class RSAUtils {
public static final String PUBLIC_KEY = "public_key";
public static final String PRIVATE_KEY = "private_key";
public static Map<String, String> generateRasKey() {
Map<String, String> rs = new HashMap<>();
try {
// KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象
KeyPairGenerator keyPairGen = null;
keyPairGen = KeyPairGenerator.getInstance("RSA");
keyPairGen.initialize(1024, new SecureRandom());
// 生成一个密钥对,保存在keyPair中
KeyPair keyPair = keyPairGen.generateKeyPair();
// 得到私钥 公钥
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
String publicKeyString = new String(Base64.encodeBase64(publicKey.getEncoded()));
// 得到私钥字符串
String privateKeyString = new String(Base64.encodeBase64((privateKey.getEncoded())));
// 将公钥和私钥保存到Map
rs.put(PUBLIC_KEY, publicKeyString);
rs.put(PRIVATE_KEY, privateKeyString);
} catch (Exception e) {
log.error("RsaUtils invoke genKeyPair failed.", e);
throw new RsaException("RsaUtils invoke genKeyPair failed.");
}
return rs;
}
public static String encrypt(String str, String publicKey) {
try {
//base64编码的公钥
byte[] decoded = Base64.decodeBase64(publicKey);
RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
//RSA加密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
return Base64.encodeBase64String(cipher.doFinal(str.getBytes(StandardCharsets.UTF_8)));
} catch (Exception e) {
log.error("RsaUtils invoke encrypt failed.", e);
throw new RsaException("RsaUtils invoke encrypt failed.");
}
}
public static String decrypt(String str, String privateKey) {
try {
//64位解码加密后的字符串
byte[] inputByte = Base64.decodeBase64(str.getBytes(StandardCharsets.UTF_8));
//base64编码的私钥
byte[] decoded = Base64.decodeBase64(privateKey);
RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
//RSA解密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, priKey);
return new String(cipher.doFinal(inputByte));
} catch (Exception e) {
log.error("RsaUtils invoke decrypt failed.", e);
throw new RsaException("RsaUtils invoke decrypt failed.");
}
}
}
RsaException: 是自定义异常
@Getter
public class RsaException extends RuntimeException {
private final String message;
public RsaException(String message) {
this.message = message;
}
}
1.2.4 UT
package com.rosh;
import com.alibaba.fastjson.JSONObject;
import com.demo.utils.RSAUtils;
import org.junit.Test;
import java.util.Map;
/**
* @Description:
* @Author: rosh
* @Date: 2021/10/25 22:30
*/
public class RsaTest {
/**
* 用测试生成的公钥,私钥赋值
*/
private static final String PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCFtTlL61IqIGd+fRLUhJ0MjsqFXFJswCohJ45m51WvbxDPRP3gllW0WChk74D5JEOpMDSWo4C7RfoGlBRNW7kQ6qYGukYZ5jgYpzoT0+gp3on96fQXEyQJysv9xiTPIdmSXXVVj1HAOJw29RbzxIVKUSzzPXvEtXRTtCC1+wkAJQIDAQAB";
private static final String PRIVATE_KEY = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAIW1OUvrUiogZ359EtSEnQyOyoVcUmzAKiEnjmbnVa9vEM9E/eCWVbRYKGTvgPkkQ6kwNJajgLtF+gaUFE1buRDqpga6RhnmOBinOhPT6Cneif3p9BcTJAnKy/3GJM8h2ZJddVWPUcA4nDb1FvPEhUpRLPM9e8S1dFO0ILX7CQAlAgMBAAECgYBC4amtbiKFa/wY61tV7pfYRjzLhKi+OUlZmD3E/4Z+4KGZ7DrJ8qkgMtDR3HO5LAikQrare1HTW2d7juqw32ascu+uDObf4yrYNKin+ZDLUYvIDfLhThPxnZJwQ/trdtfxO3VM//XbwZacmwYbAsYW/3QPUXwwOPAgbC2oth8kqQJBANKLyXcdjZx4cwJVl7xNeC847su8y6bPpcBASsaQloCIPiNBIg1h76dpfEGIQBYWJWbBsxtHe/MhOmz7fNFDS2sCQQCiktYZR0dZNH4eNX329LoRuBiltpr9tf36rVOlKr1GSHkLYEHF2qtyXV2mdrY8ZWpvuo3qm1oSLaqmop2rN9avAkBHk85B+IIUF77BpGeZVJzvMOO9z8lMRHuNCE5jgvQnbinxwkrZUdovh+T+QlvHJnBApslFFOBGn51FP5oHamFRAkEAmwZmPsinkrrpoKjlqz6GyCrC5hKRDWoj/IyXfKKaxpCJTH3HeoIghvfdO8Vr1X/n1Q8SESt+4mLFngznSMQAZQJBAJx07bCFYbA2IocfFV5LTEYTIiUeKdue2NP2yWqZ/+tB5H7jNwQTJmX1mn0W/sZm4+nJM7SjfETpNZhH49+rV6U=";
/**
* 生成公钥私钥
*/
@Test
public void generateRsaKey() {
Map<String, String> map = RSAUtils.generateRasKey();
System.out.println("随机生成的公钥为:" + map.get(RSAUtils.PUBLIC_KEY));
System.out.println("随机生成的私钥为:" + map.get(RSAUtils.PRIVATE_KEY));
}
/**
* 加密: Yeidauky/iN1/whevov2+ntzXJKAp2AHfESu5ixnDqH5iB7ww+TcfqJpDfkPHfb12Y0sVXw0gBHNJ4inkh7l2/SJBze3pKQU/mg3oyDokTia3JZIs+e80/iJcSfN+yA1JaqY+eJPYiBiOGAF2S6x0ynvJg/Wj0fwp2Tq3PDzRMo=
*/
@Test
public void testEncrypt() {
JSONObject jsonObject = new JSONObject();
jsonObject.put("username", "rosh");
jsonObject.put("password", "123456");
String str = jsonObject.toJSONString();
String encrypt = RSAUtils.encrypt(str, PUBLIC_KEY);
System.out.println(encrypt);
}
@Test
public void testDecrypt() {
String decrypt = RSAUtils.decrypt("Yeidauky/iN1/whevov2+ntzXJKAp2AHfESu5ixnDqH5iB7ww+TcfqJpDfkPHfb12Y0sVXw0gBHNJ4inkh7l2/SJBze3pKQU/mg3oyDokTia3JZIs+e80/iJcSfN+yA1JaqY+eJPYiBiOGAF2S6x0ynvJg/Wj0fwp2Tq3PDzRMo=",
PRIVATE_KEY);
System.out.println(decrypt);
}
}
1.3 案例
SpringCloud Gateway + SpringBoot + Nacos+redis
1.3.1 前端登录代码
后端把公钥跟前端约定好:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>
<h1>登录</h1>
<from id="from">
账号:<input id="username" type="text"/>
<br/>
密码:<input id="password" type="password"/>
<br/>
<input id="btn_login" type="button" value="登录"/>
</from>
<script src="js/jquery.min.js"></script>
<script src="js/jsencrypt.js"></script>
<script type="text/javascript">
var encrypt = new JSEncrypt();
encrypt.setPublicKey("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCFtTlL61IqIGd+fRLUhJ0MjsqFXFJswCohJ45m51WvbxDPRP3gllW0WChk74D5JEOpMDSWo4C7RfoGlBRNW7kQ6qYGukYZ5jgYpzoT0+gp3on96fQXEyQJysv9xiTPIdmSXXVVj1HAOJw29RbzxIVKUSzzPXvEtXRTtCC1+wkAJQIDAQAB");
$("#btn_login").click(function () {
const username = $("#username").val();
const password = $("#password").val();
const form = {};
form.username = username;
form.password = password;
$.ajax({
url: "http://localhost:9000/api/user/login",
data: encrypt.encrypt(JSON.stringify(form)),
type: "POST",
dataType: "json",
contentType: "application/json;charset=utf-8",
success: function (data) {
console.log(data);
}
});
})
</script>
</body>
</html>
1.3.2 前端查询代码
设定公钥、token,token是登录成功后返回的值
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>查询测试</title>
</head>
<body>
id:<input id="id_txt" type="text"/>
<input id="btn_search" type="button" value="查询"/>
<script src="js/jquery.min.js"></script>
<script src="js/jsencrypt.js"></script>
<script type="text/javascript">
var encrypt = new JSEncrypt();
encrypt.setPublicKey("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCFtTlL61IqIGd+fRLUhJ0MjsqFXFJswCohJ45m51WvbxDPRP3gllW0WChk74D5JEOpMDSWo4C7RfoGlBRNW7kQ6qYGukYZ5jgYpzoT0+gp3on96fQXEyQJysv9xiTPIdmSXXVVj1HAOJw29RbzxIVKUSzzPXvEtXRTtCC1+wkAJQIDAQAB");
$("#btn_search").click(function () {
const id = $("#id_txt").val();
const param = "id=" + id + "&requestId=" + getUuid();
encrypt.encrypt(param);
const url = "http://localhost:9000/api/user/detail?param=" + encrypt.encrypt(param);
$.ajax({
url: url,
beforeSend: function (XMLHttpRequest) {
XMLHttpRequest.setRequestHeader("token", "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIzYzE1ODczYS1iMGUxLTQyNzctYTRjOS1kYTMwNjdiYmE0NWIiLCJpYXQiOjE2MzUzMDYwMDAsInN1YiI6IntcInBhc3N3b3JkXCI6XCIxMjM0NTZcIixcInVzZXJJZFwiOjEsXCJ1c2VybmFtZVwiOlwiYWRtaW5cIn0iLCJleHAiOjE2MzU1NjUyMDB9.fIQi_cV2ZMszBVFV4GoIpGhCSENQKrDi8DsbArk7mGk");
},
type: "GET",
success: function (data) {
console.log(data);
}
});
});
function getUuid() {
var s = [];
var hexDigits = "0123456789abcdef";
for (var i = 0; i < 32; i++) {
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
}
s[14] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
s[8] = s[13] = s[18] = s[23];
var uuid = s.join("");
return uuid;
}
</script>
</body>
</html>
1.3.3 GatewayFilterConfig
解密前端传来的参数并修改传参
package com.demo.gateway.config;
import com.demo.constant.UserConstant;
import com.demo.excepiton.RSAException;
import com.demo.utils.RSAUtils;
import com.demo.utils.TokenUtils;
import io.jsonwebtoken.Claims;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ReactiveHttpOutputMessage;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import<以上是关于SpringCloud Gateway API接口安全设计(加密 签名)的主要内容,如果未能解决你的问题,请参考以下文章
Spring Cloud 与微服务学习总结(17)—— SpringCloud Gateway API 接口安全设计(加密 签名安全)
SpringCloud技术专题「入门实战」微服务网关服务的Gateway全流程开发实践指南
Gateway实战:SpringCloud-Gateway组件使用
深入浅出SpringCloud原理及实战「SpringCloud-Gateway系列」微服务API网关服务的Gateway全流程开发实践指南(入门篇)