基于Spring AOP切面实现请求入参出参加解密

1.Mavne导入加密解密所需的依赖

<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-io</artifactId>
  <version>1.3.2</version>
</dependency>

2.在aspect切面包下建立 入参切面与出参切面

DecodeRequestBodyAdvice.class  入参拦截 
流程:获取本次请求->判断是否包含加密/解密注解->对请求字符串解密->重构请求过来的加密字符串为实体类对象
package com.iliebe.web.aspect;


import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import com.icbc.api.internal.util.codec.Base64;
import com.iliebe.web.annotation.SecurityParameter;
import com.iliebe.web.constants.SystemConstant;
import com.iliebe.web.util.EncryptForPKCS7Util;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
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 org.apache.commons.io.IOUtils;

import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.security.NoSuchAlgorithmException;

/**
 * @Date: 2020/6/27 下午3:59
 * @Description: TODO(请求参数加密)
 */
@Slf4j
@ControllerAdvice(basePackages = "com.iliebe.web.controller")
public class DecodeRequestBodyAdvice implements RequestBodyAdvice {



    @Override
    public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return true;
    }

    @Override
    public Object handleEmptyBody(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return body;
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {
        try {
            boolean encode = false;
            String apiPath = "";
            // 方法是否 包含注解
            if (methodParameter.getMethod().isAnnotationPresent(SecurityParameter.class)) {
                //获取注解配置的包含和去除字段
                SecurityParameter serializedField = methodParameter.getMethodAnnotation(SecurityParameter.class);
                //入参是否需要解密
                encode = serializedField.inDecode();
                apiPath = serializedField.apiPath();
            }
            if (encode) {
                return new MyHttpInputMessage(inputMessage,apiPath);
            } else {
                return inputMessage;
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("方法method :【{}】参数解密出现异常:{}", methodParameter.getMethod().getName(), e.getMessage());
            return inputMessage;
        }
    }

    @Override
    public Object afterBodyRead(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return body;
    }

    class MyHttpInputMessage implements HttpInputMessage {

        private HttpHeaders headers;

        private InputStream body;

        public MyHttpInputMessage(HttpInputMessage inputMessage,String apiPath) throws Exception {
            this.headers = inputMessage.getHeaders();
            // 获取加密 json中 值
            String src = easpString(IOUtils.toString(inputMessage.getBody(), "UTF-8"));
            // 解密加密字符串
            String decryptStr = null;
            if (StringUtils.isBlank(apiPath)){
                decryptStr = EncryptForPKCS7Util.Decrypt(src, SystemConstant.AES_KEY);
            }
            if (StringUtils.isBlank(decryptStr)){
                throw new RuntimeException("参数【requestData】解密异常!");
            }
            this.body = IOUtils.toInputStream(decryptStr, "UTF-8");
        }

        @Override
        public InputStream getBody() {
            return body;
        }

        @Override
        public HttpHeaders getHeaders() {
            return headers;
        }

        /**
         * 获取requestData数据
         */
        public String easpString(String requestData) {
            if (StrUtil.isNotEmpty(requestData)) {
                JSONObject jsonObject = new JSONObject(requestData);
                String requestDataStr = jsonObject.getStr("requestData");
                if (StrUtil.isBlank(requestDataStr)) {
                    throw new RuntimeException("参数【requestData】缺失异常!");
                }
                return requestDataStr;
            }
            return "";
        }
    }

    public static String genAesSecret(){
        try {
            KeyGenerator kg = KeyGenerator.getInstance("AES");
            //下面调用方法的参数决定了生成密钥的长度,可以修改为128, 192或256
            kg.init(128);
            SecretKey sk = kg.generateKey();
            byte[] b = sk.getEncoded();
            String secret = Base64.encodeBase64String(b);
            return secret;
        }
        catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            throw new RuntimeException("没有此算法");
        }
    }

    public static void main(String[] args) {
        System.out.println(EncryptForPKCS7Util.Decrypt("jH+iZFzwlWyStzAfHaLW4eQ2MoStfSAg0hsXk6miSvfm7C+oRED1zSI1uT0wkYbX4Q0YeVqA7JM/kxC0kSrOJA3v3mByGBo8DmlS349DvpY=", "Q6Oxh8pknkr3/B+0ukKVqg=="));
    }


}
EncodeResponseBodyAdvice.class 出参拦截
流程:获取本次响应->判断是否包含加密/解密注解->对响应数据加密->返回响应数据为加密后的字符串
package com.iliebe.web.aspect;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.iliebe.web.annotation.SecurityParameter;
import com.iliebe.web.constants.SystemConstant;
import com.iliebe.web.util.EncryptForPKCS7Util;
import com.iliebe.web.util.JsonConfig;
import lombok.extern.slf4j.Slf4j;

