springboot后端接口AES加密解密

一.结构:

加密方法,通过@ControllerAdvice扫描所有接口,对含有@EncryptResponse注解的类或者方法进行加密

import com.alibaba.fastjson.JSONObject;
import com.example.util.AESOperator;
import lombok.extern.slf4j.Slf4j;
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.http.server.ServletServerHttpRequest;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import javax.servlet.http.HttpServletRequest;

/**
 * 请求响应处理类
 * 对加了@Encrypt的方法的数据进行加密操作
 */
@ControllerAdvice
@Slf4j
public class EncryptResponseBodyAdvice implements ResponseBodyAdvice<Object> {

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        //在这里调用needEncrypet方法判断是否需要加密
        return new NeedCrypto().needEncrypt(returnType);
    }

    //这个方法截取了接口中返回的对象,在对对象加密后返回
    @Override
    public Object beforeBodyWrite(Object obj, MethodParameter returnType, MediaType selectedContentType,
                                  Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest serverHttpRequest,
                                  ServerHttpResponse serverHttpResponse) {
        // 通过 ServerHttpRequest的实现类ServletServerHttpRequest 获得HttpServletRequest
        ServletServerHttpRequest sshr = (ServletServerHttpRequest) serverHttpRequest;
        // 此处获取到request 是为了取到在拦截器里面设置的一个对象 是我项目需要,可以忽略
        HttpServletRequest request = sshr.getServletRequest();

        String returnData;
        String realData = "";
        JSONObject data = new JSONObject();
        try {
            data = (JSONObject) JSONObject.toJSON(obj);
            if(!data.get("data").toString().isEmpty()){
                realData = data.get("data").toString();
            }
            // 添加encry header,告诉前端数据已加密
            serverHttpResponse.getHeaders().add("encrypt", "true");
            // 加密
            returnData = AESOperator.replace(AESUtil.encrypt(realData));

            log.debug("接口={},原始数据={},加密后数据={}", request.getRequestURI(), realData, returnData);
            data.put("data", returnData);
        } catch (Exception e) {
            log.error("异常!", e);
        }
        return data;
    }
}

解密方法,通过@ControllerAdvice扫描所有接口,对含有@DecryptRequest注解的类或者方法进行解密

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;

import java.io.IOException;
import java.lang.reflect.Type;

/**
 * 请求数据接收处理类
 * 对加了@Decrypt的方法的数据进行解密操作
 */
@ControllerAdvice
@Slf4j
public class DecryptRequestBodyAdvice implements RequestBodyAdvice {

    //needDecrypt判断是否需要解密
    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType,
                            Class<? extends HttpMessageConverter<?>> converterType) {
        return new NeedCrypto().needDecrypt(methodParameter);
    }

    @Override
    public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter,
                                  Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return body;
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
                                           Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        return inputMessage;
    }

    //拦截接口中的入参,对入参进行解密后返回
    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
                                Class<? extends HttpMessageConverter<?>> converterType) {
        JSONObject dealData = new JSONObject();
        try {
            // 解密操作
            JSONObject encryptObject = (JSONObject) JSONObject.toJSON(body);
            String srcData = String.valueOf(encryptObject.get("encryptData"));
            String decryptData = AESUtil.decrypt(srcData);
            dealData = JSON.parseObject(decryptData);
        } catch (Exception e) {
            log.error("请求body参数格式解析异常!", e);
        }
        return dealData;
    }
}

NeedCrypto类,判断是否需要进行加解密,类中是否需要加解密的逻辑可以根据需要进行修改,

目前代码中的逻辑为:注解在类上加密解密整个类,在方法是加密解密单个方法

import com.example.annotation.DecryptRequest;
import com.example.annotation.EncryptResponse;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.MethodParameter;

import javax.annotation.PostConstruct;

/**
 * 判断是否需要加解密
 */
@Configuration
@Log4j2
public class NeedCrypto {

    //开关变量,在配置文件中配置是否开启加解密功能,不需要可以去掉
    @Value("${api.encrypt}")
    private boolean encryptApiFlag;

    private static boolean flag = false;

    @PostConstruct
    public void NeedCrypto() {
        flag = encryptApiFlag;
    }

