使用注解+RequestBodyAdvice实现http请求内容加解密方式

注解主要用来指定那些需要加解密的controller方法

实现比较简单

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface SecretAnnotation {
    boolean encode() default false;
    boolean decode() default false;
}

使用时添加注解在controller的方法上

    @PostMapping("/preview")
    @SecretAnnotation(decode = true)
    public ResponseVO<ContractSignVO> previewContract(@RequestBody FillContractDTO fillContractDTO)  {
        return contractSignService.previewContract(fillContractDTO);
    }

请求数据由二进制流转为类对象数据,对于加密过的数据,需要在二进制流被处理之前进行解密,否则在转为类对象时会因为数据格式不匹配而报错。

因此使用RequestBodyAdvice的beforeBodyRead方法来处理。

@Slf4j
@RestControllerAdvice
public class MyRequestControllerAdvice implements RequestBodyAdvice {
    @Override
    public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return methodParameter.hasParameterAnnotation(RequestBody.class);
    }
    @Override
    public Object handleEmptyBody(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return o;
    }
    @Autowired
    private MySecretUtil mySecretUtil;
    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {
        if (methodParameter.getMethod().isAnnotationPresent(SecretAnnotation.class)) {
            SecretAnnotation secretAnnotation = methodParameter.getMethod().getAnnotation(SecretAnnotation.class);
            if (secretAnnotation.decode()) {
                return new HttpInputMessage() {
                    @Override
                    public InputStream getBody() throws IOException {
                        List<String> appIdList = httpInputMessage.getHeaders().get("appId");
                        if (appIdList.isEmpty()){
                            throw new RuntimeException("请求头缺少appID");
                        }
                        String appId = appIdList.get(0);
                        String bodyStr = IOUtils.toString(httpInputMessage.getBody(),"utf-8");
                        bodyStr = mySecretUtil.decode(bodyStr,appId);
                        return  IOUtils.toInputStream(bodyStr,"utf-8");
                    }
                    @Override
                    public HttpHeaders getHeaders() {
                        return httpInputMessage.getHeaders();
                    }
                };
            }
        }
        return httpInputMessage;
    }
    @Override
    public Object afterBodyRead(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return o;
    }
}

mySecretUtil.decode(bodyStr,appId)的内容是,通过请求头中的AppID去数据库中查找对于的秘钥,之后进行解密,返回解密后的字符串。

再通过common.io包中提供的工具类IOUtils将字符串转为inputstream流,替换HttpInputMessage,返回一个body数据为解密后的二进制流的HttpInputMessage。

Stringboot RequestBodyAdvice接口如何实现请求响应加解密

在实际项目中,我们常常需要在请求前后进行一些操作,比如:参数解密/返回结果加密,打印请求参数和返回结果的日志等。这些与业务无关的东西,我们不希望写在controller方法中,造成代码重复可读性变差。这里,我们讲讲使用@ControllerAdvice和RequestBodyAdvice、ResponseBodyAdvice来对请求前后进行处理(本质上就是AOP),来实现日志记录每一个请求的参数和返回结果。

1.加解密工具类

