欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

springboot接口加簽驗(yàn)簽常見的幾大問題及解決過程

 更新時(shí)間:2024年11月05日 10:57:50   作者:路西法_Lucifer  
在SpringBoot框架中通過自定義注解實(shí)現(xiàn)加簽驗(yàn)簽功能是一個(gè)非常實(shí)用的技術(shù),本文主要介紹了使用SpringBoot進(jìn)行加簽驗(yàn)簽時(shí)可能遇到的幾個(gè)問題,包括請(qǐng)求流重復(fù)讀取問題、控制器中文件參數(shù)讀取為空問題、FormData表單提交MD5加密值不一致問題

springboot接口加簽驗(yàn)簽常見問題及解決

ps:通過springboot自定義注解實(shí)現(xiàn)驗(yàn)簽的加簽驗(yàn)簽功能,不過很多容易遇坑的地方.

所需pom.xml中的jar包:

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.4</version>
        </dependency>

原始代碼段:

1、測(cè)試Controller

@RestController
@RequestMapping
public class TestController {

    @PostMapping("test1")
    public void test1(MultipartFile file){
        System.out.println("...........");
    }


    @PostMapping("test2")
    public void test2(@RequestBody String str){
        System.out.println("...........");
    }

}

2、aop切面

(ps: 這里關(guān)于通過自定義注解實(shí)現(xiàn)驗(yàn)簽加簽的代碼就不寫了,其實(shí)很簡(jiǎn)單,這里主要是為了說明可能遇到的問題,以及解決辦法,至于加簽驗(yàn)簽的具體代碼,自行百度)

@Slf4j
@Component
@Aspect
public class TestAop {

    @Around(value = "execution(* com.example.demo.controller.*.*(..))")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        ServletInputStream inputStream = request.getInputStream();
        //對(duì)請(qǐng)求體得到的字節(jié)進(jìn)行加密,我的加簽是通過url后面的一些參數(shù)+accessKey+body+secreteKey等這些字段進(jìn)行加密,我會(huì)對(duì)body加密后,然后跟其它加簽字段拼接起來,然后MD5得到簽名的,具體加簽規(guī)則去百度
        //這里我就為了演示,只對(duì)body加密了
        String digestHex = MD5.create().digestHex(IoUtil.readBytes(inputStream));
        log.info("============:{}", digestHex);
        Object obj = joinPoint.proceed();
        return obj;
    }
}

可能產(chǎn)生的問題

問題1

1.1 因?yàn)閞equest.getInputStream()讀取的流,讀到一次之后,就會(huì)關(guān)掉,所以過濾器中獲取request.getInputStream()為空;

1.2 如果對(duì)流獲取多次,就會(huì)出現(xiàn)異常,request.getInputStream()讀取的流,讀到一次之后,就會(huì)關(guān)掉。

解決辦法: 將request進(jìn)行包裝,讓request.getInputStream()可以重復(fù)讀取

1. RequestWrapper 對(duì)request對(duì)象進(jìn)行包裝

package com.example.demo.config;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.IOUtils;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;

@Slf4j
public class RequestWrapper extends HttpServletRequestWrapper {

    private ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

    public RequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        IOUtils.copy(request.getInputStream(), byteArrayOutputStream);
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        if (byteArrayOutputStream == null) {
            IOUtils.copy(super.getInputStream(), byteArrayOutputStream);
        }
        return new ServletInputStream() {
            private final ByteArrayInputStream input = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());

            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {

            }

            @Override
            public int read() throws IOException {
                return input.read();
            }
        };
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }


    public ByteArrayOutputStream getByteArrayOutputStream() {
        return byteArrayOutputStream;
    }

}

2. RequestWrapperFilter 過濾器 讓request變成自己的request包裝對(duì)象

package com.example.demo.config;

import lombok.extern.slf4j.Slf4j;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

//filter 過濾器   urlPatterns自己設(shè)置過濾的路徑,這里為了演示,就怎么方便怎么來了
@WebFilter(urlPatterns = "/*")
@Slf4j
public class RequestWrapperFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        ServletRequest requestWrapper = null;
        if (request instanceof HttpServletRequest) {
            requestWrapper = new RequestWrapper((HttpServletRequest) request);
        }
        if (requestWrapper == null) {
            chain.doFilter(request, response);
        } else {
            chain.doFilter(requestWrapper, response);
        }
    }
}

3. springboot的啟動(dòng)類中加入@ServletComponentScan,讓filter生效

@SpringBootApplication
@ServletComponentScan
public class DemoApplication {

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

}

