Cifrado y descifrado de la interfaz SpringBoot

1. Cifrado simétrico/asimétrico

1. Introducción

El cifrado simétrico tiene una sola clave, y se utiliza la misma clave para el cifrado y el descifrado, por lo que se denomina cifrado simétrico.

El cifrado asimétrico tiene dos claves secretas, una es la clave pública y la otra es la clave privada. La característica de la asimetría es que la clave privada cifrada por la clave pública puede descifrarse, pero la clave pública no puede ser descifrada por la clave privada, y solo se puede verificar si está cifrada por la clave privada.

En la actualidad, existen dos métodos de cifrado comunes, uno es el cifrado simétrico (representado por AES) y el otro es el cifrado asimétrico (representado por RSA)

2. Introducción a RSA y AES

2.1 RSA

Características : solo es necesario intercambiar claves públicas; mecanismo de clave pública/secreta, cifrado de clave pública, descifrado de clave privada (o cifrado de clave privada, descifrado de clave pública); la clave pública es responsable del cifrado, la clave privada es responsable del descifrado; la clave privada es responsable de la firma, la clave pública es responsable de verificar

Desventajas : cifrado y descifrado lentos, especialmente descifrado

2.2 AES

Características : utilice la misma clave para cifrar y descifrar

Ventajas : alta velocidad y alta eficiencia;

Desventajas : Problemas con el intercambio de llaves

3. Combinación RSA/AES

La ventaja del cifrado simétrico (AES) es que el cifrado es más rápido, pero la mayor desventaja es que una vez que se entrega la clave, no es seguro . La ventaja del cifrado asimétrico (RSA) es la seguridad , incluso si se proporciona la clave pública, otros no pueden descifrar los datos, pero la desventaja es que la velocidad de cifrado es lenta.

En el proceso de uso real, los dos se usan a menudo en combinación (AES+RSA) , lo que puede transmitir de forma segura la clave AES y evitar la lentitud del cifrado RSA.

  • Generar una cadena de clave AES aleatoria
  • Use la clave pública RSA para cifrar la clave AES y luego use la clave AES para cifrar el contenido real
  • Pase el contenido cifrado por skey = clave AES cifrada y cuerpo = clave AES
  • Por otro lado, use la clave privada RSA para descifrar la clave AES y luego use la clave AES para descifrar el contenido

4. El papel de la codificación Base64

Es posible que los datos cifrados no se puedan leer , por lo que generalmente necesitamos codificar los datos cifrados utilizando el algoritmo Base64 para obtener una cadena legible . En otras palabras, el valor de retorno del método de cifrado AES o RSA es una cadena codificada en Base 64, y el parámetro del método de descifrado AES o RSA también es una cadena codificada en Base 64. La cadena primero se decodifica y luego se descifra.

2. Java implementa el cifrado y descifrado/verificación de firma

1. Configuración 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. Cifrado asimétrico 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 salida

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

3. Cifrado simétrico 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 salida

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

3. Combate real de inicio de cifrado y descifrado

1. Introducción

El cifrado y el descifrado en sí no son difíciles, la pregunta es cuándo lidiar con eso. También es una forma de definir un filtro para interceptar la solicitud y la respuesta por separado para su procesamiento.Aunque este método es rudimentario, es flexible porque puede obtener parámetros de solicitud y datos de respuesta de primera mano. Sin embargo, SpringMVC nos proporciona ResponseBodyAdvicey RequestBodyAdvice, el uso de estas dos herramientas puede preprocesar la solicitud y la respuesta, lo cual es muy conveniente.

Referencia:
Cifrado híbrido RSA+AES-arrancador personalizado JavaWeb SpringBoot

2. Preparación

2.1 Introducción de dependencias

Debido a que nuestra herramienta está desarrollada para proyectos web, debe usarse en el entorno web en el futuro, por lo que al agregar dependencias aquí, establezca el alcance en proporcionado

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

Introducción a varios atributos de alcance

  • compilar: el valor predeterminado significa que el proyecto dependiente debe participar en la compilación del proyecto actual, así como en las pruebas posteriores, y también está involucrado el ciclo de operación, que es una dependencia relativamente fuerte. Por lo general, debe incluirse al empaquetar
  • prueba: los proyectos dependientes solo participan en el trabajo relacionado con la prueba, incluida la compilación y ejecución del código de prueba, y no se empaquetarán, por ejemplo: junit
  • runtime: Indica que el proyecto dependiente no necesita participar en la compilación del proyecto, pero sí se requiere su participación para posteriores ciclos de prueba y ejecución. En comparación con compile, la compilación se omite. Como el controlador JDBC, adecuado para la fase de ejecución y prueba
  • provisto: No necesita incluirlo al empacar, otras instalaciones lo proporcionarán. De hecho, la dependencia teóricamente puede participar en la compilación, prueba, ejecución y otros ciclos. Es equivalente a compilar, pero la operación de exclusión se realiza en la fase de empaquetado.
  • sistema: en términos de participación, es lo mismo que se proporciona, pero los elementos dependientes no se descargarán del repositorio de Maven, sino que se tomarán del sistema de archivos local. La propiedad de systemPath debe agregarse para definir la ruta

2.2 Encapsulando clases públicas correspondientes

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 la clase de herramienta de cifrado y descifrado