    /**
     * 是否需要对结果加密
     * 1.类上标注或者方法上标注,并且都为true
     * 2.有一个标注为false就不需要加密
     */
    public boolean needEncrypt(MethodParameter returnType) {
        boolean encrypt = false;
        if (flag) {
            boolean classPresentAnno = returnType.getContainingClass().isAnnotationPresent(EncryptResponse.class);
            boolean methodPresentAnno = returnType.getMethod().isAnnotationPresent(EncryptResponse.class);

            if (classPresentAnno) {
                //类上标注的是否需要加密
                encrypt = returnType.getContainingClass().getAnnotation(EncryptResponse.class).value();
                if (encrypt) {
                    return encrypt;
                }
                //类不加密,所有都不加密
                /**if(!encrypt){
                 return false;
                 }*/
            }
            if (methodPresentAnno) {
                //方法上标注的是否需要加密
                encrypt = returnType.getMethod().getAnnotation(EncryptResponse.class).value();
            }
        }
        return encrypt;
    }

    /**
     * 是否需要参数解密
     * 1.类上标注或者方法上标注,并且都为true
     * 2.有一个标注为false就不需要解密
     */
    public boolean needDecrypt(MethodParameter parameter) {
        boolean encrypt = false;
        if (flag) {
            boolean classPresentAnno = parameter.getContainingClass().isAnnotationPresent(DecryptRequest.class);
            boolean methodPresentAnno = parameter.getMethod().isAnnotationPresent(DecryptRequest.class);

            if (classPresentAnno) {
                //类上标注的是否需要解密
                encrypt = parameter.getContainingClass().getAnnotation(DecryptRequest.class).value();
                if (encrypt) {
                    return encrypt;
                }
                //类不加密,所有都不加密
                /**if (!encrypt) {
                 return false;
                 }*/
            }
            if (methodPresentAnno) {
                //方法上标注的是否需要解密
                encrypt = parameter.getMethod().getAnnotation(DecryptRequest.class).value();
            }
        }
        return encrypt;
    }
}

encryptApiFlag变量为开关变量,在配置文件中配置加解密是否生效

yml配置文件:

api:
  encrypt: true

AESUtil 加解密工具类,这里可以换成自己需要的加密方法

public class AESUtil {

    /**
     * 加密用的Key 可以用26个字母和数字组成 此处使用AES-128-CBC加密模式,key需要为16位。
     */
    public static final String skey = "smkldospd121daaa";

    /**
     * 加密偏移量
     */
    private static final String ivParameter = "1016449182184177";

    public static String encrypt(String srcData) throws Exception {
        String enString = AESOperator.getInstance().Encrypt(srcData, skey, ivParameter);
        return enString;
    }

    public static String decrypt(String srcData) throws Exception {
        String DeString = AESOperator.getInstance().Decrypt(srcData, skey, ivParameter);
        return DeString;
    }
}

 

二.自定义注解:

添加在类或方法中判断接口是否需要加解密

@DecryptRequest (解密)

@EncryptResponse (加密)

 

import java.lang.annotation.*;

/**
 * 解密注解
 *
 * 加了此注解的接口(true)将进行数据解密操作(post的body)
 *
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DecryptRequest {
    /**
     * 是否对body进行解密
     */
    boolean value() default true;
}
import java.lang.annotation.*;

/**
 * 加密注解
 *
 * 加了此注解的接口(true)将进行数据加密操作
 *
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EncryptResponse {
    /**
     * 是否对结果加密
     */
    boolean value() default true;
}

三.AES加密

加解密操作类,本文使用的是AES,可以换成自己需要的加解密方式、

 

import org.springframework.util.StringUtils;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

/**
 * AES加解密操作类
 */
public class AESOperator {

    /**
     * 加密用的Key 可以用26个字母和数字组成 此处使用AES-128-CBC加密模式,key需要为16位。
     */
    public static final String skey = "smkldospd121daaa";
    /**
     * 偏移量,可自行修改
     */
    private static String ivParameter = "1016449182184177";

    private static AESOperator instance = null;

    private AESOperator() {

    }

