首页 > 其他分享 >基于Spring AOP切面实现请求入参出参加解密

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

时间:2022-10-25 12:11:18浏览次数:78  
标签:return String Spring org AOP import com public 参出

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中加入标识,再根据标识判断是否加密/解密。

标签:return,String,Spring,org,AOP,import,com,public,参出
From: https://www.cnblogs.com/sansui/p/16824424.html

相关文章

  • Springboot 一行代码实现文件上传 20个平台!少写代码到极致
    大家好,我是小富~技术交流,公众号:程序员小富又是做好人好事的一天,有个小可爱私下问我有没有好用的springboot文件上传工具,这不巧了嘛,正好我私藏了一个好东西,顺便给小伙伴......
  • Spring的循环依赖
    Spring循环依赖1循环依赖解决及概述(1)循环依赖的问题描述和Spring解决流程@ComponentpublicclassA{ @AutowiredprivateBb;}@ComponentpublicclassB{......
  • SpringCloud-04 Feign学习笔记
    @​​TOC​​一、什么是Feign?Feign是声明式WebService客户端,它让微服务之间的调用变得更简单,类似controller调用service。SpringCloud集成了Ribbon和Eureka,可以使用Feigin......
  • SpringCloud-05 Hystrix学习笔记
    @[Toc]一、Hystrix简介1、Hystrix是什么?流量高峰时,一个单节点的宕机或延迟,会迅速导致所有服务负载达到饱和。应用中任何一个可能通过网络访问其他服务的节点,都有可能成为......
  • www.cnblogs.com/huakexiaopeng
    对于这个问题,我觉得要看业务而定。我们都知道,独享HTTP代理池是一个人使用的IP池,纯净度极高,当然价格也更贵;共享HTTP代理池是很多用户同时在IP池里获取HTTP代理使用,价格......
  • SpringBoot(五)SpringBoot拦截器
    1.继承HandlerInterceptorAdapter,实现preHandle前置处理器,returntrue则进行后续处理,实现postHandle,afterCompletion后置处理。2.实现WebMvcConfigurer,SpringBoot2......
  • 记一次springboot项目自定义HandlerMethodArgumentResolver不生效原因与解法
    前言本文素材的来源自业务部门技术负责人一次代码走查引发的故事,技术负责人在某次走查成员的代码时,发现他们的业务控制层大量充斥着如下的代码@PostMapping("add")p......
  • Spring AOP 报错:Error creating bean with name 'student' defined in file
    问题概述SpringAOP报错,一直显示:Errorcreatingbeanwithname'student'definedinfile的报错。从五个方向排查:第一,aspectj包的scope是不是runtime;第二,jar包是......
  • SpringMvc学习——整合SSM
    1、创建项目结构   2、导入坐标<dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.2......
  • springmvc中web.xml模板
    myweborg.springframework.web.servlet.DispatcherServlet<init-param><param-name>contextConfigLocation</param-name><param-value>classpat......