Hay muchas opciones para el cifrado, como el cifrado simétrico y el cifrado asimétrico, entre ellos, el cifrado simétrico puede usar diferentes algoritmos como AES, DES, 3DES, etc. 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 dos anotaciones

A continuación definimos dos anotaciones @Decrypty @Encrypt. En el proceso de uso posterior, qué método de interfaz agrega la anotación @Encrypt, los datos de la interfaz se cifran y devuelven, y qué interfaz/parámetro se agrega con la anotación @Decrypt, qué interfaz/parámetro se descifra. Además, se @Decryptpuede utilizar en parámetros

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

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

2.5 Establecer clave personalizada

Defina una clase EncryptProperties para leer la clave configurada por el usuario, de modo que la clave se pueda personalizar. El valor predeterminado se establece aquí.Si el usuario desea configurar la clave por sí mismo en el futuro, solo necesita configurarla en 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. Cifrado y descifrado de interfaz

3.1 Introducción

ResponseBodyAdviceSolo tendrá efecto cuando use @ResponseBodyanotaciones, y solo tendrá efecto cuando RequestBodyAdviceuse @RequestBodyanotaciones. En otras palabras, estos dos solo son útiles cuando los extremos frontal y posterior interactúan con JSON.

3.2 Cifrado de interfaz

Nuestra EncryptResponse clase personalizada implementa ResponseBodyAdvicela interfaz, y el tipo genérico representa el tipo de retorno de la interfaz Hay dos métodos para implementar aquí.

  • support : este método se utiliza para determinar qué tipo de interfaz debe cifrarse. El parámetro returnType indica el tipo de retorno. Nuestra lógica de juicio aquí es si el método contiene anotaciones. Si las hay, significa que la interfaz debe cifrarse @Encrypt. Si no, significa que la interfaz no necesita estar encriptada.
  • beforeBodyWrite : este método se ejecutará antes de la respuesta de datos, es decir, primero procesamos los datos de respuesta dos veces y, una vez que se completa el procesamiento, se convertirá en json y se devolverá. Nuestro método de procesamiento aquí es muy simple. Si el estado en RespBean es el código de estado, no hay necesidad de cifrarlo. Los otros dos campos se pueden volver a cifrar y luego restablecer el valor.

También tenga en cuenta que el ResponseBodyAdvice personalizado debe marcarse @ControllerAdvicecon anotaciones.

@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 Descifrado de la interfaz

En primer lugar, tenga en cuenta que la clase DecryptRequest no implementa directamente RequestBodyAdvicela interfaz, sino que hereda de la clase RequestBodyAdviceAdapter, que es una subclase de la interfaz RequestBodyAdvice e implementa algunos métodos en la interfaz, de modo que cuando heredamos de RequestBodyAdviceAdapter, solo necesidad de Algunos métodos se pueden implementar de acuerdo con las necesidades reales.

  • admite@Decrypt : este método se usa para determinar qué interfaces necesitan manejar el descifrado de la interfaz. Nuestra lógica de juicio aquí es tratar los problemas de descifrado para las interfaces con anotaciones en los métodos o parámetros .
  • beforeBodyRead : este método se ejecutará antes de que el parámetro se convierta en un objeto específico. Primero cargamos los datos de la transmisión, luego desciframos los datos y luego reconstruimos el objeto HttpInputMessage para regresar después de que se complete el descifrado.
@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. Empaque y suelte el arrancador

4.1 Definir la clase de configuración automática

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

Finalmente, resources defina -INF en el directorio METAy luego defina spring.factoriesel archivo, de modo que cuando se inicie el proyecto, la clase de configuración se cargará automáticamente

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

La instalación en el almacén local es relativamente simple y directa mvn install, o en IDEA, haga clic en Maven a la derecha y luego haga doble clic en instalar

4.2 Publicar para uso en línea

Podemos usar JitPack para hacerlo si no se puede enviar en línea. Primero, creamos un almacén en GitHub y cargamos nuestro código allí. Después de que la carga sea exitosa, haga clic en el Create a new releasebotón a la derecha para lanzar una versión oficial.

Después de que el lanzamiento sea exitoso, abra jitpack , ingrese la ruta completa del almacén, haga clic en el botón de búsqueda y, después de encontrarlo, haga clic en Get itel botón para completar la compilación. Una vez que la compilación sea exitosa, el método de referencia del proyecto se proporcionará en JitPack , que se puede importar al crear un nuevo proyecto

5. Aprovechamiento de nuevos proyectos

Crear clases de entidad

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

Cree una clase de prueba, la primera interfaz usa @Encryptanotaciones, por lo que se cifrarán los datos de la interfaz (si no se usa la anotación, no se cifrará), la segunda interfaz se usa, por lo que se @Decryptdescifrarán los parámetros cargados, pague atención a @Decryptlas anotaciones Se puede colocar en el método también se puede colocar en los 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);
    }
}

artículo de referencia

¿Cómo implementar elegantemente el cifrado y descifrado de los parámetros de la interfaz SpringBoot?

¿Por qué usar Java Cipher para especificar el modo de conversión?

Cifrado y descifrado Hutool

[Red] seguridad de contraseña java

Supongo que te gusta

Origin blog.csdn.net/qq_43842093/article/details/132009346
Recomendado
Clasificación