一、接口为什么要加密 接口加密传输,主要作用:敏感数据防止泄漏、保护隐私、防伪装攻击、防篡改攻击、防重放攻击等等4个字概括:保护数据! 当然不是说接口加密后,就能完完全全的保护我们的数据,但至少能防一部分人拿到我们的数据。 而且接口加密感觉逼格是不是高过一点!!!二、加密思路1、加密简介 加密算法有很多,在能加密又能解密的算法可分为:非对称加密算法,常见:RSA、DSA、ECC 特点:算法复杂,加解密速度慢,但安全性高,一般与对称加密结合使用(对称加密对内容加密,非对称对对称所使用的密钥加密)对称加密算法,常见:DES、3DES、AES、Blowfish、IDEA、RC5、RC6 特点:加密解密效率高,速度快,适合进行大数据量的加解密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提供两个接口RequestBodyAdvice、ResponseBodyAdvice。实现它们,即可对Controller进行增强,第一个是在controller之前增强,第二个就是对controller的返回值进行增强。在spring启动的时候会对RequestMappingHandlerAdapter的initControllerAdviceCache()方法进行初始化。会去把有ControllerAdvice的类进行注入。1、自定义类 下面就来实现上面的两个接口实现类代码EncryptRequestAdvice。java这个类的功能就是在请求到controller之前就把前端传上来的数据解密好我们还要校验是否有必要解密ControllerAdvice(basePackages{top。lrshuai。encrypt。controller})publicclassEncryptRequestAdviceimplementsRequestBodyAdvice{AutowiredprivateKeyConfigkeyC是否需要解码privatebooleanisDOverridepublicbooleansupports(MethodParametermethodParameter,Typetype,C?extendsHttpMessageC?aClass){方法或类上有注解if(Utils。hasMethodAnnotation(methodParameter,newClass〔〕{Encrypt。class,Decode。class})){isD这里返回true才支持}}OverridepublicHttpInputMessagebeforeBodyRead(HttpInputMessagehttpInputMessage,MethodParametermethodParameter,Typetype,C?extendsHttpMessageC?aClass)throwsIOException{if(isDecode){returnnewDecodeInputMessage(httpInputMessage,keyConfig);}returnhttpInputM}OverridepublicObjectafterBodyRead(Objectobj,HttpInputMessagehttpInputMessage,MethodParametermethodParameter,Typetype,C?extendsHttpMessageC?aClass){这里就是已经读取到body了,obj就是}OverridepublicObjecthandleEmptyBody(Objectobj,HttpInputMessagehttpInputMessage,MethodParametermethodParameter,Typetype,C?extendsHttpMessageC?aClass){body为空的时候调用}}在上面实现类中需要重写:supports()、beforeBodyRead()、afterBodyRead()、handleEmptyBody()方法只有在supports()返回true后面的方法才会支持执行。在RequestResponseBodyAdviceChain有判断我们可以在beforeBodyRead()这个方法进行解密处理。在上面的代码中,我加了自定义注解,因为可能需求是这样的,有些接口加密有些接口不加密,用自定义注解比较方便。然后DecodeInputMessage这个类是自定义实现了HttpInputMessage接口,解码逻辑都在里面。如下:DecodeInputMessage。java 这个类就是具体的解码逻辑了publicclassDecodeInputMessageimplementsHttpInputMessage{privateHttpHprivateInputSpublicDecodeInputMessage(HttpInputMessagehttpInputMessage,KeyConfigkeyConfig){这里是body读取之前的处理this。headershttpInputMessage。getHeaders();StringencodeAesKListStringkeysthis。headers。get(Result。KEY);if(keys!nullkeys。size()0){encodeAesKeykeys。get(0);}try{1、解码得到aes密钥StringdecodeAesKeyRsaUtils。decodeBase64ByPrivate(keyConfig。getRsaPrivateKey(),encodeAesKey);2、从inputStreamReader得到aes加密的内容StringencodeAesContentnewBufferedReader(newInputStreamReader(httpInputMessage。getBody()))。lines()。collect(Collectors。joining(System。lineSeparator()));3、AES通过密钥CBC解码StringaesDecodeAesUtils。decodeBase64(encodeAesContent,decodeAesKey,keyConfig。getAesIv()。getBytes(),AesUtils。CIPHERMODECBCPKCS5PADDING);if(!StringUtils。isEmpty(aesDecode)){4、重新写入到controllerthis。bodynewByteArrayInputStream(aesDecode。getBytes());}}catch(Exceptione){e。printStackTrace();}}OverridepublicInputStreamgetBody()throwsIOException{}OverridepublicHttpHeadersgetHeaders(){}}上面的代码注释我觉得都写的清楚了,不多介绍。EncryptResponseAdvice。java这个类的主要功能就是对返回值进行加密操作直接在beforeBodyWrite()里面执行具体的加密操作即可supports()方法也是需要返回true,在RequestResponseBodyAdviceChain。processBody()中有个判断只有supports()返回true才会执行beforeBodyWrite()Slf4jControllerAdvice(basePackages{top。lrshuai。encrypt。controller})publicclassEncryptResponseAdviceimplementsResponseBodyAdviceObject{AutowiredprivateKeyConfigkeyCOverridepublicbooleansupports(MethodParametermethodParameter,C?extendsHttpMessageC?aClass){returntrue有效}返回结果加密paramobj接口返回的对象parammethodParametermethodparammediaTypemediaTypeparamaClassHttpMessageConverterclassparamserverHttpRequestrequestparamserverHttpResponseresponsereturnobjOverridepublicObjectbeforeBodyWrite(Objectobj,MethodParametermethodParameter,MediaTypemediaType,C?extendsHttpMessageC?aClass,ServerHttpRequestserverHttpRequest,ServerHttpResponseserverHttpResponse){方法或类上有注解if(Utils。hasMethodAnnotation(methodParameter,newClass〔〕{Encrypt。class,Encode。class})){这里假设已经定义好返回的model就是Resultif(objinstanceofResult){try{1、随机aes密钥StringrandomAesKeyAesUtils。generateSecret(256);2、数据体Objectdata((Result)obj)。getData();3、转json字符串StringjsonStringJSON。toJSONString(data);4、aes加密数据体StringaesEncodeAesUtils。encodeBase64(jsonString,randomAesKey,keyConfig。getAesIv()。getBytes(),AesUtils。CIPHERMODECBCPKCS5PADDING);5、重新设置数据体((Result)obj)。put(Result。DATA,aesEncode);6、使用前端的rsa公钥加密aes密钥返回给前端((Result)obj)。put(Result。KEY,RsaUtils。encodeBase64PublicKey(keyConfig。getFrontRsaPublicKey(),randomAesKey));7、返回}catch(Exceptione){log。error(加密失败:,e);}}}}} 看代码注释,不说了。2、加密工具类 加密工具类,我在网上收集整理了一下,搞了个jar。直接在pom。xml引入即可。如下:dependencygroupIdtop。lrshuai。encryptiongroupIdencryptiontoolsartifactIdversion1。0。3versiondependency 自此核心代码都讲完了,这里只是给出了个demo,可以参考一下(代码写的也不是很好,很多地方也没有封装),加密方式多种多样,都是可以自由更改,这种加密方式不喜欢就改。 差点忘记了,前端代码呢。3、前端代码 前端也是在Github分别找了两个库:jsencrypt 这个是RSA加密库,这个是在原版的jsencrypt进行增强修改,原版的我用过太长数据加密失败,多此加密解密失败,所以就用了这个库。CryptoJS AES加密库,这个库是Google开源的,有AES、MD5、SHA等加密方法 然后我使用的是Vue写的简单页面(业余前端)html!DOCTYPEhtmlhtmllangenxmlns:thhttp:www。thymeleaf。orgheadmetacharsetUTF8title请求titlestyleapp{width:500height:500margin:100}。mytable{border:1pxsolidA6C1E4;fontfamily:Abordercollapse:}tableth{border:1backgroundcolor:71c1width:100height:20fontsize:15}tabletd{border:1pxsolidA6C1E4;textalign:height:15paddingtop:5fontsize:12}。double{backgroundcolor:c7dff6;}input{width:95;paddingleft:10}styleheadbodytableclassmytabletrclassdoubleth字段:ththValue:thtrtrclassdoubletduserId:tdtdinputvmodeluserInfo。userIdtdtrtrclassdoubletduserName:tdtdinputvmodeluserInfo。userNametdtrtrclassdoubletdage:tdtdinputvmodeluserInfo。agetdtrtrclassdoubletdinfo:tdtdtextareavmodeluserInfo。infocols50rows5placeholder随便输一点textareatdtrtrclassdoubletdAES密钥:tdtdtextareavmodelaes。keycols50rows2placeholderAES密钥textareatdtrtrclassdoubletdAES向量:tdtdtextareavmodelaes。ivcols50rows1placeholder向量的长度为16位textareatdtrtablebuttonclicktestRequest发送测试请求button p要发送的数据:span{{parameter}}spanp加密后的数据:{{encodeContent}} p收到服务端的内容:{{result}}p解密服务端AES密钥内容:{{decodeAes}}p最终拿到服务端的内容:{{decodeContent}}bodyhtml 主要看testRequest()这个方法就行了,都有代码注释。注意点后端需要注意的就是,controller参数需要用RequestBody包起来,如下:PostMapping(test1) ResponseBody publicObjecttest1(RequestBody(requiredfalse)TestDtodto){ System。out。println(dtodto); returnResult。ok(dto); }而前端传上来的时候header需要设置ContentType:charsetutf8最终效果 在上面的postman中data:里面的数据就是aes加密后的数据key:里面就是前端RSA公钥加密后的AES密钥(前端需要用私钥解密得到aes密钥,然后再用密钥解开data里面的数据)status:这个是状态码,如果报错了就不是200,不然报错了返回的数据,前端解几百年都解不开。4、源码地址https:gitee。comrstyrospringboottreemasterSpringboot2apiencrypt作者:rstyro来源:https:rstyro。github。ioblog20201022Springboot2接口加解密全过程详解(含前端代码)