springboot 对 RequestBody入参统一加解密处理

springboot 对 RequestBody入参统一加解密处理

一、前言

在实质第三方系统对接或者安全系数比较搞得数据处理过程中,往往需要对通讯进行加密处理,而我们实际开发前期可能不会进行对对应得数据通讯加解密操作,只有在项目上线,方加上对应得加密方法,而如果对代码进行大批量修改加密,不但不利于代码得扩展,而且代码审美,维护上都存在瑕疵,那么本小结主要介绍springboot 基于spring的面向切面编程对数据链路实现自动无侵入式的加解密操作。

二、实现步骤

1、实现RequestBodyAdvice接口,实现supports方法并做是否需要做解密操作

2、实现ResponseBodyAdvice接口,实现supports方法并做是否需要做加密处理

3、编写加密配置类,并做加密yaml配置

4、编写方法注解类,以便后续做是否检查判断

5、将配置及实现类配置为bean

三、代码案例

1、实现RequestBodyAdvice接口,实现supports方法并处理是否做解密操作

package com.jiuzhou.common.advice;

import com.jiuzhou.common.annotation.Decrypt;
import com.jiuzhou.common.configure.SecretKeyConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
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.lang.reflect.Type;

/**
 * 【问题】群组表
 * github地址 http://www.github.com/wanyushu
 * gitee地址 http://www.gitee.com/wanyushu
 * @author yushu
 * @email 921784721@qq.com
 * 对请求的参数进行解密
 **/
@ControllerAdvice
public class DecryptRequestBodyAdvice implements RequestBodyAdvice {

    private Logger log = LoggerFactory.getLogger(this.getClass());

    private boolean encrypt;

    @Autowired
    private SecretKeyConfig secretKeyConfig;

    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        if (methodParameter.getMethod().isAnnotationPresent(Decrypt.class) && secretKeyConfig.isOpen()) {
            encrypt = true;
        }
        return encrypt;
    }

    @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){
        if (encrypt) {
            try {
                return new DecryptHttpInputMessage(inputMessage, secretKeyConfig.getPublicKey(), secretKeyConfig.getCharset(),secretKeyConfig.isShowLog());

            } catch (Exception e) {
                log.error("Decryption failed", e);
            }
        }
        return inputMessage;
    }

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

2、实现ResponseBodyAdvice接口,实现supports方法并做加密判断


package com.jiuzhou.common.advice;
import com.alibaba.fastjson.JSON;
import com.jiuzhou.common.annotation.Encrypt;
import com.jiuzhou.common.configure.SecretKeyConfig;
import com.jiuzhou.utils.AESEncryption;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
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.util.StringUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

/**
 * github地址 http://www.github.com/wanyushu
 * gitee地址 http://www.gitee.com/wanyushu
 * @author yushu
 * @email 921784721@qq.com
 * 对请求的参数进行加密
 **/
@ControllerAdvice
public class EncryptResponseBodyAdvice implements ResponseBodyAdvice<Object> {

    private Logger log = LoggerFactory.getLogger(this.getClass());

    private boolean encrypt;

    @Autowired
    private SecretKeyConfig secretKeyConfig;

    private static ThreadLocal<Boolean> encryptLocal = new ThreadLocal<>();

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        encrypt = false;
        if (returnType.getMethod().isAnnotationPresent(Encrypt.class) && secretKeyConfig.isOpen()) {
            encrypt = true;
        }
        return encrypt;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
                                  Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        // EncryptResponseBodyAdvice.setEncryptStatus(false);
        // Dynamic Settings Not Encrypted
        Boolean status = encryptLocal.get();
        if (null != status && !status) {
            encryptLocal.remove();
            return body;
        }
        if (encrypt) {
            String publicKey = secretKeyConfig.getPublicKey();
            try {
                String content = JSON.toJSONString(body);
                if (!StringUtils.hasText(publicKey)) {
                    throw new NullPointerException("Please configure rsa.encrypt.privatekeyc parameter!");
                }
                //加密过程
                final String result = AESEncryption.encrypt(content, publicKey);
//                String result = SMCipherCaculater.instance.SM4_encrypt(content.getBytes(Charset.defaultCharset()), publicKey.getBytes(Charset.defaultCharset())).toString();
                if(secretKeyConfig.isShowLog()) {
                    log.info("Pre-encrypted data:{},After encryption:{}", content, result);
                }
                return result;
            } catch (Exception e) {
                log.error("Encrypted data exception", e);
            }
        }
        return body;
    }
}

3、编写加密配置类,并做加密yaml配置

package com.jiuzhou.common.configure;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

/**
 * Author:Bobby
 * DateTime:2019/4/9
 **/