4、修改aop,不再讀取直接讀取request,而是讀取new RequestWrapper(request)對(duì)象

    @Around(value = "execution(* com.example.demo.controller.*.*(..))")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        RequestWrapper requestWrapper = new RequestWrapper(request);
        ByteArrayOutputStream byteArrayOutputStream = requestWrapper.getByteArrayOutputStream();

        ServletInputStream inputStream = requestWrapper.getInputStream();
        String digestHex = MD5.create().digestHex(IoUtil.readBytes(inputStream));
        log.info("============:{}", digestHex);
        Object obj = joinPoint.proceed();
        return obj;
    }

驗(yàn)證結(jié)果:(多次讀取getIntPutStream沒有問題)

問題2

aop切面讀取到的文件是有值,但是controller接口中file這個(gè)參數(shù)居然讀不到,顯示為空

修改后的aop代碼段:

   @Around(value = "execution(* com.example.demo.controller.*.*(..))")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        RequestWrapper requestWrapper = new RequestWrapper(request);
        ByteArrayOutputStream byteArrayOutputStream = requestWrapper.getByteArrayOutputStream();
        String digestHex = MD5.create().digestHex(byteArrayOutputStream.toByteArray());
        log.info("============:{}", digestHex);
        Object obj = joinPoint.proceed();
        return obj;
    }

當(dāng)調(diào)用接口test2時(shí),控制臺(tái)打印如下:

2022-10-14 21:32:56.207  INFO 9968 --- [nio-8080-exec-1] com.example.demo.aop.TestAop             : ============:ab2ee64ec2b80edc3553a826c4610733
...........testEntity:{"id":1,"username":"zhangsan"}
2022-10-14 21:32:59.636  INFO 9968 --- [nio-8080-exec-3] com.example.demo.aop.TestAop             : ============:ab2ee64ec2b80edc3553a826c4610733
...........testEntity:{"id":1,"username":"zhangsan"}

當(dāng)調(diào)用接口test1時(shí),如圖:

aop切面讀取到的文件是有值,但是controller接口中file這個(gè)參數(shù)居然讀不到,顯示為空

問題出在哪兒呢??????

只要是http請(qǐng)求都包裝下request對(duì)象,最終用的都是requestWrapper對(duì)象。

當(dāng)我調(diào)用test2接口,body是實(shí)體類對(duì)象,是可以接收參數(shù)的;

ps: 使用requestWrapper對(duì)象解決了輸入流的重復(fù)讀取的問題,但是卻引發(fā)了接口文件讀取為空的bug.對(duì)test2接口,這種用實(shí)體對(duì)象接收,沒有問題。

解決辦法:替換springboot對(duì)file的默認(rèn)實(shí)現(xiàn),改用commons-fileupload包的

    <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.4</version>
        </dependency>

排除MultipartAutoConfiguration的默認(rèn)實(shí)現(xiàn),改用CommonsMultipartResolver

@SpringBootApplication(exclude = MultipartAutoConfiguration.class)
@ServletComponentScan
public class DemoApplication {

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

    
    @Bean(name = "multipartResolver")
    public MultipartResolver multipartResolver() {
        CommonsMultipartResolver resolver = new CommonsMultipartResolver();
        resolver.setDefaultEncoding("UTF-8");
        return resolver;
    }

}

效果如下:

問題3

form-data 表單提交,對(duì)body的字節(jié)數(shù)組進(jìn)行MD5加密,相同請(qǐng)求,相同參數(shù),生成的MD5值每次卻不一樣,對(duì)于接口參數(shù)是MultipartFile參數(shù)而言。

問題分析:

如果調(diào)用test2接口,則通過輸入流讀取的內(nèi)容是一個(gè)json,并沒有添加請(qǐng)求頭以及其它額外部分。

如果調(diào)用test1接口,則通過輸入流讀取的內(nèi)容是一堆亂碼,并添加請(qǐng)求頭以及其它額外部分。

綠色框的是如果接口參數(shù)是文件的話,獲取輸入流,會(huì)在流里添加除了文件內(nèi)容之外,還會(huì)額外添加的部分,而紅色框住的部分,每次都不一樣,因此盡管每次相同請(qǐng)求,相同文件,這個(gè)輸入流讀取為字節(jié)數(shù)組后,都是不一樣的,因此MD5加密得到的MD5值也肯定不一樣。

解決辦法:

因此不應(yīng)該直接讀取reque.getInputStream()里面的內(nèi)容,應(yīng)該拿到這個(gè)流后,對(duì)它應(yīng)該是文件內(nèi)容的其它額外添加的如請(qǐng)求頭,隨機(jī)數(shù)之類的東西全部去掉后,拿到單純的只是文件的內(nèi)容。

辦法1:不用form-data提交,改用binary 二進(jìn)制流提交,這種提交,request.getInputStream()得到的流 里面不會(huì)添加除了文件內(nèi)容外,如請(qǐng)求頭、隨機(jī)數(shù)等額外部分。