    public static AESOperator getInstance() {
        if (instance == null) {
            instance = new AESOperator();
        }
        return instance;
    }

    public String Encrypt(String encData, String secretKey, String vector) throws Exception {

        if (secretKey == null) {
            return null;
        }
        if (secretKey.length() != 16) {
            return null;
        }
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        byte[] raw = secretKey.getBytes();
        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
        // 使用CBC模式,需要一个向量iv,可增加加密算法的强度
        IvParameterSpec iv = new IvParameterSpec(vector.getBytes());
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
        byte[] encrypted = cipher.doFinal(encData.getBytes("utf-8"));
        // 此处使用BASE64做转码。
        return new BASE64Encoder().encode(encrypted);
    }

    /**
     * 加密
     *
     * @param sSrc
     * @param sKey
     * @return
     * @throws Exception
     */
    public String encrypt(String sSrc, String sKey) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        byte[] raw = sKey.getBytes();
        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
        // 使用CBC模式,需要一个向量iv,可增加加密算法的强度
        IvParameterSpec iv = new IvParameterSpec(ivParameter.getBytes());
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
        byte[] encrypted = cipher.doFinal(sSrc.getBytes("utf-8"));
        // 此处使用BASE64做转码。
        return replace(new BASE64Encoder().encode(encrypted));
    }

    /**
     * 解密
     *
     * @param sSrc
     * @param sKey
     * @return
     */
    public String decrypt(String sSrc, String sKey) {
        try {
            byte[] raw = sKey.getBytes("ASCII");
            SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            IvParameterSpec iv = new IvParameterSpec(ivParameter.getBytes());
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
            // 先用base64解密
            byte[] encrypted1 = new BASE64Decoder().decodeBuffer(sSrc);
            byte[] original = cipher.doFinal(encrypted1);
            String originalString = new String(original, "utf-8");
            return originalString;
        } catch (Exception ex) {
            return null;
        }
    }

    public String Decrypt(String sSrc, String key, String ivs) {
        try {
            byte[] raw = key.getBytes("ASCII");
            SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            IvParameterSpec iv = new IvParameterSpec(ivs.getBytes());
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
            // 先用base64解密
            byte[] encrypted1 = new BASE64Decoder().decodeBuffer(sSrc);
            byte[] original = cipher.doFinal(encrypted1);
            String originalString = new String(original, "utf-8");
            return originalString;
        } catch (Exception ex) {
            return null;
        }
    }

    public static String encodeBytes(byte[] bytes) {
        StringBuffer strBuf = new StringBuffer();

        for (int i = 0; i < bytes.length; i++) {
            strBuf.append((char) (((bytes[i] >> 4) & 0xF) + ((int) 'a')));
            strBuf.append((char) (((bytes[i]) & 0xF) + ((int) 'a')));
        }

        return strBuf.toString();
    }

    /**
     * 去除 换行符、制表符
     *
     * @param str
     * @return
     */
    public static String replace(String str) {
        if (!StringUtils.isEmpty(str)) {
            return str.replaceAll("\r|\n", "");
        }
        return str;
    }

    //测试
    public static void main(String[] args) throws Exception {
        // 需要加密的字串
        String cSrc = "{\"loginName\":\"master\",\"secret\":\"123456\"}";
        System.out.println(cSrc);
        // 加密
        String enString = AESOperator.getInstance().Encrypt(cSrc, skey, ivParameter);
        System.out.println("加密后的字串是:" + replace(enString));
        String test = replace(enString);

        // 解密
        String DeString = AESOperator.getInstance().Decrypt(test, skey, ivParameter);
        System.out.println("解密后的字串是:" + DeString);
    }
}

四.实战

注解加在类或者方法上就可以使用啦(类的优先级高于方法)

解密使用时入参不可以直接写实体类,需要先用json接收,使用实体类无法存放加密后的数据

@RestController
public class TestController {

    @DecryptRequest
    @EncryptResponse
    @PostMapping("/test")
    public ApiResult test(@RequestBody JSONObject params) {
        Menu menu = params.toJavaObject(Menu.class);
        return menuService.addMenu(menu);
    }
}
 
   

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值