百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

SpringBoot 接口加解密全过程详解

csdh11 2025-02-26 11:41 5 浏览

一、接口为什么要加密

接口加密传输,主要作用:

  • 敏感数据防止泄漏、
  • 保护隐私、
  • 防伪装攻击、
  • 防篡改攻击、
  • 防重放攻击
  • 等等…
  • 4个字概括:保护数据!

当然不是说接口加密后,就能完完全全的保护我们的数据,但至少能防一部分人拿到我们的数据。
而且接口加密感觉逼格是不是高过一点!!!

二、加密思路

1、加密简介

加密算法有很多,在能加密又能解密的算法可分为:

  • 非对称加密算法,常见:RSADSAECC
    特点:算法复杂,加解密速度慢,但安全性高,一般与对称加密结合使用(对称加密对内容加密,非对称对对称所使用的密钥加密)
  • 对称加密算法,常见:DES3DESAESBlowfishIDEARC5RC6
    特点:加密解密效率高,速度快,适合进行大数据量的加解密

2、加密流程

思路:
假设现在客户端是A,服务端是B,现在A要去B请求接口

  • 1、A要向B发送信息,A和B都要产生一对用于加密的非对称加密公私钥(AB各自生成自己的公私钥)
  • 2、A的私钥保密,A的公钥告诉B;B的私钥保密,B的公钥告诉A。(AB互换公钥)
  • 3、A要给B发送信息时,A用B的公钥加密信息,因为A知道B的公钥。(公钥加密只有私钥能解)
  • 4、A将这个消息发给B(已经用B的公钥加密消息)。
  • 5、B收到这个消息后,B用自己的私钥解密A的消息。其他人收到这个报文都无法解密,因为只有B才有B的私钥。

虽然这样就实现了接口的加密方式,但是呢,非对称加密的加解密速度相比对称加密速度很慢,当传输的数据很大时就更加明显了。
所以我们对称与非对称一起用,理解上面的流程之后,我们在其基础稍微改下:

  • 在A给B发信息的时候,随机生成一个对称加密的密钥,然后用刚生成的密钥加密信息,然后用B的公钥加密刚生成的对称密钥。
  • A把加密的两个信息发送给B。B收到数据之后,先用自己的私钥解开得到对称密钥,然后再用解开的对称密钥解开对称加密的信息,最终得到A传来的信息。

三、代码实现

  • 在当下Java还是SpringBoot为主流框架工作面试必备,今天还是以它来举例。
  • 加解密代码怎么写,这个时候网上已经有很多现成的库了,不用我们操心,我们想的是如何在接口加解密的时候不影响我们自己的业务,也就是不用更改我们已经写好的代码。
  • 很多人的第一反应应该就是AOP吧,对的没错可以使用AOP进行环绕增强。也可以使用@ControllerAdvice 对Controller进行增强(本文以它来做为例子)。
  • Spring 提供两个接口RequestBodyAdviceResponseBodyAdvice。实现它们,即可对Controller进行增强,第一个是在controller之前增强,第二个就是对controller 的返回值进行增强。
  • 在spring启动的时候会对RequestMappingHandlerAdapterinitControllerAdviceCache()方法进行初始化。会去把有@ControllerAdvice的类进行注入。

1、自定义类

下面就来实现上面的两个接口实现类代码

EncryptRequestAdvice.java

  • 这个类的功能就是在请求到controller之前就把前端传上来的数据解密好
  • 我们还要校验是否有必要解密
@ControllerAdvice(basePackages = {"top.lrshuai.encrypt.controller"})
public class EncryptRequestAdvice implements RequestBodyAdvice {

    @Autowired
    private KeyConfig keyConfig;

    /**
     * 是否需要解码
     */
    private boolean isDecode;

