Criptografia e descriptografia da interface SpringBoot

1. Criptografia simétrica/assimétrica

1. Introdução

A criptografia simétrica possui apenas uma chave e a mesma chave é usada para criptografar e descriptografar, por isso é chamada de criptografia simétrica.

A criptografia assimétrica possui duas chaves secretas, uma é a chave pública e a outra é a chave privada. A característica da assimetria é que a chave privada criptografada pela chave pública pode ser descriptografada, mas a chave pública não pode ser descriptografada pela chave privada e somente se ela é criptografada pela chave privada pode ser verificada.

Atualmente, existem dois métodos de criptografia comuns, um é a criptografia simétrica (representada por AES) e a outra é a criptografia assimétrica (representada por RSA).

2. Introdução ao RSA e AES

2.1 RSA

Características : só precisa trocar chaves públicas; mecanismo de chave pública/secreta, criptografia de chave pública, descriptografia de chave privada (ou criptografia de chave privada, descriptografia de chave pública); chave pública é responsável pela criptografia, chave privada é responsável pela descriptografia; chave privada é responsável pela assinatura, a chave pública é responsável por verificar

Desvantagens : criptografia e descriptografia lentas, especialmente descriptografia

2.2 AES

Recursos : use a mesma chave para criptografia e descriptografia

Vantagens : velocidade rápida e alta eficiência;

Contras : Problemas de troca de chaves

3. Combinação RSA/AES

A vantagem da criptografia simétrica (AES) é que a criptografia é mais rápida, mas a maior desvantagem é que, uma vez que a chave é fornecida, ela não é segura . A vantagem da criptografia assimétrica (RSA) é a segurança . Mesmo que a chave pública seja fornecida, outros não podem descriptografar os dados, mas a desvantagem é que a velocidade da criptografia é lenta

No processo de uso real, os dois são frequentemente usados ​​em combinação (AES+RSA) , que pode transmitir com segurança a chave AES e evitar a baixa velocidade da criptografia RSA

  • Gere uma string de chave AES aleatória
  • Use a chave pública RSA para criptografar a chave AES e, em seguida, use a chave AES para criptografar o conteúdo real
  • Passe o conteúdo criptografado por skey=chave AES criptografada e body=chave AES
  • Por outro lado, use a chave privada RSA para descriptografar a chave AES e, em seguida, use a chave AES para descriptografar o conteúdo

4. O papel da codificação Base64

Os dados criptografados podem não ser legíveis , então geralmente precisamos codificar os dados criptografados usando o algoritmo Base64 para obter uma string legível . Em outras palavras, o valor de retorno do método de criptografia AES ou RSA é uma string codificada em Base64, e o parâmetro do método de descriptografia AES ou RSA também é uma string codificada em Base64. A string é primeiro decodificada e depois descriptografada.

2. Java implementa criptografia e descriptografia/verificação de assinatura

1. Configuração Global

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. Criptografia assimétrica 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));
    }
}

resultado de saída

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

3. Criptografia simétrica 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);
    }
}

resultado de saída

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

3. Combate real inicial de criptografia e descriptografia

1. Introdução

A criptografia e a descriptografia em si não são difíceis, a questão é quando lidar com isso? É também uma forma de definir um filtro para interceptar a requisição e a resposta separadamente para processamento. Embora este método seja rudimentar, é flexível porque você pode obter em primeira mão os parâmetros da requisição e os dados da resposta. No entanto, o SpringMVC nos fornece ResponseBodyAdvicee RequestBodyAdvice, usando essas duas ferramentas, podemos pré-processar a solicitação e a resposta, o que é muito conveniente.

Referência:
Criptografia híbrida RSA+AES-JavaWeb SpringBoot custom starter

2. Preparação

2.1 Apresentando dependências

Como nossa ferramenta é desenvolvida para projetos Web, futuramente ela deverá ser utilizada no ambiente Web, portanto, ao adicionar dependências aqui, defina o escopo como fornecido

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <scope>provided</scope>
    <version>2.7.0</version>
</dependency>

Introdução a vários atributos de escopo

  • compile: O valor padrão significa que o projeto dependente precisa participar da compilação do projeto atual, bem como dos testes subsequentes, e o ciclo de operação também está envolvido, o que é uma dependência relativamente forte. Geralmente precisam ser incluídos ao embalar
  • test: Projetos dependentes participam apenas de trabalhos relacionados a testes, incluindo compilação e execução de código de teste, e não serão empacotados, por exemplo: junit
  • runtime: Indica que o projeto dependente não precisa participar da compilação do projeto, mas sua participação é necessária para testes posteriores e ciclos de execução. Em comparação com a compilação, a compilação é ignorada. Como o driver JDBC, adequado para execução e fase de teste
  • fornecido: você não precisa incluí-lo ao embalar, outras instalações o fornecerão. Na verdade, a dependência pode teoricamente participar da compilação, teste, execução e outros ciclos. Equivale a compilar, mas a operação de exclusão é feita na fase de empacotamento
  • sistema: Em termos de participação, é o mesmo fornecido, mas os itens dependentes não serão baixados do repositório maven, mas retirados do sistema de arquivos local. A propriedade de systemPath precisa ser adicionada para definir o caminho

2.2 Encapsulando classes correspondentes públicas

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 Definir a classe de ferramenta de criptografia e descriptografia

Existem muitas opções de criptografia, como criptografia simétrica e criptografia assimétrica. Entre elas, a criptografia simétrica pode usar diferentes algoritmos, como AES, DES, 3DES, etc. Aqui usamos o Cipher que vem com o Java para implementar a criptografia simétrica, usando o algoritmo 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 Definir duas anotações

