SpringBoot接口加解密全过程详解
一、接口为什么要加密
接口加密传输,主要作用: 敏感数据防止泄漏、 保护隐私、 防伪装攻击、 防篡改攻击、 防重放攻击 等等… 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"}) public class EncryptRequestAdvice implements RequestBodyAdvice { @Autowired private KeyConfig keyConfig; /** * 是否需要解码 */ private boolean isDecode; @Override public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> 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<? extends HttpMessageConverter<?>> 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<? extends HttpMessageConverter<?>> aClass) { // 这里就是已经读取到body了,obj就是 return obj; } @Override public Object handleEmptyBody(Object obj, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> 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