    @Override
    public boolean supports(MethodParameter methodParameter, Type type, Class> aClass) {
        // 方法或类上有注解
        if (Utils.hasMethodAnnotation(methodParameter,new Class[]{Encrypt.class,Decode.class})) {
            isDecode=true;
            // 这里返回true 才支持
            return true;
        }
        return false;
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class> aClass) throws IOException {
        if(isDecode){
            return new DecodeInputMessage(httpInputMessage, keyConfig);
        }
        return httpInputMessage;
    }

    @Override
    public Object afterBodyRead(Object obj, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class> aClass) {
        // 这里就是已经读取到body了,obj就是
        return obj;
    }

    @Override
    public Object handleEmptyBody(Object obj, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class> aClass) {
        // body 为空的时候调用
        return obj;
    }

}
  • 在上面实现类中需要重写:supports()beforeBodyRead()afterBodyRead()handleEmptyBody() 方法
  • 只有在supports() 返回true 后面的方法才会支持执行。在RequestResponseBodyAdviceChain有判断
  • 我们可以在beforeBodyRead()这个方法进行解密处理。
  • 在上面的代码中,我加了自定义注解,因为可能需求是这样的,有些接口加密有些接口不加密,用自定义注解比较方便。
  • 然后DecodeInputMessage 这个类是自定义实现了HttpInputMessage接口,解码逻辑都在里面。如下:

DecodeInputMessage.java

这个类就是具体的解码逻辑了

public class DecodeInputMessage implements HttpInputMessage {

    private HttpHeaders headers;

    private InputStream body;