package com.linkus.common.utils;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Security;
import javax.annotation.PostConstruct;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Hex;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Aes {
    /**
     *
     * @author ngh
     * AES128 算法
     *
     * CBC 模式
     *
     * PKCS7Padding 填充模式
     *
     * CBC模式需要添加偏移量参数iv,必须16位
     * 密钥 sessionKey,必须16位
     *
     * 介于java 不支持PKCS7Padding,只支持PKCS5Padding 但是PKCS7Padding 和 PKCS5Padding 没有什么区别
     * 要实现在java端用PKCS7Padding填充,需要用到bouncycastle组件来实现
     */
    private String sessionKey="加解密密钥";
    // 偏移量 16位
    private static  String iv="偏移量";
    // 算法名称
    final String KEY_ALGORITHM = "AES";
    // 加解密算法/模式/填充方式
    final String algorithmStr = "AES/CBC/PKCS7Padding";
    // 加解密 密钥 16位
    byte[] ivByte;
    byte[] keybytes;
    private Key key;
    private Cipher cipher;
    boolean isInited = false;
    public void init() {
        // 如果密钥不足16位,那么就补足.  这个if 中的内容很重要
        keybytes = iv.getBytes();
        ivByte = iv.getBytes();
        Security.addProvider(new BouncyCastleProvider());
        // 转化成JAVA的密钥格式
        key = new SecretKeySpec(keybytes, KEY_ALGORITHM);
        try {
            // 初始化cipher
            cipher = Cipher.getInstance(algorithmStr, "BC");
        } catch (NoSuchAlgorithmException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (NoSuchProviderException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    /**
     * 加密方法
     *
     * @param content
     *            要加密的字符串
     *            加密密钥
     * @return
     */
    public String encrypt(String content) {
        byte[] encryptedText = null;
        byte[] contentByte = content.getBytes();
        init();
        try {
            cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(ivByte));
            encryptedText = cipher.doFinal(contentByte);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return new String(Hex.encode(encryptedText));
    }
    /**
     * 解密方法
     *
     * @param encryptedData
     *            要解密的字符串
     *            解密密钥
     * @return
     */
    public String decrypt(String encryptedData) {
        byte[] encryptedText = null;
        byte[] encryptedDataByte = Hex.decode(encryptedData);
        init();
        try {
            cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(ivByte));
            encryptedText = cipher.doFinal(encryptedDataByte);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return new String(encryptedText);
    }
    public static void main(String[] args) {
        Aes aes = new Aes();
        String a="{\n" +
                "\"distance\":\"1000\",\n" +
                "\"longitude\":\"28.206471\",\n" +
                "\"latitude\":\"112.941301\"\n" +
                "}";
        //加密字符串
        //String content = "孟飞快跑";
        // System.out.println("加密前的:" + content);
//        System.out.println("加密密钥:" + new String(keybytes));
        // 加密方法
        String enc = aes.encrypt(a);
        System.out.println("加密后的内容:" + enc);
        String dec="";
        // 解密方法
        try {
            dec = aes.decrypt(enc);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("解密后的内容:" + dec);
    }
}

2.请求解密

前端页面传过来的是密文,我们需要在Controller获取请求之前对密文解密然后传给Controller

package com.linkus.common.filter;
import com.alibaba.fastjson.JSON;
import com.linkus.common.constant.KPlatResponseCode;
import com.linkus.common.exception.CustomException;
import com.linkus.common.exception.JTransException;
import com.linkus.common.service.util.MyHttpInputMessage;
import com.linkus.common.utils.Aes;
import com.linkus.common.utils.http.HttpHelper;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.lang.reflect.Type;
/**
 * 请求参数 解密操作
 *
 * @Author: Java碎碎念
 * @Date: 2019/10/24 21:31
 *
 */
@Component
//可以配置指定需要解密的包,支持多个
@ControllerAdvice(basePackages = {"com.linkus.project"})
@Slf4j
public class DecryptRequestBodyAdvice implements RequestBodyAdvice {
    Logger log = LoggerFactory.getLogger(getClass());
    Aes aes=new Aes();
    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
    	//true开启功能,false关闭这个功能
        return true;
    }
//在读取请求之前做处理
    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> selectedConverterType) throws IOException {
        //获取请求数据
        String string = "";
        BufferedReader bufferedReader = null;
        InputStream inputStream = inputMessage.getBody();
            //这个request其实就是入参 可以从这里获取流
            //入参放在HttpInputMessage里面  这个方法的返回值也是HttpInputMessage
            try {
            string=getRequestBodyStr(inputStream,bufferedReader);
        } finally {
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (IOException ex) {
                    throw ex;
                }
            }
        }
        /*****************进行解密start*******************/
        String decode = null;
        if(HttpHelper.isEncrypted(inputMessage.getHeaders())){
        try {
            //            //解密操作
            //Map<String,String> dataMap = (Map)body;
            //log.info("接收到原始请求数据={}", string);
            // inputData 为待加解密的数据源
			//解密
            decode= aes.decrypt(string);
            //log.info("解密后数据={}",decode);
        } catch (Exception e ) {
            log.error("加解密错误:",e);
           throw  new CustomException(KPlatResponseCode.MSG_DECRYPT_TIMEOUT,KPlatResponseCode.CD_DECRYPT_TIMEOUT);
        }
        //把数据放到我们封装的对象中
        }else{
            decode = string;
        }
       // log.info("接收到请求数据={}", decode);
//        log.info("接口请求地址{}",((HttpServletRequest)inputMessage).getRequestURI());
        return new MyHttpInputMessage(inputMessage.getHeaders(), new ByteArrayInputStream(decode.getBytes("UTF-8")));
    }
    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return body;
    }
    @Override
    public Object handleEmptyBody(@Nullable Object var1, HttpInputMessage var2, MethodParameter var3, Type var4, Class<? extends HttpMessageConverter<?>> var5) {
        return var1;
    }
	//自己写的方法,不是接口的方法,处理密文
    public String getRequestBodyStr( InputStream inputStream,BufferedReader bufferedReader) throws IOException {
        StringBuilder stringBuilder = new StringBuilder();
            if (inputStream != null) {
                bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                char[] charBuffer = new char[128];
                int bytesRead = -1;
                while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
                    stringBuilder.append(charBuffer, 0, bytesRead);
                }
            } else {
                stringBuilder.append("");
            }
        String string = stringBuilder.toString();
        return string;
    }
}