辦法2:仍然使用form-data提交方式,將request.getInputStream()得到的流 里面添加除了文件內(nèi)容外,如請(qǐng)求頭、隨機(jī)數(shù)等額外部分都去掉。

修改后的RequestWrapper :

package com.example.demo.config;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.http.ContentType;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.util.List;

@Slf4j
public class RequestWrapper extends HttpServletRequestWrapper {

    private ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

    public RequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        IOUtils.copy(request.getInputStream(), byteArrayOutputStream);
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        if (byteArrayOutputStream == null) {
            IOUtils.copy(super.getInputStream(), byteArrayOutputStream);
        }
        return new ServletInputStream() {
            private final ByteArrayInputStream input = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());

            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {

            }

            @Override
            public int read() throws IOException {
                return input.read();
            }
        };
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }


    public ByteArrayOutputStream getByteArrayOutputStream() {
        return byteArrayOutputStream;
    }

    public byte[] getPureBody() throws FileUploadException {
        if (this.getContentType().contains(ContentType.MULTIPART.getValue())) {
            DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory();
            ServletFileUpload servletFileUpload = new ServletFileUpload(diskFileItemFactory);
            //獲取所有的上傳文件
            List<FileItem> fileItems = servletFileUpload.parseRequest(this);
            if (CollUtil.isNotEmpty(fileItems)) {
                byte[][] bytes = new byte[fileItems.size()][];
                for (int i = 0; i < fileItems.size(); i++) {
                    bytes[i] = fileItems.get(i).get();
                }
                //判斷二維數(shù)組不能為空
                if (bytes != null && bytes.length > 0) {
                    if (!(bytes.length == 1 && bytes[0].length == 0)) {
                        //將多個(gè)文件對(duì)應(yīng)的字節(jié)數(shù)組合并成一個(gè)字節(jié)數(shù)組 byte[]
                        return mergeBytes(bytes);
                    }
                }
            }
        }
        return byteArrayOutputStream.toByteArray();
    }

    private static byte[] mergeBytes(byte[]... values) {
        int lengthByte = 0;
        for (byte[] value : values) {
            lengthByte += value.length;
        }
        byte[] allBytes = new byte[lengthByte];
        int countLength = 0;
        for (byte[] b : values) {
            System.arraycopy(b, 0, allBytes, countLength, b.length);
            countLength += b.length;
        }
        return allBytes;
    }


}

修改后的aop:

package com.example.demo.aop;

import cn.hutool.core.io.IoUtil;
import cn.hutool.crypto.digest.MD5;
import com.example.demo.config.RequestWrapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;

@Slf4j
@Component
@Aspect
public class TestAop {

    @Around(value = "execution(* com.example.demo.controller.*.*(..))")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        RequestWrapper requestWrapper = new RequestWrapper(request);
        String digestHex = MD5.create().digestHex(requestWrapper.getPureBody());
        log.info("============:{}", digestHex);
        Object obj = joinPoint.proceed();
        return obj;
    }
}

測(cè)試結(jié)果:

調(diào)用兩次接口test1,查看控制臺(tái)

2022-10-14 22:29:07.548 xxTestAop: =====:be53fbee299d4c13b4f68420afb26127
...........jd-gui.exe
2022-10-14 22:29:08.719 x.TestAop: =====:be53fbee299d4c13b4f68420afb26127

調(diào)用兩次接口test2,查看控制臺(tái)

2022-10-14 22:29:10.903   TestAop: ====:ab2ee64ec2b80edc3553a826c4610733
...........testEntity:{"id":1,"username":"zhangsan"}
2022-10-14 22:29:11.762  TestAop:=====:ab2ee64ec2b80edc3553a826c4610733

增加接口test3,驗(yàn)證多文件上傳,生成的MD5是否一致:

    @PostMapping("test3")
    public void test3(List<MultipartFile> file) {
        for (MultipartFile multipartFile : file) {
            System.out.println("..........." + multipartFile.getOriginalFilename());
        }
    }

控制臺(tái)輸出:

2022-10-14 22:38:47.764  TestAop ===:7a0f9ab5a674935ff8a5177a49c0efdf
...........jd-gui.exe
...........README.md
2022-10-14 22:38:54.506  TestAop ====:7a0f9ab5a674935ff8a5177a49c0efdf
...........jd-gui.exe
...........README.md

問題4(額外拓展)

不是本博客的代碼出現(xiàn)的,曾經(jīng)也是提供給第三方調(diào)用的接口加簽名驗(yàn)簽,由于那次是第一次寫接口的加簽驗(yàn)簽功能,接口沒有文件上傳,全是傳json的這種,但是當(dāng)時(shí)怎么做得呢?