@Data
@ConfigurationProperties(prefix = "aes.encrypt")
@Configuration
public class SecretKeyConfig {

    private String privateKey;

    private String publicKey;

    private String charset = "UTF-8";

    private boolean open = false;

    private boolean showLog = false;

    private Integer time;

}

aes:
  encrypt:
    open: true # 是否开启加密 true  or  false
    showLog: true # 是否打印加解密log true  or  false
    publicKey: mN4Yn8Or8r7SH1w3 # AES密钥
    privateKey:  # RSA私钥
    time: 600000

4、编写方法注解类,以便后续做是否检查判断

package com.jiuzhou.common.annotation;

import java.lang.annotation.*;

/**
 * github地址 http://www.github.com/wanyushu
 * gitee地址 http://www.gitee.com/wanyushu
 * @author yushu
 * @email 921784721@qq.com
 **/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Encrypt {

}
package com.jiuzhou.common.annotation;

import java.lang.annotation.*;

/**
 * github地址 http://www.github.com/wanyushu
 * gitee地址 http://www.gitee.com/wanyushu
 * @author yushu
 * @email 921784721@qq.com
 **/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Encrypt {

}

5、配置spring bean

package com.jiuzhou.common.annotation;

import com.jiuzhou.common.advice.DecryptRequestBodyAdvice;
import com.jiuzhou.common.advice.EncryptResponseBodyAdvice;
import com.jiuzhou.common.configure.SecretKeyConfig;
import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

/**
 * github地址 http://www.github.com/wanyushu
 * gitee地址 http://www.gitee.com/wanyushu
 * @author yushu
 * @email 921784721@qq.com
 **/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Import({SecretKeyConfig.class,
        EncryptResponseBodyAdvice.class,
        DecryptRequestBodyAdvice.class})
public @interface EnableSecurity {

}
package com.jiuzhou;

import com.jiuzhou.common.annotation.EnableSecurity;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@EnableSecurity
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class,args);
    }

}

四、demo演示

1、在controller添加加解密注解 @Encrypt @Decrypt

package com.jiuzhou.controller;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jiuzhou.common.annotation.Decrypt;
import com.jiuzhou.common.annotation.Encrypt;
import com.jiuzhou.common.annotation.Repeat;
import com.jiuzhou.entity.QuestionGroup;
import com.jiuzhou.service.QuestionGroupService;
import com.jiuzhou.utils.RestResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.Date;
import java.util.List;

/**
 * 【问题】群组表
 * github地址 http://www.github.com/wanyushu
 * gitee地址 http://www.gitee.com/wanyushu
 * @author yushu
 * @email 
 * @date 2023-04-27 18:00:53
 */
@Api(tags = "【问题】群组表controller",value= "【问题】群组表相关接口")
@RestController
@RequestMapping("questionGroup")
public class QuestionGroupController {

    @Autowired
    private QuestionGroupService questionGroupService;


    /**
     * 根据id删除对应的问题组
     */

    @Encrypt
    @Decrypt
    @Repeat
    @ApiOperation(value = "新增聊天组")
    @PostMapping("save")
    public RestResult save(@RequestBody QuestionGroup questionGroup){
        if(StringUtils.isEmpty(questionGroup.getGroupName())){
            return  RestResult.failed("新增聊天组名不能为空");
        }
        questionGroup.setCreateTime(new Date());
        questionGroup.setIsDel(0);
        System.out.println("入参情况打印:"+JSON.toJSONString(questionGroup));
        return RestResult.ok();
    }


}

2 、创建加密密钥
在这里插入图片描述
3、postman请求

post请求
http://127.0.0.1:8081/questionGroup/save
入参 application/json
VViXp/JPYRbUWSCII5NbFU8avzSotbpgXEuyMZP9csE=

postman输出
在这里插入图片描述
控制台打印信息
在这里插入图片描述

五、总结

在使用的过程中,大家注意

1、开启 加密的同时,需要在springboot启动类上面加@EnableSecurity 注解便spring初始化对应的bean信息

2、在生成环境使用,请注意关闭openlog

3、开发的过程中,可以将open设置为false

4、如果使用其它加密算法可以自行修改EncryptResponseBodyAdvice  类

5、最后加密比较耗性能,注意只有在安全级别较高时使用,对具体的方法加密需要添加@Decrypt

6、对输入的参数解密请在controller层方法上加@Encrypt

7、需要修改加解密密钥,请长度设置为16的倍数的字符串长度

七、源码分享

以上本编为大家演示了springboot的加解密参数统一处理,如有问题或建议,可在评论区留言或私信,记得帮忙点赞并收藏,分享给大家让程序猿的生活不在为编码而愁!
最后源码已上传至 springboot框架使用技巧

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值