3.响应加密

将返给前端的响应加密,保证数据的安全性

package com.linkus.common.filter;
import com.alibaba.fastjson.JSON;
import com.linkus.common.utils.Aes;
import com.linkus.common.utils.DesUtil;
import com.linkus.common.utils.http.HttpHelper;
import io.swagger.models.auth.In;
import lombok.experimental.Helper;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.stereotype.Component;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
 * 请求参数 加密操作
 *
 * @Author: Java碎碎念
 * @Date: 2019/10/24 21:31
 *
 */
@Component
@ControllerAdvice(basePackages = {"com.linkus.project"})
@Slf4j
public class EncryResponseBodyAdvice implements ResponseBodyAdvice<Object> {
    Logger log = LoggerFactory.getLogger(getClass());
    Aes aes=new Aes();
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object obj, MethodParameter returnType, MediaType selectedContentType,
                                  Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest serverHttpRequest,
                                  ServerHttpResponse serverHttpResponse) {
        String returnStr = "";
        Object retObj = null;
        log.info("接口请求地址{}",serverHttpRequest.getURI());
        //日志过滤
        //retObj=infofilter.getInfoFilter(returnType,obj);
        if(HttpHelper.isEncrypted(serverHttpRequest.getHeaders())) {
            try {
                //添加encry header,告诉前端数据已加密
                //serverHttpResponse.getHeaders().add("infoe", "e=a");
                //获取请求数据
                String srcData = JSON.toJSONString(obj);
                //加密
                returnStr = aes.encrypt(srcData).replace("\r\n", "");
                //log.info("原始数据={},加密后数据={}", obj, returnStr);
                return returnStr;
            } catch (Exception e) {
                log.error("异常!", e);
            }
        }
        log.info("原始数据={}",JSON.toJSONString(obj));
        return obj;
    }
}

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Spring MVC请求参数与响应结果全局加密和解密详解

    前提 前段时间在做一个对外的网关项目,涉及到加密和解密模块,这里详细分析解决方案和适用的场景.为了模拟真实的交互场景,先定制一下整个交互流程.第三方传输(包括请求和响应)数据报文包括三个部分: 1.timestamp,long类型,时间戳. 2.data,String类型,实际的业务请求数据转化成的Json字符串再进行加密得到的密文. 3.sign,签名,生成规则算法伪代码是SHA-256(data=xxx&timestamp=11111),防篡改. 为了简单起见,加密和解密采用AES,对称秘钥

  • SpringMvc/SpringBoot HTTP通信加解密的实现

    前言 从去年10月份到现在忙的没时间写博客了,今天就甩给大家一个干货吧!!! 近来很多人问到下面的问题 我们不想在每个Controller方法收到字符串报文后再调用一次解密,虽然可以完成,但是很low,且如果想不再使用加解密,修改起来很是麻烦. 我们想在使用Rest工具或swagger请求的时候不进行加解密,而在app调用的时候处理加解密,这可如何操作. 针对以上的问题,下面直接给出解决方案: 实现思路 APP调用API的时候,如果需要加解密的接口,需要在httpHeader中给出加密方式,如h

  • SpringBoot接口加密解密统一处理

    我们与客户端的接口交互中,为了更高的安全性,我们可能需要对接口加密(请求参数加密,服务端解密).返回信息加密(服务端加密,客户端解密),但是也不是所有的接口都这样,有些接口可能不需要,我们可以使用注解来轻松达到此要求. 将接口参数的加密解密和返回信息的加密解密分开,分别定义注解,利用Controller的ControllerAdvice来拦截所有的请求,在其中判断是否需要加密解密,即可达到要求. 使用方法:使用 DecryptRequest 和 EncryptResponse 注解即可,可以放在

  • Spring Boot 接口参数加密解密的实现方法

    因为有小伙伴刚好问到这个问题,松哥就抽空撸一篇文章和大家聊聊这个话题. 加密解密本身并不是难事,问题是在何时去处理?定义一个过滤器,将请求和响应分别拦截下来进行处理也是一个办法,这种方式虽然粗暴,但是灵活,因为可以拿到一手的请求参数和响应数据.不过 SpringMVC 中给我们提供了 ResponseBodyAdvice 和 RequestBodyAdvice,利用这两个工具可以对请求和响应进行预处理,非常方便. 所以今天这篇文章有两个目的: 分享参数/响应加解密的思路. 分享 Response

  • SpringBoot http请求注解@RestController原理解析

    这篇文章主要介绍了SpringBoot http请求注解@RestController原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 @RestController @RestController = @Controller + @ResponseBody组成,等号右边两位同志简单介绍两句,就明白我们@RestController的意义了: @Controller 将当前修饰的类注入SpringBoot IOC容器,使得从该类所在的项目

  • 使用注解+RequestBodyAdvice实现http请求内容加解密方式

    注解主要用来指定那些需要加解密的controller方法 实现比较简单 @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface SecretAnnotation { boolean encode() default false; boolean decode() default false; } 使用时添加注解在controller的方法上 @PostMapping("/preview

  • 详解Mybatis拦截器安全加解密MySQL数据实战

    需求背景 公司为了通过一些金融安全指标(政策问题)和防止数据泄漏,需要对用户敏感数据进行加密,所以在公司项目中所有存储了用户信息的数据库都需要进行数据加密改造.包括Mysql.redis.mongodb.es.HBase等. 因为在项目中是使用springboot+mybatis方式连接数据库进行增删改查,并且项目是中途改造数据.所以为了不影响正常业务,打算这次改动尽量不侵入到业务代码,加上mybatis开放的各种拦截器接口,所以就以此进行改造数据. 本篇文章讲述如何在现有项目中尽量不侵入业务方

  • 详细分析JAVA加解密算法

    加解密算法分析 日常开发中,无论你是使用什么语言,都应该遇到过使用加解密的使用场景,比如接口数据需要加密传给前端保证数据传输的安全:HTTPS使用证书的方式首先进行非对称加密,将客户端的私匙传递给服务端,然后双方后面的通信都使用该私匙进行对称加密传输:使用MD5进行文件一致性校验,等等很多的场景都使用到了加解密技术. 很多时候我们对于什么时候要使用什么样的加解密方式是很懵的.因为可用的加解密方案实在是太多,大家对加解密技术的类型可能不是很清楚,今天这篇文章就来梳理一下目前主流的加解密技术,本篇文

  • .NET Core结合Nacos实现配置加解密的方法

    目录 背景 简单原理说明 自定义 ConfigFilter 简单应用 写在最后 背景 当我们把应用的配置都放到配置中心后,很多人会想到这样一个问题,配置里面有敏感的信息要怎么处理呢? 信息既然敏感的话,那么加个密就好了嘛,相信大部分人的第一感觉都是这个,确实这个是最简单也是最合适的方法. 其实很多人都在关注这个问题,好比说,数据库的连接字符串,调用第三方的密钥等等这些信息,都是不太想让很多人知道的. 那么如果我们把配置放在 Nacos 了,我们可以怎么操作呢? 想了想不外乎这么几种: 全部服务端

  • C#加解密之AES算法的实现

    目录 实现功能 开发环境 实现代码 实现效果 从这一篇开始呢,写一下常用的一些加解密方式.一般我们来说呢,对于加密,我们分为可逆和不可逆.可逆加密又可分为对称加密(AES.DES等)和非对称加密(RSA),还有就是一些编码加密等(BASE64):不可逆的呢,大部分又都称为摘要算法(MD5.SHA). 其实上面扯这些也是白扯,对于一般用户来讲,我从明文能变成看不懂的密文就是加密了,管他叫什么,为什么要写这些,因为我发现很多人喜欢较真,拿MD5来说吧,专业点来讲,他确实是摘要算法而不是加密算法,但很

  • 使用webservice自定义注解处理参数加解密问题

    目录 webservice自定义注解处理参数加解密 代码实现 webservice注解汇总 @WebService @WebMethod @Oneway @WebParam @WebResult @HandlerChain webservice自定义注解处理参数加解密 前一段项目中用到了webservice,正好自己之前也了解过一点apache的cxf框架,所以就采用了cxf来实现webservice服务端,本身实现并没技术难点,但是项目为了保证安全性,采用了传输加密的过程,所以大部分请求参数需

  • AES加解密在php接口请求过程中的应用示例

    在php请求接口的时候,我们经常需要考虑的一个问题就是数据的安全性,因为数据传输过程中很有可能会被用fillder这样的抓包工具进行截获.一种比较好的解决方案就是在客户端请求发起之前先对要请求的数据进行加密,服务端api接收到请求数据后再对数据进行解密处理,返回结果给客户端的时候也对要返回的数据进行加密,客户端接收到返回数据的时候再解密.因此整个api请求过程中数据的安全性有了一定程度的提高. 今天结合一个简单的demo给大家分享一下AES加解密技术在php接口请求中的应用. 首先,准备一个AE

  • 巧用ajax请求服务器加载数据列表时提示loading的方法

    我们利用weui.js中的weui.loading为效果,ajax的beforeSend与complete方法,做一个加载数据时会有几秒的 loading... 要在页面需要加载的JS文件: <script src="../js/libs/weui.min.js"></script> 可以去weui的文档中下载,这是它的demo:   https://weui.io/weui.js/ 这里主要讲jQuery ajax的get,查询数据时,它的结构为: $.aja

  • Android使用缓存机制实现文件下载及异步请求图片加三级缓存

    首先给大家介绍Android使用缓存机制实现文件下载 在下载文件或者在线浏览文件时,或者为了保证文件下载的正确性,需要使用缓存机制,常使用SoftReference来实现. SoftReference的特点是它的一个实例保存对一个Java对象的软引用,该软引用的存在不妨碍垃圾收集线程对该Java对象的回收.也就是说,一旦SoftReference保存了对一个Java对象的软引用后,在垃圾线程对这个Java对象回收前,SoftReference类所提供的get()方法返回Java对象的强引用.另外

随机推荐