我拿到的body是一個(gè)實(shí)體類的對(duì)象,我用fastjson對(duì)它進(jìn)行解析,得到j(luò)son字符串,然后跟其它需要加簽的字段拼接在一起,在這里有一個(gè)問題的,但是當(dāng)時(shí)自己自測(cè),沒有測(cè)出來,因?yàn)槲襭ostman里面的json中屬性位置是一樣的,而調(diào)用者的body里面屬性位置跟我不一樣,最終body的內(nèi)容不一樣,導(dǎo)致生成的簽名跟我這邊的始終不一樣。

解決辦法:

  • 方法1:提供相同的json解析工具,確保兩邊的body的json串里面屬性在json解析后,順序是一樣的
  • 方法2:讀取輸入流,接口傳參是什么樣子,自己拿到的就是什么樣子,可以對(duì)輸入流進(jìn)行操作,也可以對(duì)從輸入流中讀取到字節(jié)數(shù)組操作

總結(jié)

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Spring Cloud LoadBalancer 負(fù)載均衡詳解

    Spring Cloud LoadBalancer 負(fù)載均衡詳解

    本文介紹了如何在Spring Cloud中使用SpringCloudLoadBalancer實(shí)現(xiàn)客戶端負(fù)載均衡,并詳細(xì)講解了輪詢策略和隨機(jī)策略的配置方法,此外,還提供了部署到云服務(wù)器并在多個(gè)實(shí)例之間進(jìn)行負(fù)載均衡的步驟,感興趣的朋友一起看看吧
    2025-02-02
  • 詳解SpringBoot 快速整合Mybatis(去XML化+注解進(jìn)階)

    詳解SpringBoot 快速整合Mybatis(去XML化+注解進(jìn)階)

    本篇文章主要介紹了詳解SpringBoot 快速整合Mybatis(去XML化+注解進(jìn)階),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-11-11
  • Java高性能實(shí)體類轉(zhuǎn)換工具M(jìn)apStruct的使用教程詳解

    Java高性能實(shí)體類轉(zhuǎn)換工具M(jìn)apStruct的使用教程詳解

    MapStruct 是一個(gè)代碼生成器,它基于約定優(yōu)于配置的方法,極大地簡(jiǎn)化了 Java bean 類型之間的映射實(shí)現(xiàn),本文主要介紹了MapStruct的具體使用以及如何進(jìn)行實(shí)體類轉(zhuǎn)換,感興趣的可以了解下
    2024-03-03
  • Java死鎖問題詳解及示例

    Java死鎖問題詳解及示例

    本文將討論Java程序中死鎖問題的概念、產(chǎn)生原因以及避免策略。同時(shí),我們還將通過代碼示例來進(jìn)一步闡述這個(gè)問題,感興趣的小伙伴可以跟著小編一起來學(xué)習(xí)
    2023-04-04
  • java獲取當(dāng)前日期和時(shí)間的二種方法分享

    java獲取當(dāng)前日期和時(shí)間的二種方法分享

    這篇文章主要介紹了java獲取當(dāng)前日期和時(shí)間的二種方法,需要的朋友可以參考下
    2014-03-03
  • 淺談Java并發(fā)編程之Lock鎖和條件變量

    淺談Java并發(fā)編程之Lock鎖和條件變量

    這篇文章主要介紹了淺談Java并發(fā)編程之Lock鎖和條件變量,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08
  • Kafka 安裝與配置詳細(xì)過程

    Kafka 安裝與配置詳細(xì)過程

    本節(jié)詳細(xì)介紹 Kafka 運(yùn)行環(huán)境的搭建,為了節(jié)省篇幅,本節(jié)的內(nèi)容以 Linux CentOS 作為安裝演示的操作系統(tǒng),其他 Linux 系列的操作系統(tǒng)也可以參考本節(jié)的內(nèi)容,對(duì)Kafka 安裝與配置相關(guān)知識(shí)感興趣的朋友一起看看吧
    2021-11-11
  • mybatis?查詢返回Map<String,Object>類型

    mybatis?查詢返回Map<String,Object>類型

    本文主要介紹了mybatis?查詢返回Map<String,Object>類型,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-03-03
  • Java concurrency集合之 CopyOnWriteArrayList_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    Java concurrency集合之 CopyOnWriteArrayList_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    這篇文章主要介紹了Java concurrency集合之 CopyOnWriteArrayList的相關(guān)資料,需要的朋友可以參考下
    2017-06-06
  • SpringCloud Gateway 權(quán)限認(rèn)證的實(shí)現(xiàn)

    SpringCloud Gateway 權(quán)限認(rèn)證的實(shí)現(xiàn)

    Spring Cloud Gateway 作為網(wǎng)關(guān)層,承擔(dān)著請(qǐng)求轉(zhuǎn)發(fā)、權(quán)限校驗(yàn)等重要職責(zé),本文主要介紹了SpringCloud Gateway 權(quán)限認(rèn)證的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下
    2025-04-04

最新評(píng)論