当前位置: 移动技术网 > IT编程>开发语言>Java > SpringBoot中如何灵活的实现接口数据的加解密功能?

SpringBoot中如何灵活的实现接口数据的加解密功能?

2019年10月28日  | 移动技术网IT编程  | 我要评论

红盟风云录,张家港讨债歌,总裁的夺爱游戏下载

数据是企业的第四张名片,企业级开发中少不了数据的加密传输,所以本文介绍下springboot中接口数据加密、解密的方式。

本文目录

一、加密方案介绍

对接口的加密解密操作主要有下面两种方式:

  1. 自定义消息转换器

优势:仅需实现接口,配置简单。
劣势:仅能对同一类型的mediatype进行加解密操作,不灵活。

  1. 使用spring提供的接口requestbodyadvice和responsebodyadvice

优势:可以按照请求的referrer、header或url进行判断,按照特定需要进行加密解密。

比如在一个项目升级的时候,新开发功能的接口需要加解密,老功能模块走之前的逻辑不加密,这时候就只能选择上面的第二种方式了,下面主要介绍下第二种方式加密、解密的过程。

二、实现原理

requestbodyadvice可以理解为在@requestbody之前需要进行的 操作,responsebodyadvice可以理解为在@responsebody之后进行的操作,所以当接口需要加解密时,在使用@requestbody接收前台参数之前可以先在requestbodyadvice的实现类中进行参数的解密,当操作结束需要返回数据时,可以在@responsebody之后进入responsebodyadvice的实现类中进行参数的加密。

requestbodyadvice处理请求的过程:

requestbodyadvice源码如下:

 public interface requestbodyadvice {

    boolean supports(methodparameter methodparameter, type targettype,
            class<? extends httpmessageconverter<?>> convertertype);


    httpinputmessage beforebodyread(httpinputmessage inputmessage, methodparameter parameter,
            type targettype, class<? extends httpmessageconverter<?>> convertertype) throws ioexception;


    object afterbodyread(object body, httpinputmessage inputmessage, methodparameter parameter,
            type targettype, class<? extends httpmessageconverter<?>> convertertype);


    @nullable
    object handleemptybody(@nullable object body, httpinputmessage inputmessage, methodparameter parameter,
            type targettype, class<? extends httpmessageconverter<?>> convertertype);


}

调用requestbodyadvice实现类的部分代码如下:

 protected <t> object readwithmessageconverters(httpinputmessage inputmessage, methodparameter parameter,
            type targettype) throws ioexception, httpmediatypenotsupportedexception, httpmessagenotreadableexception {

        mediatype contenttype;
        boolean nocontenttype = false;
        try {
            contenttype = inputmessage.getheaders().getcontenttype();
        }
        catch (invalidmediatypeexception ex) {
            throw new httpmediatypenotsupportedexception(ex.getmessage());
        }
        if (contenttype == null) {
            nocontenttype = true;
            contenttype = mediatype.application_octet_stream;
        }

        class<?> contextclass = parameter.getcontainingclass();
        class<t> targetclass = (targettype instanceof class ? (class<t>) targettype : null);
        if (targetclass == null) {
            resolvabletype resolvabletype = resolvabletype.formethodparameter(parameter);
            targetclass = (class<t>) resolvabletype.resolve();
        }

        httpmethod httpmethod = (inputmessage instanceof httprequest ? ((httprequest) inputmessage).getmethod() : null);
        object body = no_value;

        emptybodycheckinghttpinputmessage message;
        try {
            message = new emptybodycheckinghttpinputmessage(inputmessage);

            for (httpmessageconverter<?> converter : this.messageconverters) {
                class<httpmessageconverter<?>> convertertype = (class<httpmessageconverter<?>>) converter.getclass();
                generichttpmessageconverter<?> genericconverter =
                        (converter instanceof generichttpmessageconverter ? (generichttpmessageconverter<?>) converter : null);
                if (genericconverter != null ? genericconverter.canread(targettype, contextclass, contenttype) :
                        (targetclass != null && converter.canread(targetclass, contenttype))) {
                    if (logger.isdebugenabled()) {
                        logger.debug("read [" + targettype + "] as \"" + contenttype + "\" with [" + converter + "]");
                    }
                    if (message.hasbody()) {
                        httpinputmessage msgtouse =
                                getadvice().beforebodyread(message, parameter, targettype, convertertype);
                        body = (genericconverter != null ? genericconverter.read(targettype, contextclass, msgtouse) :
                                ((httpmessageconverter<t>) converter).read(targetclass, msgtouse));
                        body = getadvice().afterbodyread(body, msgtouse, parameter, targettype, convertertype);
                    }
                    else {
                        body = getadvice().handleemptybody(null, message, parameter, targettype, convertertype);
                    }
                    break;
                }
            }
        }
        catch (ioexception ex) {
            throw new httpmessagenotreadableexception("i/o error while reading input message", ex);
        }

        if (body == no_value) {
            if (httpmethod == null || !supported_methods.contains(httpmethod) ||
                    (nocontenttype && !message.hasbody())) {
                return null;
            }
            throw new httpmediatypenotsupportedexception(contenttype, this.allsupportedmediatypes);
        }

        return body;
    }