Em seguida, definimos duas anotações @Decrypte @Encrypt. No processo de uso posterior, qual método de interface adiciona a anotação @Encrypt, os dados da interface são criptografados e retornados e qual interface/parâmetro é adicionado com a anotação @Decrypt, qual interface/parâmetro é descriptografado. Além disso, @Decryptpode ser usado em parâmetros

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

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

2.5 Definir chave personalizada

Defina uma classe EncryptProperties para ler a chave configurada pelo usuário, para que a chave possa ser customizada. O valor padrão é definido aqui, caso o usuário queira configurar a chave sozinho no futuro, basta configurá-la em 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. Criptografia e descriptografia da interface

3.1 Introdução

ResponseBodyAdviceEle só terá efeito quando você usar @ResponseBodyanotações e só terá efeito quando RequestBodyAdvicevocê usar @RequestBodyanotações. Em outras palavras, esses dois são úteis apenas quando o front-end e o back-end estão interagindo com o JSON

3.2 Criptografia de interface

Nossa EncryptResponse classe personalizada implementa ResponseBodyAdvicea interface, e o tipo genérico representa o tipo de retorno da interface.Existem dois métodos a serem implementados aqui.

  • suporta : Este método é usado para determinar que tipo de interface precisa ser criptografada. O parâmetro returnType indica o tipo de retorno. Nossa lógica de julgamento aqui é se o método contém anotações. Se houver, significa que a interface precisa ser criptografada @Encrypt. Caso contrário, significa que a interface não precisa ser criptografada.
  • beforeBodyWrite : Este método será executado antes da resposta dos dados, ou seja, primeiro processamos os dados da resposta duas vezes, e depois que o processamento for concluído, ele será convertido em json e retornado. Nosso método de processamento aqui é muito simples. O status no RespBean é o código de status, portanto não há necessidade de criptografá-lo, e os outros dois campos podem ser criptografados novamente e redefinir o valor.

Observe também que o ResponseBodyAdvice personalizado precisa ser @ControllerAdvicemarcado com anotações.

@EnableConfigurationProperties(EncryptProperties.class)
@ControllerAdvice
public class EncryptResponse implements ResponseBodyAdvice<RespBean> {
    
    
    
    private ObjectMapper om = new ObjectMapper();
    
    @Autowired
    EncryptProperties encryptProperties;
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
    
    
        return returnType.hasMethodAnnotation(Encrypt.class);
    }


    @Override
    public RespBean beforeBodyWrite(RespBean body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> 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 Descriptografia da interface

Em primeiro lugar, observe que a classe DecryptRequest não implementa diretamente RequestBodyAdvicea interface, mas herda da classe RequestBodyAdviceAdapter, que é uma subclasse da interface RequestBodyAdvice e implementa alguns métodos na interface, de modo que quando herdamos de RequestBodyAdviceAdapter, apenas necessidade de Alguns métodos podem ser implementados de acordo com as necessidades reais.

  • suporta@Decrypt : Este método é usado para determinar quais interfaces precisam lidar com a descriptografia da interface. Nossa lógica de julgamento aqui é lidar com problemas de descriptografia para interfaces com anotações em métodos ou parâmetros .
  • beforeBodyRead : Este método será executado antes que o parâmetro seja convertido em um objeto específico. Primeiro carregamos os dados do fluxo, depois descriptografamos os dados e, em seguida, reconstruímos o objeto HttpInputMessage para retornar após a conclusão da descriptografia.
@EnableConfigurationProperties(EncryptProperties.class)
@ControllerAdvice
public class DecryptRequest extends RequestBodyAdviceAdapter {
    
    
    
    @Autowired
    EncryptProperties encryptProperties;
    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
    
    
        return methodParameter.hasMethodAnnotation(Decrypt.class) || methodParameter.hasParameterAnnotation(Decrypt.class);
    }

    @Override
    public HttpInputMessage beforeBodyRead(final HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> 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. Embale e libere o starter

4.1 Definir a classe de configuração automática

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

Por fim, resources defina -INF no diretório META, e depois defina spring.factorieso arquivo, para que quando o projeto iniciar, a classe de configuração seja carregada automaticamente

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

A instalação no warehouse local é relativamente simples e direta mvn installou, no IDEA, clique em Maven à direita e clique duas vezes em instalar

4.2 Publicar para uso online

Podemos usar o JitPack para fazer isso se não puder ser enviado online. Primeiro, criamos um warehouse no GitHub e carregamos nosso código nele. Depois que o upload for bem-sucedido, clique no Create a new releasebotão à direita para liberar uma versão oficial

Após o lançamento bem-sucedido, abra o jitpack , insira o caminho completo do warehouse, clique no botão de pesquisa e, após encontrá-lo, clique no Get itbotão para concluir a compilação. Após a compilação ser bem-sucedida, o método de referência do projeto será fornecido no JitPack , que pode ser importado ao criar um novo projeto

5. Utilização de novos projetos

Criar classe de entidade

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

Crie uma classe de teste, a primeira interface usa @Encryptanotações, então os dados da interface serão criptografados (se a anotação não for usada, ela não será criptografada), a segunda interface é usada, então @Decryptos parâmetros carregados serão descriptografados, pague atenção para @Decryptas anotações Pode ser colocado no método também pode ser colocado nos parâmetros.

@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);
    }
}

artigo de referência

Como implementar elegantemente a criptografia e descriptografia dos parâmetros da interface SpringBoot?

Por que usar Java Cipher para especificar o modo de conversão?

Hutool criptografia e descriptografia

[Rede] segurança de senha java

おすすめ

転載: blog.csdn.net/qq_43842093/article/details/132009346