客户需求之SpringBoot接口加密解密统一处理

发布时间:2023年12月27日

1、业务需求梳理

  • 设备需要与后台对接,采取http请求的方式。客户对于数据保密性有一定考虑,因此提出设备与后台交互时的敏感数据需要加密。

2、业务代码逻辑

为了减少项目代码的变动,采用自定义注解的方案。 逻辑如下

  • 自定义两个注解,加密注解和解密注解。
  • 选定加密方式。
  • 重写请求体到controller层的某个方法,用于实现解密逻辑。
  • 重写响应体返回客户端之前的某个方法,用于实现加密逻辑。
  • 将加密逻辑与加密注解绑定,解密逻辑与解密注解绑定。

3、具体代码实现

(1) 选定加解密的方式,这里采用AES的加密方式,加密算法是AES/CBC/PKCS5Padding,偏移量是0000000000000000

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

public class AESUtils {

    // 设置采用的加密算法
    private static final String AES_ALGORITHM = "AES/CBC/PKCS5Padding";
    
	// 设置加解密偏移量
    private static final byte[] IV_PARAMETER = "0000000000000000".getBytes();  
    
    // 获取 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, new IvParameterSpec(IV_PARAMETER));
        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 {
        String str = new String(data);
        String trim = str.trim();
        Cipher cipher = getCipher(key, Cipher.DECRYPT_MODE);
        return cipher.doFinal(Base64.getDecoder().decode(trim.getBytes()));
    }

    public static void main(String[] args) throws Exception {
        String enStr = "";
        String encrypt = encrypt(enStr.getBytes(), "1a2d3m4i5n6.far.".getBytes());
        System.out.println("encrypt = " + encrypt);
    }

}

(2) 设定加密采用的密钥,这里采用yml配置方式

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "spring.encrypt")
public class EncryptProperties {
    
    private final static String DEFAULT_KEY = "uniwinnnL$$linkcom.";
    
    private String key = DEFAULT_KEY;

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }
}

(3) 定义用于加密和解密的注解,之后将注解与对应的加解密类进行对应

  • 解密注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

  • 加密注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

(4) 定义具体的加密类和解密类

  • 请求解密类,通过继承 RequestBodyAdviceAdapter类,重写beforeBodyRead方法来拦截响应体,从而实现对数据的加密,重写supports方法,实现加密类与注解的绑定。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;

@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 = StreamUtils.copyToByteArray(inputMessage.getBody());
        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);
    }
}

  • 响应加密类,实现ResponseBodyAdvice接口,重写beforeBodyWrite方法,编写对应的加密逻辑
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.util.HashMap;

@EnableConfigurationProperties(EncryptProperties.class)
@ControllerAdvice
public class EncryptResponse implements ResponseBodyAdvice<HashMap<String, Object>> {
    
    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 HashMap<String, Object> beforeBodyWrite(HashMap<String, Object> body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        // 获取加密密钥
        byte[] keyBytes = encryptProperties.getKey().getBytes();
        try {
            // 对parameterInfo字段对应的值加密 
            if (body.get("parameterInfo")!=null) {
                body.put("parameterInfo",AESUtils.encrypt(om.writeValueAsBytes(body.get("parameterInfo")), keyBytes));
            }
            if (body.get("passInfo")!=null) {
                body.put("passInfo",AESUtils.encrypt(om.writeValueAsBytes(body.get("passInfo")), keyBytes));
            }
            if (body.get("deleteInfo")!=null) {
                body.put("deleteInfo",AESUtils.encrypt(om.writeValueAsBytes(body.get("deleteInfo")), keyBytes));
            }
            if (body.get("severPath")!=null) {
                body.put("severPath",AESUtils.encrypt(body.get("severPath").toString().getBytes(), keyBytes));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return body;
    }
}

(5) 使用时,在对应的接口上添加@Encrypt或者@Decrypt注解即可

@Encrypt
@Decrypt
@ResponseBody
@RequestMapping(value = "/insertNoteFace", method = RequestMethod.POST, produces = "application/json;charset=UTF-8")
public HashMap<String, Object> insertNote(@RequestBody JSONObject jsonParam,HttpServletRequest req) {

}
文章来源:https://blog.csdn.net/qq_45110186/article/details/135210043
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。