对于项目中的敏感数据处理,很多同学刚开始接触的时候,往往觉得比较繁琐,项目中实现的方式也是千奇百怪,基本就是百度搜索和粘贴。
对于敏感数据的处理,是任何一家互联网公司必须要注重的事情,用户的敏感信息泄露带来的危害也是十分严重的。在下不才,结合多年积累,整理了一款开箱即用的加解密解决方案,大致功能如下:
- RSA 加解密,支持分段加密,解决加密文本超长的问题
- DES 加解密,支持多种模式 EBC、CBC(分组加密、迭代分组加密),增加盐混淆策略,避免 DES密文被抓包破解
- Spring 单体应用下动态解密
- Spring Cloud Gateway 微服务场景下网关动态解密
针对以上功能,大体是笔者目前需要解决敏感数据传输几个常见场景。
文章目录
手机用户请
横屏
获取最佳阅读体验,REFERENCES
中是本文参考的链接,如需要链接和更多资源,可以加入『知识星球』获取长期知识分享服务。
使用方式
引入依赖
## 单体应用(基于 Spring Webmvc)
pub.dsb.framework.boot:dsb-boot-api-starter:0.0.5.RELEASE
## 网关(默认集成 spring-cloud-starter-gateway)
pub.dsb.framework.boot:dsb-boot-gateway-starter:0.0.5.RELEASE
RSA 加解密
密钥对生成
/**
* rsa 密钥对生成
* @throws Exception
*/
@Test
public void createKeyPair() throws Exception {
KeyPair keyPair = RsaAssistant.generateKeyPair();
System.out.println(Base64Utils.encodeToString(keyPair.getPublic().getEncoded()));
System.out.println(Base64Utils.encodeToString((keyPair.getPrivate().getEncoded())));
}
//生成密钥对后 Base64 编码,避免乱码问题
加解密工具类测试
/**
* RSA 加解密测试(先生成密钥对,然后公钥用来加密、私钥用来解密)
* @throws Exception
*/
@Test
public void decryptTest() throws Exception {
String encrypt = RsaAssistant.encrypt("架构探险之道@Yiyuery", rsaPubKey);
System.out.println(RsaAssistant.decrypt(encrypt,rsaPriKey));
}
//日志:
//架构探险之道@Yiyuery
//BUILD SUCCESSFUL in 1s
Spring 切面动态解密
-
yaml
配置
dsb:
security:
decrypt:
charset: UTF-8
show-log: true
rsa-config:
public-key: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtRofNeIZ719pzib6f57OlTRVtzH5BTnIRjdkAPm21HRaiu1DzqJlfVUxyvLhyfevzlN0zv+iUnrOJlNniycyM+NCpCnPpdnzNFPb/ZaPwi8r0klQN2SY5eTUQAAygtaFiD2P1Ojc4iXSZeQf6cDEhr45o1y9LoagMRwlSxmHrhXcP+7IB4gWNOQXDgj7yCaheJez+ICm8qxSqINkSjZKPgB+lchoOH4o17rHsjSzq2eNcxAjtIxL1aIchwM6Pvxm83hsti59jHLycHIxHnK1a0RJBBAeS2Ev7CoQ9Fa+/jYe5D7LEUersNfVGImjQzHJFSKO705qt8Ympnk2fvxvywIDAQAB
private-key: MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC1Gh814hnvX2nOJvp/ns6VNFW3MfkFOchGN2QA+bbUdFqK7UPOomV9VTHK8uHJ96/OU3TO/6JSes4mU2eLJzIz40KkKc+l2fM0U9v9lo/CLyvSSVA3ZJjl5NRAADKC1oWIPY/U6NziJdJl5B/pwMSGvjmjXL0uhqAxHCVLGYeuFdw/7sgHiBY05BcOCPvIJqF4l7P4gKbyrFKog2RKNko+AH6VyGg4fijXuseyNLOrZ41zECO0jEvVohyHAzo+/GbzeGy2Ln2McvJwcjEecrVrREkEEB5LYS/sKhD0Vr7+Nh7kP***R6uw19UYiaNDMckVIo7vTmq3xiameTZ+/G/LAgMBAAECggEAX0T6njHvSsl6s4Q1yuUT79G0NccIJQOco7OH3CuBTopXBzaBsTYlBaXHp+fVd5Xg2j10+V/pWFJaGDdQBRf9hOZMrGeCYNEi66gh1mlZ/uEpwFno5Pr6pBWYwoJYEBQh8uXPwEUvzZfv8sHrN+C8gdWYJKQosU0JAEy6IaOwiJb8Soc1aaltzDD1bzEsxjJVxmMAmV3ChO/2sSaN6lsa0AzYIZ/J4oSxdWwNIsI+ZuzhO+ytLx9DK2iIs9ech/6ocQxUlP+vNWMZ3E7nCDwSAS1D2xHHx5RvqclDgswzWXzsDkzPyECf7vSaCQGOLLtEmvQIAsA/4j/rQc4b6szoYQKBgQDm4IptZghxKtfFD+uCTGqzbj5JAYpRpbXw2i4hbEITs7MdHwq2taWQD1LVIgNbT/UyPZOhdF1mX3B4OMwlZT5EyRgEDb+BTeJ88PvGwA+XGXEr12QnbU6uG+A0Ru0+H5JtQreklFDqKuKndShvd/yVTYjVCuLKwYqThD+dzvDSlwKBgQDIzwPiCPVqmo6FYCvSHkTimZDRuvJxcBxnNBUNhAn5zmk9W5gYM/BbwnRYCJQvJvBweC62JCvKUvhBz74SzdaawdIJL7ktzwvnw+PP7PZsP3lvvjpsPS0JU5fopZKvErwCGVaw8zvIFyp8VgoIlyE7kyRw+7wCE/7SxZrEnCOW7QKBgCtNiikirgqrwnSPm9iAhLLKxpvi0hKmRg26nlRefbY8Sif4HoZOY5M1jI+1JXQG9zJJIltx++Krm+iwnnmVF6zHGt4Hxhd2iDhu6opIk6P/fZ5/c6WBdvRo/hBQDUdNnKUpklAoEVUaXhCShNcDZjiKplNvC0KEMn2gnF345mpZAoGBAL2PfpDf+BxcLnIFqRg+7rQiVy1FFxyywn1CEyWhIXGpwnrjfh5K2XklhYKdBpXEYnEpYp8aYiQqUqR9oWZK3W1VzhpR7LMrood0yhc8EBt7h/1OTARlc6A8Q0ihFGkkfEpW9RkxY5utErQw3GPjlsGQU3Q8juw/R+xcEY/L/WS5AoGAT4w1awT1xnMPbo0JwYTP+LTDUGUT1phH6d0fOCYE/Jjt7EB7+VMdANpddv6l29hzWA69qYfEqkvNRwhPYbjXFk4z+sLD7prc/MUdjeJ27+vTg1yXB0PigQpDhmoNNSq2TLgdhrBJxiiBTl3JTW+3bA9Os60LW4p2KXbCsBHHCIk=
- 测试接口定义
/*
* @ProjectName: 编程学习
* @Copyright: 2020 HangZhou Yiyuery Dev, Ltd. All Right Reserved.
* @address: 微信搜索公众号「架构探险之道」获取更多资源。
* @date: 2020/9/6 11:28 上午
* @blog: https://yiyuery.blog.csdn.net/
* @description: 本内容仅限于编程技术学习使用,转发请注明出处.
*/
package pub.dsb.api.controller.decrypt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import pub.dsb.framework.boot.rest.R;
import pub.dsb.framework.boot.security.annotation.Decrypt;
import pub.dsb.framework.boot.security.beans.strategy.RsaDecryptStrategy;
import pub.dsb.framework.boot.security.constants.DecryptTypeEnum;
import java.net.URLDecoder;
import java.net.URLEncoder;
/**
* <p>
* 加解密功能测试
* </p>
*
* @author Yiyuery
* @date 2020/9/6 11:28 上午
*/
@RequestMapping("/decrypt")
@RestController()
public class DecryptTestController {
@Autowired
private RsaDecryptStrategy rsaDecryptStrategy;
/**
* 动态解密
* @param value
* @return
*/
@PostMapping("/auto")
@Decrypt(DecryptTypeEnum.RSA)
public R<String> postDecryptAuto(@RequestBody String value){
return R.ok(value);
}
/**
* 手动解密 + URL DECODE
* @param value
* @return
* @throws Exception
*/
@PostMapping("/hand/ud")
public R<String> postDecryptHand(String value) throws Exception {
return R.ok(rsaDecryptStrategy.decrypt(URLDecoder.decode(value).replaceAll(" ","+")));
}
/**
* 手动加密 + URL ENCODE
* @param value
* @return
* @throws Exception
*/
@PostMapping("/hand/ue")
public R<String> postDecryptHandWithUE(String value) throws Exception {
return R.ok(URLEncoder.encode(rsaDecryptStrategy.encrypt(value)));
}
/**
* 手动加密
* @param value
* @return
* @throws Exception
*/
@PostMapping("/encrypt")
public R<String> postEncryptHand(String value) throws Exception {
return R.ok(rsaDecryptStrategy.encrypt(value));
}
}
- 加密测试
POST http://127.0.0.1:8080/decrypt/encrypt?value=Yiyuery%40%E6%9E%B6%E6%9E%84%E6%8E%A2%E9%99%A9%E4%B9%8B%E9%81%93
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sun, 06 Sep 2020 03:49:45 GMT
{
"code": 0,
"msg": "SUCCESS",
"data": "jS363ER6PYmmqB3A0DmimJm0bu6nPhnHKSbUNXMMpArMK5mLUdCxUqMZfCIOVX/qyzQzdEAhHkXSfeTlDyunupgJLP+Rv3jAZ0im7wBOXPQCDN/jaRlbQhhlJSerQVPckgrxXnjbYFjO0FfWZSiKIrnRrMHw1OVSkAAgA8RleT/Za2RWgWEWqIVtYTC4QNCv8yEYak75isazCAklens8tfvYlH91IxOFMptt72cG5CjdnEqDecqU+/PKDQTwdsQLQG++8VVSuiNhLoQ9XpBi0fvFu7YbEr0IeKYw45zMaSPTvGuouD68H1UX2NY28wqPUBnJBjth2iqOgN/WeRQPDA=="
}
- 动态解密验证
POST http://127.0.0.1:8080/decrypt/auto
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sun, 06 Sep 2020 04:15:33 GMT
{
"code": 0,
"msg": "SUCCESS",
"data": "Yiyuery@架构探险之道"
}
- 手动加密
POST http://127.0.0.1:8080/decrypt/hand/ue?value=Yiyuery%40%E6%9E%B6%E6%9E%84%E6%8E%A2%E9%99%A9%E4%B9%8B%E9%81%93
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sun, 06 Sep 2020 04:32:15 GMT
{
"code": 0,
"msg": "SUCCESS",
"data": "d%2Fn16X4GRh%2FflnWwtrriUouP1EMQ9p5J%2BeoIVCJlJoYyr0K8iMp2HtPBp5Q5orSZ2PZr5attbb%2FPOe9N77X87Nj74D5CdnMLTVZvomkb%2FPkpJSXp7jUk2xzMfIHejAXogAs2GLwRf%2Fir50zlzbk1GnC6WwGXacIRqUDc8Kglq3SXLWfimMA5UQ9bGvJFf6uMP6opZlLtkezvEgCE3IxEz4uOgkeCgtE%2FQO3GEP8Mu0izZjEci65LypCELAc%2FIWIz9jjeD9qilUQ3vbaWbu25%2BJ5anqzxusbq4WLi%2BG7rHUuCqb8Sx7OHjpDPJEKxuEAKIoOnzV%2FTQWUKwL2Ms4oI0g%3D%3D"
}
Response code: 200; Time: 20ms; Content length: 406 bytes
- 手动解密
POST http://127.0.0.1:8080/decrypt/hand/ud?value=d%2Fn16X4GRh%2FflnWwtrriUouP1EMQ9p5J%2BeoIVCJlJoYyr0K8iMp2HtPBp5Q5orSZ2PZr5attbb%2FPOe9N77X87Nj74D5CdnMLTVZvomkb%2FPkpJSXp7jUk2xzMfIHejAXogAs2GLwRf%2Fir50zlzbk1GnC6WwGXacIRqUDc8Kglq3SXLWfimMA5UQ9bGvJFf6uMP6opZlLtkezvEgCE3IxEz4uOgkeCgtE%2FQO3GEP8Mu0izZjEci65LypCELAc%2FIWIz9jjeD9qilUQ3vbaWbu25%2BJ5anqzxusbq4WLi%2BG7rHUuCqb8Sx7OHjpDPJEKxuEAKIoOnzV%2FTQWUKwL2Ms4oI0g%3D%3D
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sun, 06 Sep 2020 04:33:03 GMT
{
"code": 0,
"msg": "SUCCESS",
"data": "Yiyuery@架构探险之道"
}
Response code: 200; Time: 36ms; Content length: 50 bytes
通过观察,可以看出来手动加解密针对 Base64 编码后的字符必须做 URLEncode、URLDecode 处理(否则 Spring 框架会因为有特殊字符无法解析而报错)。为了简化动态参数解密的处理逻辑,避免太多的参数注解反而导致了加解密功能的复杂性,建议对于太过灵活的数据结构,直接使用手动加解密配合 URLEncode、URLDecode 使用。如果传输的数据中敏感信息较多,可以直接使用全文加密,然后通过 POST+RSA+BASE64
加密后,后端配置注解 @Decrypt(DecryptTypeEnum.RSA)
,动态解密更为简便。需要留意的是,此处针对的是 JSON 结构的数据,所以需要在参数上添加注解 @RequestBody
。
DES 加解密
DES 的动态加解密和 RSA 比较相似,由于都是敏感数据处理,所以总体的抽象代码设计上是一样的,但是需要注意:
- RSA 是针对密钥对的非对称加解密方式
- DES 是对称加解密方式,所以密钥需要做安全处理
密钥生成
@Test
public void desKey(){
System.out.println(new DesCbcAssistant("DES").generatorRandomKey(200));
}
//1Mk2Cu7Tk3Wm4Ao2Gv6Ff4Au3Lh8Ry7Vr1Tn0Qm5Ci4Rw5Fr6Hr3Ff7Cl8Ye6Sw8Ch7Ij5Xx0Qf3Dk1Ke3Mj7Wd7Fl2Do1Yi8Fq3Ue7Tx3Km0Ie1Jl0Hu6Bq6Yt8Jj2Ip7Ol6It7Vv7Ej3Ll5Bs4Ug4Bf5Im2Yx3Nt6Vm2Lf1Ka4Mn7Jv5Fk0Se4Py2Lr2Qp4Ee2Lp4E
yaml
配置
dsb:
security:
decrypt:
# 配置编码字符集
charset: UTF-8
# 配置解密后是否在日志输出
show-log: true
des-config:
key: 1Mk2Cu7Tk3Wm4Ao2Gv6Ff4Au3Lh8Ry7Vr1Tn0Qm5Ci4Rw5Fr6Hr3Ff7Cl8Ye6Sw8Ch7Ij5Xx0Qf3Dk1Ke3Mj7Wd7Fl2Do1Yi8Fq3Ue7Tx3Km0Ie1Jl0Hu6Bq6Yt8Jj2Ip7Ol6It7Vv7Ej3Ll5Bs4Ug4Bf5Im2Yx3Nt6Vm2Lf1Ka4Mn7Jv5Fk0Se4Py2Lr2Qp4Ee2Lp4E
salt: exampleapp
测试接口定义
/*
* @ProjectName: 编程学习
* @Copyright: 2020 HangZhou Yiyuery Dev, Ltd. All Right Reserved.
* @address: 微信搜索公众号「架构探险之道」获取更多资源。
* @date: 2020/9/6 11:28 上午
* @blog: https://yiyuery.blog.csdn.net/
* @description: 本内容仅限于编程技术学习使用,转发请注明出处.
*/
package pub.dsb.api.controller.decrypt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import pub.dsb.framework.boot.rest.R;
import pub.dsb.framework.boot.security.annotation.Decrypt;
import pub.dsb.framework.boot.security.beans.strategy.DesDecryptStrategy;
import pub.dsb.framework.boot.security.beans.strategy.RsaDecryptStrategy;
import pub.dsb.framework.boot.security.constants.DecryptTypeEnum;
import java.net.URLDecoder;
import java.net.URLEncoder;
/**
* <p>
* 加解密功能测试
* </p>
*
* @author Yiyuery
* @date 2020/9/6 11:28 上午
*/
@RequestMapping("/des")
@RestController()
public class DESTestController {
@Autowired
private DesDecryptStrategy desDecryptStrategy;
/**
* 动态解密
* @param value
* @return
*/
@PostMapping("/auto")
@Decrypt(DecryptTypeEnum.DES)
public R<String> postDecryptAuto(@RequestBody String value){
return R.ok(value);
}
/**
* 手动解密 + URL DECODE
* @param value
* @return
* @throws Exception
*/
@PostMapping("/hand/ud")
public R<String> postDecryptHand(String value) throws Exception {
return R.ok(desDecryptStrategy.decrypt(URLDecoder.decode(value).replaceAll(" ","+")));
}
/**
* 手动加密 + URL ENCODE
* @param value
* @return
* @throws Exception
*/
@PostMapping("/hand/ue")
public R<String> postDecryptHandWithUE(String value) throws Exception {
return R.ok(URLEncoder.encode(desDecryptStrategy.encrypt(value)));
}
/**
* 手动加密
* @param value
* @return
* @throws Exception
*/
@PostMapping("/encrypt")
public R<String> postEncryptHand(String value) throws Exception {
return R.ok(desDecryptStrategy.encrypt(value));
}
}
手动加密
POST http://127.0.0.1:8080/des/encrypt?value=Yiyuery%40%E6%9E%B6%E6%9E%84%E6%8E%A2%E9%99%A9%E4%B9%8B%E9%81%93
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sun, 06 Sep 2020 05:21:52 GMT
{
"code": 0,
"msg": "SUCCESS",
"data": "3tbmXHtXBW/0psMSs8PLGjMvTSQZd1UwFm6TQRkC8nc="
}
Response code: 200; Time: 20ms; Content length: 80 bytes
动态解密
POST http://127.0.0.1:8080/des/auto
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sun, 06 Sep 2020 05:22:25 GMT
{
"code": 0,
"msg": "SUCCESS",
"data": "Yiyuery@架构探险之道"
}
Response code: 200; Time: 13ms; Content length: 50 bytes
与前文提到的 RSA 加解密的特殊字符处理方式一样,对于比较复杂的数据结构,建议结合 URLEncode 转码后再进行接口调用,此处不再进行赘述。
DES 的加解密由于分组加密策略的不同,默认提供了两种实现 EBC、CBC
,可以通过配置 algorithm
进行切换DES | DES/CBC | DES/EBC
# For Example:
dsb:
security:
decrypt:
# 配置编码字符集
charset: UTF-8
# 配置解密后是否在日志输出
show-log: true
des-config:
key: 1Mk2Cu7Tk3Wm4Ao2Gv6Ff4Au3Lh8Ry7Vr1Tn0Qm5Ci4Rw5Fr6Hr3Ff7Cl8Ye6Sw8Ch7Ij5Xx0Qf3Dk1Ke3Mj7Wd7Fl2Do1Yi8Fq3Ue7Tx3Km0Ie1Jl0Hu6Bq6Yt8Jj2Ip7Ol6It7Vv7Ej3Ll5Bs4Ug4Bf5Im2Yx3Nt6Vm2Lf1Ka4Mn7Jv5Fk0Se4Py2Lr2Qp4Ee2Lp4E
salt: dosthbetter
algorithm: DESede/CBC
/**
* encryption algorithm DESede/CBC
*/
public static final String KEY_ALGORITHM_DEFAULT = "DESede/CBC";
//...
/**
* 默认DES加解密辅助类
*/
DES_DEFAULT("DES", new DesDefaultAssistant("DES")),
DES_CBC("DESede/CBC", new DesCbcAssistant("DESede")),
DES_EBC("DESede/ECB", new DesEcbAssistant("DESede"));
密钥获取
由于 RSA、DES 必然都需要暴露给 Client 获取对应的密钥信息。所以框架中默认生成了一个获取的接口定义:
- RSA 公钥获取
GET http://127.0.0.1:8080/security/rsa/key
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sun, 06 Sep 2020 05:31:04 GMT
{
"code": 0,
"msg": "SUCCESS",
"data": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtRofNeIZ719pzib6f57OlTRVtzH5BTnIRjdkAPm21HRaiu1DzqJlfVUxyvLhyfevzlN0zv+iUnrOJlNniycyM+NCpCnPpdnzNFPb/ZaPwi8r0klQN2SY5eTUQAAygtaFiD2P1Ojc4iXSZeQf6cDEhr45o1y9LoagMRwlSxmHrhXcP+7IB4gWNOQXDgj7yCaheJez+ICm8qxSqINkSjZKPgB+lchoOH4o17rHsjSzq2eNcxAjtIxL1aIchwM6Pvxm83hsti59jHLycHIxHnK1a0RJBBAeS2Ev7CoQ9Fa+/jYe5D7LEUersNfVGImjQzHJFSKO705qt8Ympnk2fvxvywIDAQAB"
}
- DES 密钥获取
GET http://127.0.0.1:8080/security/des/key
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sun, 06 Sep 2020 05:31:29 GMT
{
"code": 0,
"msg": "SUCCESS",
"data": "MU1rMkN1N1RrM1dtZDRvQXNvdDJoR2J2ZTZ0RnRmZTRyQXUzTGg4Unk3VnIxVG4wUW01Q2k0Unc1RnI2SHIzRmY3Q2w4WWU2U3c4Q2g3SWo1WHgwUWYzRGsxS2UzTWo3V2Q3RmwyRG8xWWk4RnEzVWU3VHgzS20wSWUxSmwwSHU2QnE2WXQ4SmoySXA3T2w2SXQ3VnY3RWozTGw1QnM0VWc0QmY1SW0yWXgzTnQ2Vm0yTGYxS2E0TW43SnY1RmswU2U0UHkyTHIyUXA0RWUyTHA0RQ=="
}
Response code: 200; Time: 15ms; Content length: 320 bytes
由于 DES 是对称加密的,所以针对密钥需要进行特殊处理,加 salt
进行混淆,避免被破解后直接使用。
默认混淆策略(跳过密钥长度的字符,分别从原文和 salt
中各取一位进行填充):
- 密钥: ABC12345
- salt: 555
- 混淆后:ABC15253545
Tips:
需要注意的是密钥长度必须大于salt 的2倍长度
然后在进行 Base64
加密后返回,这样做的好处是,即便被抓包后获取对应获取 DES 的密钥,尝试 Base64 后的,仍然无法直接进行破解。客户端内部可以保存不同应用的 salt,根据内部的业务规则进行区分,这样,除非客户端被完全破解,其依赖的应用的密文传输才会被破解。
服务端可以通过在不同的服务中设置不同的 salt 来确保各个服务密文传输的安全。
网关动态解密
看了上面的 Spring 场景下的,对于一个微服务集群,往往接口都是通过网关暴露服务出去的,我们可以直接在指定的业务统一网关中设置对应的加解密方式来实现敏感数据处理。话不多说,Show you this code
.
准备工作
两个服务,一个网关,一个服务提供者,网关负责解密后传输给后续的服务。
- 密钥配置统一放在网关上
- 通过 Nacos 来统一管理网关和服务提供者
- 进一步简化接口调用方式,针对服务的请求要求是
Post
并且Header
中包含application/json
网关依赖和配置
- 依赖配置
buildscript {
repositories {
maven { url = "https://plugins.gradle.org/m2/" }
maven { url = "http://maven.aliyun.com/nexus/content/groups/public/" }
jcenter()
}
dependencies {
classpath libs["spring-boot-gradle-plugin"]
}
}
apply plugin: "io.spring.dependency-management"
apply plugin: "org.springframework.boot"
dependencyManagement {
imports {
//spring bom helps us to declare dependencies without specifying version numbers.
mavenBom "org.springframework.cloud:spring-cloud-dependencies:Greenwich.SR6"
mavenBom "com.alibaba.cloud:spring-cloud-alibaba-dependencies:2.1.0.RELEASE"
}
}
dependencies {
compile libs["spring-boot-starter-actuator"]
//网关
compile "org.springframework.cloud:spring-cloud-starter-gateway"
//nacos
compile libs["spring-cloud-starter-alibaba-nacos-config"]
compile libs["spring-cloud-starter-alibaba-nacos-discovery"]
//sentinel
compile libs["sentinel-spring-cloud-gateway-adapter"]
compile libs["sentinel-transport-simple-http"]
testCompile "org.springframework.boot:spring-boot-starter-test"
}
- 启动类
@SpringBootApplication
@EnableDiscoveryClient
public class ApiGatewayExampleApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayExampleApplication.class, args);
}
}
- yaml
server:
port: 8080
tomcat:
uri-encoding: UTF-8
dsb:
security:
decrypt:
charset: UTF-8
show-log: true
rsa-config:
public-key: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtRofNeIZ719pzib6f57OlTRVtzH5BTnIRjdkAPm21HRaiu1DzqJlfVUxyvLhyfevzlN0zv+iUnrOJlNniycyM+NCpCnPpdnzNFPb/ZaPwi8r0klQN2SY5eTUQAAygtaFiD2P1Ojc4iXSZeQf6cDEhr45o1y9LoagMRwlSxmHrhXcP+7IB4gWNOQXDgj7yCaheJez+ICm8qxSqINkSjZKPgB+lchoOH4o17rHsjSzq2eNcxAjtIxL1aIchwM6Pvxm83hsti59jHLycHIxHnK1a0RJBBAeS2Ev7CoQ9Fa+/jYe5D7LEUersNfVGImjQzHJFSKO705qt8Ympnk2fvxvywIDAQAB
private-key: MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC1Gh814hnvX2nOJvp/ns6VNFW3MfkFOchGN2QA+bbUdFqK7UPOomV9VTHK8uHJ96/OU3TO/6JSes4mU2eLJzIz40KkKc+l2fM0U9v9lo/CLyvSSVA3ZJjl5NRAADKC1oWIPY/U6NziJdJl5B/pwMSGvjmjXL0uhqAxHCVLGYeuFdw/7sgHiBY05BcOCPvIJqF4l7P4gKbyrFKog2RKNko+AH6VyGg4fijXuseyNLOrZ41zECO0jEvVohyHAzo+/GbzeGy2Ln2McvJwcjEecrVrREkEEB5LYS/sKhD0Vr7+Nh7kP***R6uw19UYiaNDMckVIo7vTmq3xiameTZ+/G/LAgMBAAECggEAX0T6njHvSsl6s4Q1yuUT79G0NccIJQOco7OH3CuBTopXBzaBsTYlBaXHp+fVd5Xg2j10+V/pWFJaGDdQBRf9hOZMrGeCYNEi66gh1mlZ/uEpwFno5Pr6pBWYwoJYEBQh8uXPwEUvzZfv8sHrN+C8gdWYJKQosU0JAEy6IaOwiJb8Soc1aaltzDD1bzEsxjJVxmMAmV3ChO/2sSaN6lsa0AzYIZ/J4oSxdWwNIsI+ZuzhO+ytLx9DK2iIs9ech/6ocQxUlP+vNWMZ3E7nCDwSAS1D2xHHx5RvqclDgswzWXzsDkzPyECf7vSaCQGOLLtEmvQIAsA/4j/rQc4b6szoYQKBgQDm4IptZghxKtfFD+uCTGqzbj5JAYpRpbXw2i4hbEITs7MdHwq2taWQD1LVIgNbT/UyPZOhdF1mX3B4OMwlZT5EyRgEDb+BTeJ88PvGwA+XGXEr12QnbU6uG+A0Ru0+H5JtQreklFDqKuKndShvd/yVTYjVCuLKwYqThD+dzvDSlwKBgQDIzwPiCPVqmo6FYCvSHkTimZDRuvJxcBxnNBUNhAn5zmk9W5gYM/BbwnRYCJQvJvBweC62JCvKUvhBz74SzdaawdIJL7ktzwvnw+PP7PZsP3lvvjpsPS0JU5fopZKvErwCGVaw8zvIFyp8VgoIlyE7kyRw+7wCE/7SxZrEnCOW7QKBgCtNiikirgqrwnSPm9iAhLLKxpvi0hKmRg26nlRefbY8Sif4HoZOY5M1jI+1JXQG9zJJIltx++Krm+iwnnmVF6zHGt4Hxhd2iDhu6opIk6P/fZ5/c6WBdvRo/hBQDUdNnKUpklAoEVUaXhCShNcDZjiKplNvC0KEMn2gnF345mpZAoGBAL2PfpDf+BxcLnIFqRg+7rQiVy1FFxyywn1CEyWhIXGpwnrjfh5K2XklhYKdBpXEYnEpYp8aYiQqUqR9oWZK3W1VzhpR7LMrood0yhc8EBt7h/1OTARlc6A8Q0ihFGkkfEpW9RkxY5utErQw3GPjlsGQU3Q8juw/R+xcEY/L/WS5AoGAT4w1awT1xnMPbo0JwYTP+LTDUGUT1phH6d0fOCYE/Jjt7EB7+VMdANpddv6l29hzWA69qYfEqkvNRwhPYbjXFk4z+sLD7prc/MUdjeJ27+vTg1yXB0PigQpDhmoNNSq2TLgdhrBJxiiBTl3JTW+3bA9Os60LW4p2KXbCsBHHCIk=
des-config:
key: 1Mk2Cu7Tk3Wm4Ao2Gv6Ff4Au3Lh8Ry7Vr1Tn0Qm5Ci4Rw5Fr6Hr3Ff7Cl8Ye6Sw8Ch7Ij5Xx0Qf3Dk1Ke3Mj7Wd7Fl2Do1Yi8Fq3Ue7Tx3Km0Ie1Jl0Hu6Bq6Yt8Jj2Ip7Ol6It7Vv7Ej3Ll5Bs4Ug4Bf5Im2Yx3Nt6Vm2Lf1Ka4Mn7Jv5Fk0Se4Py2Lr2Qp4Ee2Lp4E
salt: dosthbetter
algorithm: DESede/CBC
spring:
application:
name: dsb-boot-gateway-starter-example
cloud:
gateway:
enabled: true
discovery:
locator:
lower-case-service-id: true
# 测出配置了两种路由规则,分别处理 DES 和 RSA 敏感数据处理
routes:
- id: rsa-route
uri: lb://spring-cloud-provider-example
predicates:
- Path=/api/rsa/**
filters:
- StripPrefix=2
- name: Decrypt
args:
# 配置 rsaDecryptRewriteFunction or desDecryptRewriteFunction
rewriteFunction: '#{@rsaDecryptRewriteFunction}'
- id: des-route
uri: lb://spring-cloud-provider-example
predicates:
- Path=/api/des/**
filters:
- StripPrefix=2
- name: Decrypt
args:
# 配置 rsaDecryptRewriteFunction or desDecryptRewriteFunction
rewriteFunction: '#{@desDecryptRewriteFunction}'
logging:
level:
root: debug
-
nacos
所在配置bootstrap.yaml
# nacos 配置 (nacos config必须配置在bootstrap)
spring:
cloud:
nacos:
discovery:
server-addr: ${NACOS_SERVER_ADDR:127.0.0.1:8848}
namespace: ${NACOS_NAMESPACE:dsb-cloud}
metadata:
{"checkSum": "${random.value}-${random.uuid}"}
config:
server-addr: ${NACOS_SERVER_ADDR:127.0.0.1:8848}
namespace: ${NACOS_NAMESPACE:dsb-cloud}
file-extension: yaml
定义一个简单的接受参数的服务消费者
@SpringBootApplication
@EnableDiscoveryClient
public class SpringCloudProviderApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudProviderApplication.class, args);
}
}
//接口定义
@RestController
public class EchoController {
@PostMapping("/decrypt")
public R<String> data(@RequestBody String value) {
return R.ok(value);
}
}
- yaml 中配置对应的 Nacos 注册信息
# nacos 配置 (nacos config必须配置在bootstrap)
spring:
cloud:
nacos:
discovery:
server-addr: ${NACOS_SERVER_ADDR:127.0.0.1:8848}
namespace: ${NACOS_NAMESPACE:dsb-cloud}
metadata:
{"checkSum": "${random.value}-${random.uuid}"}
config:
server-addr: ${NACOS_SERVER_ADDR:127.0.0.1:8848}
namespace: ${NACOS_NAMESPACE:dsb-cloud}
file-extension: yaml
服务注册
接口测试
- RSA 敏感数据处理
## http
POST http://127.0.0.1:8081/api/rsa/decrypt
Content-Type: application/json
Ojn4Z8vgmFqUB6xOFoz1fOJz+TuoviPceUtRLsBOYpL7xcpv/UlLsF+scDU+8JEIMM2kOVybcTHQ55jgcvhU1H6nKSKlkawTP82mHO8FiRXX9YTg/eZd3di2/VLVp8WM8EwxabFmZCuyaPNpkhLk94TcM2RDffdtw3PB4PDrKrM1EXDKWEtLSMAuKuECfBo8vSkk3Zl7dbXkfSyI0ritLCXaNoIPdnB8BTjosaC7/RXtlxvUxNG3jT2nLRoQcWWODGxDmX6P/HaHvs1WAUTAZ5GG+maDtVxK2lSQFPw2lYzAlD62CeaZST2RKXuuNK+SvIHUPMR9SpqlR9oeYZcxAw==
## 结果
POST http://127.0.0.1:8081/api/rsa/decrypt
HTTP/1.1 200 OK
transfer-encoding: chunked
Content-Type: application/json;charset=UTF-8
Date: Sun, 06 Sep 2020 15:07:49 GMT
{
"code": 0,
"msg": "SUCCESS",
"data": "Yiyuery@架构探险之道"
}
Response code: 200 (OK); Time: 38ms; Content length: 50 bytes
- DES 敏感数据处理
## http
POST http://127.0.0.1:8081/api/des/decrypt
Content-Type: application/json
3tbmXHtXBW/0psMSs8PLGjMvTSQZd1UwFm6TQRkC8nc=
## 结果
POST http://127.0.0.1:8081/api/des/decrypt
HTTP/1.1 200 OK
transfer-encoding: chunked
Content-Type: application/json;charset=UTF-8
Date: Sun, 06 Sep 2020 15:08:21 GMT
{
"code": 0,
"msg": "SUCCESS",
"data": "Yiyuery@架构探险之道"
}
Response code: 200 (OK); Time: 32ms; Content length: 50 bytes
可以看到都成功解密并返回了明文。
实现原理
单体应用
- 依赖
RequestBodyAdvice
切面截取数据,结合方法注解选择对应方式并处理
微服务网关应用
- 依赖
AbstractGatewayFilterFactory
进行扩展,拦截 Post 请求,并处理数据
最后强调下依赖:
## 单体应用(基于 Spring Webmvc)
pub.dsb.framework.boot:dsb-boot-api-starter:0.0.5.RELEASE
## 网关(默认集成 spring-cloud-starter-gateway)
pub.dsb.framework.boot:dsb-boot-gateway-starter:0.0.5.RELEASE
补充说明:
- 网关简化了调用的解密方式,只针对 Post 和 Header 包含 application/json 的进行处理
-
DSB
框架默认提供了DES
和RSA
的加密策略类注入,可以在任何位置通过DI(依赖注入)
使用
小结
希望这个加解密工具可以帮助到看到此文的同学们,如果觉得还不错的话,右下角点个再看哦,谢谢啦!