相关推荐recommended
SpringBoot接口加密与解密
作者:mmseoamin日期:2023-12-14

文章目录

  • 一、对称/非对称加密
    • 1、简介
    • 2、RSA和AES介绍
      • 2.1 RSA
      • 2.2 AES
      • 3、RSA/AES组合
      • 4、Base64编码的作用
      • 二、Java实现加解密/加验签
        • 1、全局Config
        • 2、RSA非对称加密
        • 3、AES对称加密
        • 三、加解密 starter实战
          • 1、介绍
          • 2、前期准备
            • 2.1 引入依赖
            • 2.2 封装公共相应类
            • 2.3 定义加解密工具类
            • 2.4 定义两个注解
            • 2.5 设置自定义key
            • 3、接口加密与解密
              • 3.1 介绍
              • 3.2 接口加密
              • 3.3 接口解密
              • 4、打包发布starter
                • 4.1 定义自动化配置类
                • 4.2 发布线上使用
                • 5、新项目使用

                  一、对称/非对称加密

                  1、简介

                  对称加密只有一个秘钥,加密和解密都是用同一个秘钥,所以叫做对称加密。

                  非对称加密有两个秘钥,一个是公钥,一个是私钥。非对称的特点在于,公钥加密的私钥可以解密,但私钥加密的,公钥解不出来,只能验证是否由私钥进行加密

                  目前常见的加密方式是有两种,一种是对称加密(AES为代表),一种是非对称加密(RSA为代表)

                  2、RSA和AES介绍

                  2.1 RSA

                  特点:只需交换公钥;公/秘钥机制,公钥加密,私钥解密(或者私钥加密,公钥解密);公钥负责加密,私钥负责解密;私钥负责签名,公钥负责验证

                  缺点:加解密速度慢,特别是解密

                  2.2 AES

                  特点:加解密用同一秘钥

                  优点:速度快,效率高;

                  缺点:秘钥交换问题

                  3、RSA/AES组合

                  对称加密(AES)的优势在于加密较快,**但**劣势在于秘钥一旦给出去就不安全了。非对称加密(RSA)的优势在于安全,就算提供公钥出去,别人也解密不了数据,但劣势是加密速度较慢

                  实际使用的过程中常常将两者组合使用(AES+RSA),这样可以安全的传输AES秘钥,避免了RSA加密的慢速度

                  • 生成一个随机AES秘钥字符串
                  • 使用RSA公钥加密AES秘钥,然后再用AES秘钥加密真正的内容
                  • 把skey=加密的AES秘钥,body=AES秘钥加密的内容传过去
                  • 对面使用RSA私钥解密AES秘钥,然后用AES秘钥解密出内容

                    4、Base64编码的作用

                    加密后的数据可能不具备可读性,因此我们一般需要对加密后的数据再使用 Base64 算法进行编码,获取可读字符串。换言之,AES 或者RSA加密方法的返回值是一个 Base64 编码之后的字符串,AES或者RSA 解密方法的参数也是一个 Base64 编码之后的字符串,先对该字符串进行解码,然后再解密。

                    二、Java实现加解密/加验签

                    1、全局Config

                    public class Config {
                        public static final String AES_ALGORITHM = "AES/CBC/PKCS5Padding";
                        public static final String RSA_ALGORITHM = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";
                        //必须是PKCS8格式
                        public static final String CLIENT_PRIVATE_KEY = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAO/8ucCgOTJ7DCPC" +
                                "rCCL1VKDnUX61QnxwbAvpGp1/lletEIcjUouM7F0VvMHzViNLvpw7N7NBHPa+5gO" +
                                "js68t9hKMUh+a6RTE34SWIqSDRPCzDKVWugsFb04o3vRl3rZ1z6B+QDdW7xwOhEr" +
                                "PPoEqmjjIOjQPcU6xs0SPzSimOa1AgMBAAECgYAO5m0OBaSnerZNPhf7yVLMVbmd" +
                                "D67MeEMjUkHuDjdlixi8BhPLqESzXtrLKg/Y0KM7D2nVh3sgSldWoIjDUzpCx8Z2" +
                                "yHLU1K2wakMdBgEF3xeJPxxZRpP+earl0SyLTA4hMxl48uAjn/mkPgzoMgQkqyQz" +
                                "5HOWjjsCLJFyEvqmoQJBAP5cBk0KXpHnCMgOupbi/pXDyaF1o+dCE97GaEdrV/0P" +
                                "uwDfYDYfY3wzd1QM7C4b4MmE+SNVpC0W9PyaMONJlN0CQQDxiPiGdwX9actMNJea" +
                                "JZ+k3BjCN+mM6Px7j/mtYcXWNZkyCXSXUBI62drZ0htenrh2qwichMlMgNJClvG6" +
                                "Gu+5AkEA30R7q2gstrkrNh/nnMZHXcJr3DPc2QNhWayin/4TT+hc51krpJZMxxqN" +
                                "5dMqBRcnavwzi9aCs6lxBcF6pCdUaQJANhd7uPls4PzRZ6abkQz9/LjB3rUQ29rN" +
                                "uIpc2yR7XuawAVG2x7BJ9N4XMhLoyD75hrH1AsCGKFjtPbZ6OjiQGQJAF2DbIodC" +
                                "uYb6eMZ8ux1Ab0wBEWWc5+iGgEVBNh22uZ/klE1/C0+KKzZhqgzaA/vPapq6dhuJ" +
                                "sNXlJia10PwYrQ==";
                        public static final String CLIENT_PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDv/LnAoDkyewwjwqwgi9VSg51F" +
                                "+tUJ8cGwL6Rqdf5ZXrRCHI1KLjOxdFbzB81YjS76cOzezQRz2vuYDo7OvLfYSjFI" +
                                "fmukUxN+EliKkg0TwswylVroLBW9OKN70Zd62dc+gfkA3Vu8cDoRKzz6BKpo4yDo" +
                                "0D3FOsbNEj80opjmtQIDAQAB";
                        public static final String SERVER_PRIVATE_KEY = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAPGkxlAJPKR3BRxT" +
                                "PIeB3pDv117j8XbpuEik5UIOlY3GUtAV1sad5NNDUAnP/DB80yAQ8ycm9Xdkutuo" +
                                "f25Xlb7w0bRQNpfJlijx9eF8PsB6t63r8KAfWJlqbNHgN8AMK9P5XzVyN4YiEnUl" +
                                "Jh/EYiwLiYzflNnmnnfRrI4nUo8fAgMBAAECgYEAvwTxm81heeV4Tcbi33/jUBG4" +
                                "4BMzCzyA6DQp4wkiYju3tTS+Xq3seLEKcWdPxYi3YO7lODsM6j/fksrlSXXFMe1i" +
                                "ZAF3FNuDVZPz2zdFYS8vh6kdlDHMJAUnU/POMMWJ880MQDtkwTuzH8Tao8OKcAP4" +
                                "kc0QuG00wOrmuE+5gZECQQD9bqZkJsN+tj3+pxs57azy6B6gOqgm54/ujB+u63XU" +
                                "rO9Sf57asgF4OfUFltaVhjlUMSrWcgp6f4HSy7hBSKJpAkEA9BeML5iDIHOgTIws" +
                                "+ID55ELbzO7A/YtcYnUU09mkKCdonMXbXke+EhLApf5vX9ZmreoEfJCdsTnMEcQi" +
                                "fkjkRwJBALpf2TXl2/cfhs/zjG45f+rTEVK8UFTsDklb+yDkQC87TnTZLbWfGr2T" +
                                "wcFugDhOEXL9BYfXLiWQB6VB9Crug6ECQGEmTiFTbj0oSBCvaeauTsdO5PS3whAn" +
                                "u2lkeBmpcfCZXsWm6hyoKTpARHTMw789Mjjd/1Mkq96xxkr76U6h7FkCQHRc2elg" +
                                "Dh84wqHIptwa+moosVvd7aSzktuOB4CQRO10qKkSHVFuI+sl47A4KGzH/nX9ydUm" +
                                "tpsTnQAlXwBczd4=";
                        public static final String SERVER_PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDxpMZQCTykdwUcUzyHgd6Q79de" +
                                "4/F26bhIpOVCDpWNxlLQFdbGneTTQ1AJz/wwfNMgEPMnJvV3ZLrbqH9uV5W+8NG0" +
                                "UDaXyZYo8fXhfD7Aeret6/CgH1iZamzR4DfADCvT+V81cjeGIhJ1JSYfxGIsC4mM" +
                                "35TZ5p530ayOJ1KPHwIDAQAB";
                    }
                    

                    2、RSA非对称加密

                    import javax.crypto.Cipher;
                    import javax.crypto.spec.OAEPParameterSpec;
                    import javax.crypto.spec.PSource;
                    import java.security.*;
                    import java.security.spec.MGF1ParameterSpec;
                    import java.security.spec.PKCS8EncodedKeySpec;
                    import java.security.spec.X509EncodedKeySpec;
                    import org.springframework.util.Base64Utils;
                    public class RSACipher {
                        /**
                         * 获取公钥
                         * @param key 密钥字符串(经过base64编码)
                         * @return 公钥
                         */
                        public static PublicKey getPublicKey(String key) throws Exception {
                            // 按照X.509标准对其进行编码的密钥
                            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64Utils.decode(key.getBytes()));
                            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
                            // 生成公钥
                            PublicKey publicKey = keyFactory.generatePublic(keySpec);
                            return publicKey;
                        }
                        /**
                         * 获取私钥
                         * @param key 密钥字符串(经过base64编码)
                         * @return 私钥
                         */
                        public static PrivateKey getPrivateKey(String key) throws Exception {
                            // 按照PKCS8格式标准对其进行编码的密钥,首先要将key进行base64解码
                            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64Utils.decode(key.getBytes()));
                            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
                            // 生成私钥
                            PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
                            return privateKey;
                        }
                        /**
                         * 加密方法
                         * @param publicKey 公钥
                         * @param raw       待加密明文
                         * @return 加密后的密文
                         */
                        public static byte[] encrypt(String publicKey, byte[] raw) throws Exception {
                            Key key = getPublicKey(publicKey);
                            Cipher cipher = Cipher.getInstance(Config.RSA_ALGORITHM);
                            // 初始化
                            cipher.init(Cipher.ENCRYPT_MODE, key, new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT));
                            byte[] encryption = cipher.doFinal(raw);
                            // 最后将加密后的数据进行base64编码
                            return Base64Utils.encode(encryption);
                        }
                        /**
                         * 解密方法
                         * @param privateKey 私钥
                         * @param enc  待解密密文
                         * @return 解密后的明文
                         */
                        public static byte[] decrypt(String privateKey, byte[] enc) throws Exception {
                            Key key = getPrivateKey(privateKey);
                            Cipher cipher = Cipher.getInstance(Config.RSA_ALGORITHM);
                            // 初始化
                            cipher.init(Cipher.DECRYPT_MODE, key, new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT));
                            // 先进行base64解密,然后解码
                            return cipher.doFinal(Base64Utils.decode(enc));
                        }
                        /**
                         * 签名
                         * @param privateKey 私钥
                         * @param content    要进行签名的内容
                         * @return 签名
                         */
                        public static String sign(String privateKey, byte[] content) {
                            try {
                                // privateKey进行base64编码,然后生成PKCS8格式私钥
                                PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(Base64Utils.decode(privateKey.getBytes()));
                                KeyFactory key = KeyFactory.getInstance("RSA");
                                PrivateKey priKey = key.generatePrivate(priPKCS8);
                                // 签名摘要算法
                                Signature signature = Signature.getInstance("SHA256WithRSA");
                                // 用私钥初始化此对象以进行签名
                                signature.initSign(priKey);
                                // 使用指定的字节数组更新签名或验证
                                signature.update(content);
                                // 获得签名字节
                                byte[] signed = signature.sign();
                                // 进行base64编码返回
                                return new String(Base64Utils.encode(signed));
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                            return null;
                        }
                        /**
                         * 验签
                         * @param publicKey 公钥
                         * @param content   要验签的内容
                         * @param sign      签名
                         * @return 验签结果
                         */
                        public static boolean checkSign(String publicKey, byte[] content, String sign) {
                            try {
                                KeyFactory keyFactory = KeyFactory.getInstance("RSA");
                                // 进行base64解码
                                byte[] encodedKey = Base64Utils.decodeFromString(publicKey);
                                // 生成公钥
                                PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
                                // 签名摘要算法
                                Signature signature = Signature.getInstance("SHA256WithRSA");
                                // 用公钥初始化签名
                                signature.initVerify(pubKey);
                                // 使用指定的字节数组更新签名或验证
                                signature.update(content);
                                // base64解码后进行验证
                                return signature.verify(Base64Utils.decodeFromString(sign));
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                            return false;
                        }
                        public static void main(String[] args) throws Exception {
                            //客户端代码
                            String text = "hello";
                            //使用服务端公钥加密
                            byte[] encryptText = RSACipher.encrypt(Config.SERVER_PUBLIC_KEY, text.getBytes());
                            System.out.println("加密后:\n" + new String(encryptText));
                            //使用客户端私钥签名
                            String signature = RSACipher.sign(Config.CLIENT_PRIVATE_KEY, encryptText);
                            System.out.println("签名:\n" + signature);
                            //服务端代码
                            //使用客户端公钥验签
                            boolean result = RSACipher.checkSign(Config.CLIENT_PUBLIC_KEY, encryptText, signature);
                            System.out.println("验签:\n" + result);
                            //使用服务端私钥解密
                            byte[] decryptText = RSACipher.decrypt(Config.SERVER_PRIVATE_KEY, encryptText);
                            System.out.println("解密后:\n" + new String(decryptText));
                        }
                    }
                    

                    输出结果

                    加密后:
                    ODdEkwo1RgRW8UMoHXPKe9Gwcp6lTCkg4P/Ra3gfkrO+Fw6pSgo0H54nMC5sYSsoUVy1wy2/QXeLSwR6Obfl7SU7DeW+XdGee83O2kgdsDQPbYFwlPYTd0cdOmWwZxtgEOIB9d5G75Iut4kci15vrhXZVtku92U+7aNwtYimSDQ=
                    签名:
                    RL1qIScizRyu79/y+r2TN2FL/bSQDxnDj4JlDwSZM6XZR7CL7u5ZjLNHbsSYpHaCv9qKMS4ump50LyF+go05dsPjWZOvFNkgcm9LepkDP1qm8AzKdTGwlzhdBmy2397Ed8uBrQocFGj/721Y2xM/Db0nt7r54zKZkDXbMMlsd9k=
                    验签:
                    true
                    解密后:
                    hello
                    

                    3、AES对称加密

                    import org.springframework.util.Base64Utils;
                    import javax.crypto.Cipher;
                    import javax.crypto.spec.IvParameterSpec;
                    import javax.crypto.spec.SecretKeySpec;
                    import java.security.SecureRandom;
                    public class AESCipher {
                        public static SecureRandom random = new SecureRandom();
                        /**
                         * 获取随机16位key,key必须要是10的整数倍,否则会出错
                        */
                        public static String getRandom(int length) {
                            StringBuilder ret = new StringBuilder();
                            for (int i = 0; i < length; i++) {
                                // 输出字母还是数字
                                boolean isChar = (random.nextInt(2) % 2 == 0);
                                // 字符串
                                if (isChar) {
                                    // 取得大写字母还是小写字母
                                    int choice = random.nextInt(2) % 2 == 0 ? 65 : 97;
                                    ret.append((char) (choice + random.nextInt(26)));
                                } else { // 数字
                                    ret.append(random.nextInt(10));
                                }
                            }
                            return ret.toString();
                        }
                        /**
                         * 加密方法,使用key充当向量iv,增加加密算法的强度
                         * 更加安全
                         * @param key 密钥
                         * @param raw 需要加密的内容
                         * @return
                         */
                        public static String encrypt(byte[] key, String raw) throws Exception {
                            // 第一次加密
                            SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
                            byte[] enCodeFormat = secretKey.getEncoded();
                            // 获取二次加密的key
                            SecretKeySpec secondSecretKey = new SecretKeySpec(enCodeFormat, "AES");
                            Cipher cipher = Cipher.getInstance(Config.AES_ALGORITHM);
                            // 向量iv,增加加密算法的强度
                            IvParameterSpec iv = new IvParameterSpec(key);
                            // 初始化加密器
                            cipher.init(Cipher.ENCRYPT_MODE, secondSecretKey, iv);
                            // 加密
                            byte[] result = cipher.doFinal(raw.getBytes());
                            // 进行base64编码
                            return Base64Utils.encodeToString(result);
                        }
                        /**
                         * 解密方法,使用key充当向量iv,增加加密算法的强度
                         * @param key 密钥
                         * @param enc 待解密内容
                         * @return
                         */
                        public static String decrypt(byte[] key, String enc) throws Exception {
                            SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
                            byte[] enCodeFormat = secretKey.getEncoded();
                            // 二次加密
                            SecretKeySpec secondSecretKey = new SecretKeySpec(enCodeFormat, "AES");
                            Cipher cipher = Cipher.getInstance(Config.AES_ALGORITHM);
                            IvParameterSpec iv = new IvParameterSpec(key);
                            // 初始化
                            cipher.init(Cipher.DECRYPT_MODE, secondSecretKey, iv);
                            // 首先进行base64解码
                            byte[] bytes = Base64Utils.decodeFromString(enc);
                            // 解密
                            byte[] result = cipher.doFinal(bytes);
                            return new String(result);
                        }
                        public static void main(String[] args) throws Exception {
                            //客户端代码
                            String text = "hello";
                            //随机生成16位aes密钥,也可以自己指定16位
                            byte[] aesKey = getRandom(16).getBytes();
                            String encryptText = AESCipher.encrypt(aesKey, text);
                            System.out.println("加密后:\n" + encryptText);
                            String decryptText = AESCipher.decrypt(aesKey, encryptText);
                            System.out.println("解密后:\n" + decryptText);
                        }
                    }
                    

                    输出结果

                    加密后:
                    hwkYAF9eXj/dytmDBD30xg==
                    解密后:
                    hello
                    

                    三、加解密 starter实战

                    1、介绍

                    加密解密本身并不是难事,问题是在何时去处理?定义一个过滤器,将请求和响应分别拦截下来进行处理也是一个办法,这种方式虽然粗暴,但是灵活,因为可以拿到一手的请求参数和响应数据。不过 SpringMVC 中给我们提供了 ResponseBodyAdvice和 RequestBodyAdvice,利用这两个工具可以对请求和响应进行预处理,非常方便。

                    参考:

                    RSA+AES混合加密-JavaWebSpringBoot自定义starter

                    2、前期准备

                    2.1 引入依赖

                    因为我们这个工具是为 Web 项目开发的,以后必然使用在 Web 环境中,所以这里添加依赖时 scope 设置为 provided

                    
                        org.springframework.boot
                        spring-boot-starter-web
                        provided
                        2.7.0
                    
                    

                    scope几个属性介绍

                    • compile:默认值 他表示被依赖项目需要参与当前项目的编译,还有后续的测试,运行周期也参与其中,是一个比较强的依赖。打包的时候通常需要包含进去
                    • test:依赖项目仅仅参与测试相关的工作,包括测试代码的编译和执行,不会被打包,例如:junit
                    • runtime:表示被依赖项目无需参与项目的编译,不过后期的测试和运行周期需要其参与。与compile相比,跳过了编译而已。例如JDBC驱动,适用运行和测试阶段
                    • provided:打包的时候可以不用包进去,别的设施会提供。事实上该依赖理论上可以参与编译,测试,运行等周期。相当于compile,但是打包阶段做了exclude操作
                    • system:从参与度来说,和provided相同,不过被依赖项不会从maven仓库下载,而是从本地文件系统拿。需要添加systemPath的属性来定义路径

                      2.2 封装公共相应类

                      public class RespBean {
                          private Integer status;
                          private String msg;
                          private Object obj;
                          public static RespBean build() {
                              return new RespBean();
                          }
                          public static RespBean ok(String msg) {
                              return new RespBean(200, msg, null);
                          }
                          public static RespBean ok(String msg, Object obj) {
                              return new RespBean(200, msg, obj);
                          }
                          public static RespBean error(String msg) {
                              return new RespBean(500, msg, null);
                          }
                          public static RespBean error(String msg, Object obj) {
                              return new RespBean(500, msg, obj);
                          }
                          private RespBean() {
                          }
                          private RespBean(Integer status, String msg, Object obj) {
                              this.status = status;
                              this.msg = msg;
                              this.obj = obj;
                          }
                          public Integer getStatus() {
                              return status;
                          }
                          public RespBean setStatus(Integer status) {
                              this.status = status;
                              return this;
                          }
                          public String getMsg() {
                              return msg;
                          }
                          public RespBean setMsg(String msg) {
                              this.msg = msg;
                              return this;
                          }
                          public Object getObj() {
                              return obj;
                          }
                          public RespBean setObj(Object obj) {
                              this.obj = obj;
                              return this;
                          }
                      }
                      

                      2.3 定义加解密工具类

                      加密这块有多种方案可以选择,对称加密、非对称加密,其中对称加密又可以使用 AES、DES、3DES 等不同算法,这里我们使用 Java 自带的 Cipher 来实现对称加密,使用 AES 算法

                      public class AESUtils {
                          private static final String AES_ALGORITHM = "AES/ECB/PKCS5Padding";
                          // 获取 cipher
                          private static Cipher getCipher(byte[] key, int model) throws Exception {
                              SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
                              Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
                              cipher.init(model, secretKeySpec);
                              return cipher;
                          }
                          // AES加密
                          public static String encrypt(byte[] data, byte[] key) throws Exception {
                              Cipher cipher = getCipher(key, Cipher.ENCRYPT_MODE);
                              return Base64.getEncoder().encodeToString(cipher.doFinal(data));
                          }
                          // AES解密
                          public static byte[] decrypt(byte[] data, byte[] key) throws Exception {
                              Cipher cipher = getCipher(key, Cipher.DECRYPT_MODE);
                              return cipher.doFinal(Base64.getDecoder().decode(data));
                          }
                      }
                      

                      2.4 定义两个注解

                      接下来我们定义两个注解 @Decrypt 和 @Encrypt。在以后使用的过程中,哪个接口方法添加了 @Encrypt 注解就对哪个接口的数据加密返回,哪个接口/参数添加了 @Decrypt 注解就对哪个接口/参数进行解密。另外就是 @Decrypt 可以用在参数上

                      @Retention(RetentionPolicy.RUNTIME)
                      @Target({ElementType.METHOD,ElementType.PARAMETER})
                      public @interface Decrypt {
                      }
                      @Retention(RetentionPolicy.RUNTIME)
                      @Target(ElementType.METHOD)
                      public @interface Encrypt {
                      }
                      

                      2.5 设置自定义key

                      定义一个 EncryptProperties 类来读取用户配置的 key,这样就可以自定义key。这里设置了默认值,以后如果用户想自己配置 key,只需要在 application.properties 中配置 spring.encrypt.key=xxx 即可。

                      @ConfigurationProperties(prefix = "spring.encrypt")
                      @Component
                      public class EncryptProperties {
                          // 这一块一定要16位或者整数倍,最多256
                          private final static String DEFAULT_KEY = "www.shawn222.com";
                          private String key = DEFAULT_KEY;
                          public String getKey() {
                              return key;
                          }
                          public void setKey(String key) {
                              this.key = key;
                          }
                      }
                      

                      3、接口加密与解密

                      3.1 介绍

                      ResponseBodyAdvice 在你使用了 @ResponseBody 注解的时候才会生效,RequestBodyAdvice 在你使用了 @RequestBody 注解的时候才会生效,换言之,前后端都是 JSON 交互的时候,这两个才有用

                      3.2 接口加密

                      我们自定义 EncryptResponse 类实现 ResponseBodyAdvice接口,泛型表示接口的返回类型,这里一共要实现两个方法

                      • supports:这个方法用来判断什么样的接口需要加密,参数 returnType 表示返回类型,我们这里的判断逻辑就是方法是否含有 @Encrypt 注解,如果有,表示该接口需要加密处理,如果没有,表示该接口不需要加密处理。
                      • beforeBodyWrite:这个方法会在数据响应之前执行,也就是我们先对响应数据进行二次处理,处理完成后,才会转成 json 返回。我们这里的处理方式很简单,RespBean 中的 status 是状态码就不用加密了,另外两个字段重新加密后重新设置值即可。

                        另外需要注意,自定义的 ResponseBodyAdvice 需要用 @ControllerAdvice 注解来标记。

                        @EnableConfigurationProperties(EncryptProperties.class)
                        @ControllerAdvice
                        public class EncryptResponse implements ResponseBodyAdvice {
                            
                            private ObjectMapper om = new ObjectMapper();
                            
                            @Autowired
                            EncryptProperties encryptProperties;
                            @Override
                            public boolean supports(MethodParameter returnType, Class> converterType) {
                                return returnType.hasMethodAnnotation(Encrypt.class);
                            }
                            @Override
                            public RespBean beforeBodyWrite(RespBean body, MethodParameter returnType, MediaType selectedContentType, Class> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
                                byte[] keyBytes = encryptProperties.getKey().getBytes();
                                try {
                                    if (body.getMsg()!=null) {
                                        body.setMsg(AESUtils.encrypt(body.getMsg().getBytes(),keyBytes));
                                    }
                                    if (body.getObj() != null) {
                                        body.setObj(AESUtils.encrypt(om.writeValueAsBytes(body.getObj()), keyBytes));
                                    }
                                } catch (Exception e) {
                                    e.printStackTrace();
                                }
                                return body;
                            }
                        }
                        

                        3.3 接口解密

                        首先大家注意,DecryptRequest 类我们没有直接实现 RequestBodyAdvice 接口,而是继承自 RequestBodyAdviceAdapter 类,该类是 RequestBodyAdvice 接口的子类,并且实现了接口中的一些方法,这样当我们继承自 RequestBodyAdviceAdapter 时,就只需要根据自己实际需求实现某几个方法即可。

                        • supports:该方法用来判断哪些接口需要处理接口解密,我们这里的判断逻辑是方法上或者参数上含有 @Decrypt 注解的接口,处理解密问题。
                        • beforeBodyRead:这个方法会在参数转换成具体的对象之前执行,我们先从流中加载到数据,然后对数据进行解密,解密完成后再重新构造 HttpInputMessage 对象返回。
                          @EnableConfigurationProperties(EncryptProperties.class)
                          @ControllerAdvice
                          public class DecryptRequest extends RequestBodyAdviceAdapter {
                              
                              @Autowired
                              EncryptProperties encryptProperties;
                              @Override
                              public boolean supports(MethodParameter methodParameter, Type targetType, Class> converterType) {
                                  return methodParameter.hasMethodAnnotation(Decrypt.class) || methodParameter.hasParameterAnnotation(Decrypt.class);
                              }
                              @Override
                              public HttpInputMessage beforeBodyRead(final HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class> converterType) throws IOException {
                                  byte[] body = new byte[inputMessage.getBody().available()];
                                  inputMessage.getBody().read(body);
                                  try {
                                      byte[] decrypt = AESUtils.decrypt(body, encryptProperties.getKey().getBytes());
                                      final ByteArrayInputStream bais = new ByteArrayInputStream(decrypt);
                                      return new HttpInputMessage() {
                                          @Override
                                          public InputStream getBody() throws IOException {
                                              return bais;
                                          }
                                          @Override
                                          public HttpHeaders getHeaders() {
                                              return inputMessage.getHeaders();
                                          }
                                      };
                                  } catch (Exception e) {
                                      e.printStackTrace();
                                  }
                                  return super.beforeBodyRead(inputMessage, parameter, targetType, converterType);
                              }
                          }
                          

                          4、打包发布starter

                          4.1 定义自动化配置类

                          // 换成自己的包路径
                          @Configuration
                          @ComponentScan("com.example.encryption")
                          public class EncryptAutoConfiguration {
                          }
                          

                          最后,resources 目录下定义 META-INF,然后再定义 spring.factories 文件,这样当项目启动时,就会自动加载该配置类

                          org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.example.encryption.EncryptAutoConfiguration
                          

                          安装到本地仓库比较简单,直接 mvn install,或者在 IDEA 中,点击右边的 Maven,然后双击 install

                          4.2 发布线上使用

                          发不到线上我们可以使用 JitPack来做。首先我们在 GitHub 上创建一个仓库,将我们的代码上传上去,上传成功后,点击右边的 Create a new release 按钮,发布一个正式版

                          发布成功后,打开 jitpack,输入仓库的完整路径,点击 lookup 按钮,查找到之后,再点击 Get it 按钮完成构建,构建成功后,JitPack 上会给出项目引用方式,新建项目时引入即可

                          5、新项目使用

                          创建实体类

                          public class User {
                              private Long id;
                              private String username;
                              //省略 getter/setter
                          }
                          

                          创建测试类,第一个接口使用了 @Encrypt 注解,所以会对该接口的数据进行加密(如果不使用该注解就不加密),第二个接口使用了 @Decrypt 所以会对上传的参数进行解密,注意 @Decrypt 注解既可以放在方法上也可以放在参数上。

                          @RestController
                          public class HelloController {
                              @GetMapping("/user")
                              @Encrypt
                              public RespBean getUser() {
                                  User user = new User();
                                  user.setId((long) 99);
                                  user.setUsername("javaboy");
                                  return RespBean.ok("ok", user);
                              }
                              @PostMapping("/user")
                              public RespBean addUser(@RequestBody @Decrypt User user) {
                                  System.out.println("user = " + user);
                                  return RespBean.ok("ok", user);
                              }
                          }
                          

                          参考文章

                          如何优雅的实现 SpringBoot 接口参数加密解密?

                          为什么使用 Java Cipher 要指定转换模式?

                          Hutool加密解密

                          【网络】java密码安全