从上面源码可以到当converter.canread()和message.hasbody()都为true的时候,会调用beforebodyread()和afterbodyread()方法,所以我们在实现类的afterbodyread()中添加解密代码即可。

responsebodyadvice处理响应的过程:

responsebodyadvice源码如下:

public interface responsebodyadvice<t> {


    boolean supports(methodparameter returntype, class<? extends httpmessageconverter<?>> convertertype);


    @nullable
    t beforebodywrite(@nullable t body, methodparameter returntype, mediatype selectedcontenttype,
            class<? extends httpmessageconverter<?>> selectedconvertertype,
            serverhttprequest request, serverhttpresponse response);

}

调用responsebodyadvice实现类的部分代码如下:

if (selectedmediatype != null) {
            selectedmediatype = selectedmediatype.removequalityvalue();
            for (httpmessageconverter<?> converter : this.messageconverters) {
                generichttpmessageconverter genericconverter =
                        (converter instanceof generichttpmessageconverter ? (generichttpmessageconverter<?>) converter : null);
                if (genericconverter != null ?
                        ((generichttpmessageconverter) converter).canwrite(declaredtype, valuetype, selectedmediatype) :
                        converter.canwrite(valuetype, selectedmediatype)) {
                    outputvalue = (t) getadvice().beforebodywrite(outputvalue, returntype, selectedmediatype,
                            (class<? extends httpmessageconverter<?>>) converter.getclass(),
                            inputmessage, outputmessage);
                    if (outputvalue != null) {
                        addcontentdispositionheader(inputmessage, outputmessage);
                        if (genericconverter != null) {
                            genericconverter.write(outputvalue, declaredtype, selectedmediatype, outputmessage);
                        }
                        else {
                            ((httpmessageconverter) converter).write(outputvalue, selectedmediatype, outputmessage);
                        }
                        if (logger.isdebugenabled()) {
                            logger.debug("written [" + outputvalue + "] as \"" + selectedmediatype +
                                    "\" using [" + converter + "]");
                        }
                    }
                    return;
                }
            }
        }

从上面源码可以到当converter.canwrite()为true的时候,会调用beforebodywrite()方法,所以我们在实现类的beforebodywrite()中添加解密代码即可。

三、实战

新建一个spring boot项目spring-boot-encry,按照下面步骤操作。

  1. pom.xml中引入jar
  <dependencies>
        <dependency>
            <groupid>org.springframework.boot</groupid>
            <artifactid>spring-boot-starter-web</artifactid>
        </dependency>

        <dependency>
            <groupid>org.projectlombok</groupid>
            <artifactid>lombok</artifactid>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupid>org.springframework.boot</groupid>
            <artifactid>spring-boot-starter-test</artifactid>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupid>org.junit.vintage</groupid>
                    <artifactid>junit-vintage-engine</artifactid>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupid>com.alibaba</groupid>
            <artifactid>fastjson</artifactid>
            <version>1.2.60</version>
        </dependency>
    </dependencies>
  1. 请求参数解密拦截类

decryptrequestbodyadvice代码如下:

/**
 * 请求参数 解密操作
 *
 * @author: java碎碎念
 * @date: 2019/10/24 21:31
 *
 */
@component
@controlleradvice(basepackages = "com.example.springbootencry.controller")
@slf4j
public class decryptrequestbodyadvice implements requestbodyadvice {


    @override
    public boolean supports(methodparameter methodparameter, type targettype, class<? extends httpmessageconverter<?>> convertertype) {
        return true;
    }

    @override
    public httpinputmessage beforebodyread(httpinputmessage inputmessage, methodparameter methodparameter, type targettype, class<? extends httpmessageconverter<?>> selectedconvertertype) throws ioexception {
        return inputmessage;
    }