import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
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;


/**
 * @Date: 2020/6/27 下午3:59
 * @Description: TODO(返回数据解密)
 */
@Slf4j
@ControllerAdvice(basePackages = "com.iliebe.web.controller")
public class EncodeResponseBodyAdvice implements ResponseBodyAdvice {

    @Override
    public boolean supports(MethodParameter methodParameter, Class aClass) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        boolean encode = false;
        if (methodParameter.getMethod().isAnnotationPresent(SecurityParameter.class)) {
            //获取注解配置的包含和去除字段
            SecurityParameter serializedField = methodParameter.getMethodAnnotation(SecurityParameter.class);
            //出参是否需要加密
            encode = serializedField.outEncode();
        }
        if (encode) {
            ObjectMapper objectMapper = JsonConfig.getObjectMapper();
            try {
                String result = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(body);
                return EncryptForPKCS7Util.Encrypt(result, SystemConstant.AES_KEY);
            } catch (Exception e) {
                e.printStackTrace();
                log.error("对方法method :【" + methodParameter.getMethod().getName() + "】返回数据进行解密出现异常:" + e.getMessage());
            }
        }
        return body;
    }
}

3.加密注解,加在controller层中需要加密/解密的接口

SecurityParameter 
package com.iliebe.web.annotation;

import org.springframework.web.bind.annotation.Mapping;

import java.lang.annotation.*;

/**
 * @Date: 2020/6/27 下午3:59
 * @Description: TODO(参数加密注解)
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Mapping
@Documented
public @interface SecurityParameter {

    /**
     * 入参是否解密,默认解密
     */
    boolean inDecode() default true;

    /**
     * 出参是否加密,默认加密
     */
    boolean outEncode() default true;

    /**
     * 方法路径名
     */
    String apiPath() default "";
}

4.配置类 配置Json数据编码为UTF-8

package com.iliebe.web.util;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.springframework.context.annotation.Configuration;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
 * 配置返回的json字符 key为字符串时null则不返回
 *
 * @Date: 2019/5/13 13:45
 */
@Configuration
public class JsonConfig {

    /**
     * 网络层需要标识gbk,utf-8
     * @return
     */
    public static ObjectMapper getObjectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        JavaTimeModule timeModule = new JavaTimeModule();
        timeModule.addDeserializer(LocalDate.class,
                new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        timeModule.addDeserializer(LocalDateTime.class,
                new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        timeModule.addSerializer(LocalDate.class,
                new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        timeModule.addSerializer(LocalDateTime.class,
                new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        objectMapper.registerModule(timeModule);
        return objectMapper;
    }
}

5.加密/解密方法

package com.iliebe.web.util;

import org.apache.commons.io.IOUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.Security;
import java.util.Base64;

/**
 * @Date: 2020/6/27 下午3:59
 * @Description: TODO()
 */
public class EncryptForPKCS7Util {

    private static final String SECRET = "AES";
    private static final String CIPHER_ALGORITHM = "AES/ECB/PKCS7Padding";

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

/** * AES加密ECB模式PKCS7Padding填充方式 * * @param str 字符串 * @param key 密钥 * @return 加密字符串 * @throws Exception 异常信息 */ public static String Encrypt(String str, String key) { try { Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8); cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(keyBytes, SECRET)); byte[] doFinal = cipher.doFinal(str.getBytes(StandardCharsets.UTF_8)); return new String(Base64.getEncoder().encode(doFinal)); } catch (Exception e) { e.printStackTrace(); } return ""; } /** * AES解密ECB模式PKCS7Padding填充方式 * * @param str 字符串 * @param key 密钥 * @return 解密字符串 * @throws Exception 异常信息 */ public static String Decrypt(String str, String key) { try { Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8); cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyBytes, SECRET)); byte[] doFinal = cipher.doFinal(Base64.getDecoder().decode(str)); return new String(doFinal); } catch (Exception e) { e.printStackTrace(); } return ""; } }

 

注意事项:

在Web项目中如果使用了全局异常拦截器,需要在全局异常捕获的地方也处理,如果是只针对某端的请求处理,可以通过请求header中加入标识,再根据标识判断是否加密/解密。

posted @ 2022-10-25 12:05  洋三岁  阅读(2312)  评论(0)    收藏  举报
友情链接: 梦想农夫