    public DecodeInputMessage(HttpInputMessage httpInputMessage, KeyConfig keyConfig) {
        // 这里是body 读取之前的处理
        this.headers = httpInputMessage.getHeaders();
        String encodeAesKey = "";
        List keys = this.headers.get(Result.KEY);
        if (keys != null && keys.size() > 0) {
            encodeAesKey = keys.get(0);
        }
        try {
            // 1、解码得到aes 密钥
            String decodeAesKey = RsaUtils.decodeBase64ByPrivate(keyConfig.getRsaPrivateKey(), encodeAesKey);
            // 2、从inputStreamReader 得到aes 加密的内容
            String encodeAesContent = new BufferedReader(new InputStreamReader(httpInputMessage.getBody())).lines().collect(Collectors.joining(System.lineSeparator()));
            // 3、AES通过密钥CBC解码
            String aesDecode = AesUtils.decodeBase64(encodeAesContent, decodeAesKey, keyConfig.getAesIv().getBytes(), AesUtils.CIPHER_MODE_CBC_PKCS5PADDING);
            if (!StringUtils.isEmpty(aesDecode)) {
                // 4、重新写入到controller
                this.body = new ByteArrayInputStream(aesDecode.getBytes());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public InputStream getBody() throws IOException {
        return body;
    }

    @Override
    public HttpHeaders getHeaders() {
        return headers;
    }
}
  • 上面的代码注释我觉得都写的清楚了,不多介绍。

EncryptResponseAdvice.java

  • 这个类的主要功能就是对返回值进行加密操作
  • 直接在beforeBodyWrite()里面执行具体的加密操作即可
  • supports()方法也是需要返回true,在RequestResponseBodyAdviceChain.processBody()中有个判断只有supports()返回true才会执行beforeBodyWrite()
@Slf4j
@ControllerAdvice(basePackages = {"top.lrshuai.encrypt.controller"})
public class EncryptResponseAdvice implements ResponseBodyAdvice {

    @Autowired
    private KeyConfig keyConfig;

    @Override
    public boolean supports(MethodParameter methodParameter, Class> aClass) {
        // return true 有效
        return true;
    }

    /**
     * 返回结果加密
     * @param obj 接口返回的对象
     * @param methodParameter method
     * @param mediaType  mediaType
     * @param aClass HttpMessageConverter class
     * @param serverHttpRequest request
     * @param serverHttpResponse response
     * @return obj
     */
    @Override
    public Object beforeBodyWrite(Object obj, MethodParameter methodParameter, MediaType mediaType, Class> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        // 方法或类上有注解
        if (Utils.hasMethodAnnotation(methodParameter,new Class[]{Encrypt.class, Encode.class})) {
            // 这里假设已经定义好返回的model就是Result
            if (obj instanceof Result) {
                try {
                    // 1、随机aes密钥
                    String randomAesKey = AesUtils.generateSecret(256);
                    // 2、数据体
                    Object data = ((Result) obj).getData();
                    // 3、转json字符串
                    String jsonString = JSON.toJSONString(data);
                    // 4、aes加密数据体
                    String aesEncode = AesUtils.encodeBase64(jsonString, randomAesKey,keyConfig.getAesIv().getBytes(),AesUtils.CIPHER_MODE_CBC_PKCS5PADDING);
                    // 5、重新设置数据体
                    ((Result) obj).put(Result.DATA,aesEncode);
                    // 6、使用前端的rsa公钥加密 aes密钥 返回给前端
                    ((Result) obj).put(Result.KEY,RsaUtils.encodeBase64PublicKey(keyConfig.getFrontRsaPublicKey(),randomAesKey));
                    // 7、返回
                    return obj;
                } catch (Exception e) {
                   log.error("加密失败:",e);
                }
            }
        }
        return obj;
    }
}

看代码注释,不说了。

2、加密工具类

加密工具类,我在网上收集整理了一下,搞了个jar。直接在pom.xml 引入即可。如下:


	top.lrshuai.encryption
	encryption-tools
	1.0.3

自此核心代码都讲完了,这里只是给出了个demo,可以参考一下(代码写的也不是很好,很多地方也没有封装),加密方式多种多样,都是可以自由更改,这种加密方式不喜欢就改。
差点忘记了,前端代码呢。

3、前端代码

前端也是在Github分别找了两个库:

  • jsencrypt
    这个是RSA加密库,这个是在原版的
    jsencrypt进行增强修改,原版的我用过太长数据加密失败,多此加密解密失败,所以就用了这个库。
  • CryptoJS
    AES加密库,这个库是Google开源的,有AES、MD5、SHA 等加密方法

然后我使用的是Vue写的简单页面(业余前端)

html




    
    请求

    


字段: Value:
userId:
userName:
age:
info:
AES密钥:
AES向量:

要发送的数据:{{parameter}}

加密后的数据:{{encodeContent}}


收到服务端的内容:{{result}}

解密服务端AES密钥内容:{{decodeAes}}

最终拿到服务端的内容:{{decodeContent}}

主要看testRequest() 这个方法就行了,都有代码注释。

注意点

  • 后端需要注意的就是,controller参数需要用@RequestBody包起来,如下:
  • @PostMapping("/test1")
    @ResponseBody
    public Object test1(
    @RequestBody(required = false) TestDto dto){
    System.out.println(
    "dto="+dto);
    return Result.ok(dto);
    }
  • 而前端传上来的时候header需要设置"Content-Type": "application/json;charset=utf-8"

最终效果

在上面的postman中

  • data:里面的数据就是aes加密后的数据
  • key:里面就是前端RSA公钥加密后的AES密钥(前端需要用私钥解密得到aes密钥,然后再用密钥解开data里面的数据)
  • status:这个是状态码,如果报错了就不是200,不然报错了返回的数据,前端解几百年都解不开。

4、源码地址

https://gitee.com/rstyro/spring-boot/tree/master/Springboot2-api-encrypt
  • 作者:rstyro
  • 来源: https://rstyro.github.io/blog/2020/10/22/Springboot2接口加解密全过程详解(含前端代码)/

相关推荐

法拍房「捡漏」陷阱中,都是刚需们的血泪

文|镜相工作室,作者|李丹,编辑丨卢枕买法拍房的人越来越多了。2024年全国住宅类法拍房挂拍31.9万套,成交11.7万套。而在两年前,挂拍数相差无几,成交还只有8.1万套。当阿里法拍房五年激...

睡后收入新神话:90后程序员靠DEEPSEEK躺赚百万?

凌晨四点的杭州未来科技城,27岁程序员陈墨的支付宝响起提示音——这是他开发的AI情感咨询机器人自动完成第89单服务,当日收益突破1.2万元。这个基于DEEPSEEK模型打造的"赛博月老",正在批量制造...

当00后也变成打工人,还会认真谈恋爱吗?

当代年轻人的爱情里,总是会有以下这些场景:在家里被催婚,在约会app上左滑右滑地徘徊,又或者在陌生的环境里等待不期而遇。似乎比真爱更令人失落的,是一种现代人孤岛式的伤感。在专题“预制爱情”中,《新周刊...

我在冬天的上海,被汉王的办公本救了一命?

这老祖宗啊,说得还真没错。人到年末,工作上的破事就一箩筐,事情一多,感觉连血条都短了一大截。你敢相信,今天早上起床的时候,我看着镜子里那张有点认不出来人形的脸,那副垂头丧气的样子,属于是整个人从上到下...

老司机程序员用到的各种优秀资料、神器及框架整理

作者:欧巴冰冰链接:https://www.jianshu.com/p/d74934b49ba3目录资料篇技术站点...

聪明人都在教你用Deep Seek赚钱?不!他们只想“割韭菜”。

当“聪明人”开始用DeepSeek教你赚钱时,请先捂住钱包——因为他们的“聪明”,不过是把镰刀磨得更快,韭菜割得更响。第一步:制造焦虑,贩卖“时代抛弃论”。“不懂AI的人,正在被淘汰!”“某大厂裁员5...

我有一种紧迫感,去呈现社会转型中的思考|专访李睿珺

李睿珺可能是当代最受关注的青年导演之一。对乡村生活的关注是李睿珺创作的一大主题。《老驴头》《告诉他们,我乘白鹤去了》《家在水草丰茂的地方》对这一主题都有不同程度的体现。身为创作者,李睿珺的思考一直在持...

阿里云云效 vs 腾讯云CODING,DevOps孰强孰弱?

编辑导语:DevOps这个话题在IT、开发等领域是相对热门的,它意味着开发运维一体化,是推动业务发展与自动化交付的有效方式。国内便有不少DevOps平台可供企业使用。那么这些DevOps平台目前的发展...

所有小人书目录,以及手机观看截图

单双本连环画1-500连环画_B型美猴王(24册)连环画_一千零一日的故事(10册)连环画_一颗铜纽扣(3册)连环画_丁丁历险记(1-12册)...

瞧,布鲁范德又在一本正经地胡说八道

作者:孙欣祺1996年4月,在那个互联网尚不发达的年代,有一则网络流言引起不少美国人的关注:犹他大学的布鲁范德教授是不是去世了?刚刚办完退休手续的扬·哈罗德·布鲁范德亲自回复:“报道属实。本条信息发送...

“种草经济”种出了什么?

每天,有近400万篇笔记,在小红书上种草“诗与远方”。每小时,有超过1875万人在小红书搜索,求解“柴米油盐”。“种草”一词,源自社交媒体上看到他人分享,产生好感和想要亲自尝试的想法。一件心仪好物,一...

对Http Rest API接口设计和API治理管控的思考

作者:人月神话,新浪博客同名简介:多年SOA规划建设,私有云PaaS平台架构设计经验,长期从事一线项目实践在前面关于微服务方面的文章里面提到,对于多个微服务模块间往往都是以轻量的HttpRestA...

一部汇辑有关吕洞宾之事迹、神话传说及著作《吕祖全书》

0957-吕祖全书(古本)...

塞林格遗稿出版谜案:他并非完全拒斥出版

今年是J·D·塞林格诞辰一百周年,塞林格之子、塞林格基金会负责人马特·塞林格(MattSalinger)首次访华,进行了一系列讲座和对谈活动。在他的指导修订下,译林出版社于2018年末推出了塞林格作...

程序员十大层次,你在第几层

大家好,我是柠檬哥,专注编程知识分享。欢迎关注@程序员柠檬橙,编程路上不迷路,私信发送以下关键字获取编程资源:...