    @override
    public object afterbodyread(object body, httpinputmessage inputmessage, methodparameter parameter, type targettype, class<? extends httpmessageconverter<?>> convertertype) {
        string dealdata = null;
        try {
            //解密操作
            map<string,string> datamap = (map)body;
            string srcdata = datamap.get("data");
            dealdata = desutil.decrypt(srcdata);
        } catch (exception e) {
            log.error("异常!", e);
        }
        return dealdata;
    }


    @override
    public object handleemptybody(@nullable object var1, httpinputmessage var2, methodparameter var3, type var4, class<? extends httpmessageconverter<?>> var5) {
        log.info("3333");
        return var1;
    }


}
  1. 响应参数加密拦截类

encryresponsebodyadvice代码如下:

/**
 * 请求参数 解密操作
 *
 * @author: java碎碎念
 * @date: 2019/10/24 21:31
 *
 */
@component
@controlleradvice(basepackages = "com.example.springbootencry.controller")
@slf4j
public class encryresponsebodyadvice implements responsebodyadvice<object> {


    @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) {
        //通过 serverhttprequest的实现类servletserverhttprequest 获得httpservletrequest
        servletserverhttprequest sshr = (servletserverhttprequest) serverhttprequest;
        //此处获取到request 是为了取到在拦截器里面设置的一个对象 是我项目需要,可以忽略
        httpservletrequest request = sshr.getservletrequest();

        string returnstr = "";

        try {
            //添加encry header,告诉前端数据已加密
            serverhttpresponse.getheaders().add("encry", "true");
            string srcdata = json.tojsonstring(obj);
            //加密
            returnstr = desutil.encrypt(srcdata);
            log.info("接口={},原始数据={},加密后数据={}", request.getrequesturi(), srcdata, returnstr);

        } catch (exception e) {
            log.error("异常!", e);
        }
        return returnstr;
    }
  1. 新建controller类

testcontroller代码如下:

/**
 * @author: java碎碎念
 * @date: 2019/10/24 21:40
 */
@restcontroller
public class testcontroller {

    logger log = loggerfactory.getlogger(getclass());

    /**
     * 响应数据 加密
     */
    @requestmapping(value = "/sendresponseencrydata")
    public result sendresponseencrydata() {
        result result = result.createresult().setsuccess(true);
        result.setdatavalue("name", "java碎碎念");
        result.setdatavalue("encry", true);
        return result;
    }

    /**
     * 获取 解密后的 请求参数
     */
    @requestmapping(value = "/getrequestdata")
    public result getrequestdata(@requestbody object object) {
        log.info("controller接收的参数object={}", object.tostring());
        result result = result.createresult().setsuccess(true);
        return result;
    }
}
  1. 其他类在源码中,后面有github地址

四、测试

  1. 访问响应数据加密接口

使用postman发请求http://www.lhsxpumps.com/_localhost:8888/sendresponseencrydata,可以看到返回数据已加密,请求截图如下:


响应数据加密截图

后台也打印相关的日志,内容如下:

接口=/sendresponseencrydata

原始数据={"data":{"encry":true,"name":"java碎碎念"},"success":true}

加密后数据=vjc26g3sqru9gajdg7rhnax6ky/ihgioagdwi6almmtyynab4nebmxvdskepnia5bqat7zaimal7
3veiccusta==
  1. 访问请求数据解密接口

使用postman发请求http://www.lhsxpumps.com/_localhost:8888/getrequestdata,可以看到请求数据已解密,请求截图如下:


请求数据解密截图

后台也打印相关的日志,内容如下:

接收到原始请求数据={"data":"vwlvde8n6fusxn/jrrjavatopaba3m1qen+9bkuf2jpwc1esofgahq=="}

解密后数据={"name":"java碎碎念","des":"请求参数"}

五、踩到的坑

  1. 测试解密请求参数时候,请求体一定要有数据,否则不会调用实现类触发解密操作。

到此springboot中如何灵活的实现接口数据的加解密功能的功能已经全部实现,有问题欢迎留言沟通哦!

完整源码地址:

 

推荐阅读

1.springboot中神奇的@enable*注解?
2.java中integer.parseint和integer.valueof,你还傻傻分不清吗?
3.springcloud系列-整合hystrix的两种方式
4.springcloud系列-利用feign实现声明式服务调用
5.手把手带你利用ribbon实现客户端的负载均衡


限时领取免费java相关资料,涵盖了java、redis、mongodb、mysql、zookeeper、spring cloud、dubbo/kafka、hadoop、hbase、flink等高并发分布式、大数据、机器学习等技术。
关注下方公众号即可免费领取:

java碎碎念公众号

 

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网