HttpServletRequestWrapper的應用

    最近的一個項目前後端分離,然後後端自定義Filter,通過繼承org.springframework.web.filter.OncePerRequestFilter實現在請求到達Controller層之前進行統一的參數驗籤(MD5+鹽)和token驗證。其中,在進行驗籤時,需要獲取body中的JSON數據,這時想着直接通過HttpServletRequest#getInputStream() 獲取到請求的輸入流,從該輸入流中可以讀取到請求體。但這裏就有個坑了,這個流在被我們的代碼 read 過後,之後的代碼就會報錯,因爲流已經被我們讀取過了 , 嘗試使用 mark() , reset() 也是不行的,會拋出異常,這時,可以通過將 HttpServletRequest 對象包裝一層的方式來實現這個功能。Servlet規範中也有相關說明:IO都是隻能讀取一次的,除非做了拷貝!也就是做了緩存!一個重要的概念就是Scope與Single Thread Model。

一. HttpServletRequestWrapper
通過繼承HttpServletRequestWrapper類以實現在Filter中修改HttpServletRequest的參數。

  • UML結構圖
    在這裏插入圖片描述
    這裏其實是應用了裝飾器模式對HttpServletRequest進行了增強。
  • ServletRequest 抽象組件
  • HttpServletRequest 抽象組件的一個子類,它的實例被稱作“被裝飾者”
  • ServletRequestWrapper 一個基本的裝飾類,這裏是非抽象的
  • HttpServletRequestWrapper 一個具體的裝飾者,當然這裏也繼承了HttpServletRequest這個接口,是爲了獲取一些在ServletRequest中沒有的方法

二.代碼實現

  • 自定義Wrapper
package com.hong.security.common;

import org.springframework.util.StreamUtils;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

/**
 * @author wanghong
 * @date 2020/05/11 22:47
 **/
public class WrappedRequest extends HttpServletRequestWrapper {

    private byte[] requestBody = null;

    public WrappedRequest(HttpServletRequest request) {
        super(request);
        // 緩存請求body
        try {
            requestBody = StreamUtils.copyToByteArray(request.getInputStream());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        if (requestBody == null) {
            requestBody = new byte[0];
        }
        final ByteArrayInputStream bais = new ByteArrayInputStream(requestBody);
        return new ServletInputStream() {
            @Override
            public int read() throws IOException {
                return bais.read();
            }

            @Override
            public boolean isFinished() {
                // TODO Auto-generated method stub
                return true;
            }

            @Override
            public boolean isReady() {
                // TODO Auto-generated method stub
                return true;
            }

            @Override
            public void setReadListener(ReadListener listener) {
                // TODO Auto-generated method stub
            }
        };
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }
}
  • Filter中獲取請求體數據
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

	@Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("JwtAuthenticationTokenFilter Begin");
			
	      ServletRequest requestWrapper = new WrappedRequest(request);
	      String requestParams = readJSONString(requestWrapper);
	      // 。。。		
	}
	
    public String readJSONString(ServletRequest request) {
        StringBuffer json = new StringBuffer();
        String line = null;
        try {
            BufferedReader reader = request.getReader();
            while ((line = reader.readLine()) != null) {
                json.append(StringUtils.trim(line));
            }
        } catch (Exception e) {
            log.info("readJSONString方法異常:[{}]", request, e);
        }
        return json.toString();
    }
}

三. 修改HttpServletRequest的參數或請求頭

package com.hong.security.common;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.util.Enumeration;
import java.util.Map;
import java.util.Vector;

/**
 * @author wanghong
 * @date 2020/05/19 10:50
 * 繼承HttpServletRequestWrapper,創建裝飾類,以達到修改HttpServletRequest參數的目的
 **/
public class ModifyParametersWrapper extends HttpServletRequestWrapper {

    private Map<String, String[]> parameterMap; // 所有參數的Map集合

    public ModifyParametersWrapper(HttpServletRequest request) {
        super(request);
        parameterMap = request.getParameterMap();
    }

    // 重寫幾個HttpServletRequestWrapper中的方法

    /**
     * 獲取所有參數名
     *
     * @return 返回所有參數名
     */
    @Override
    public Enumeration<String> getParameterNames() {
        Vector<String> vector = new Vector<>(parameterMap.keySet());
        return vector.elements();
    }

    /**
     * 獲取指定參數名的值,如果有重複的參數名,則返回第一個的值 接收一般變量 ,如text類型
     *
     * @param name 指定參數名
     * @return 指定參數名的值
     */
    @Override
    public String getParameter(String name) {
        String[] results = parameterMap.get(name);
        if (results == null || results.length <= 0)
            return null;
        else {
            System.out.println("before modify: " + results[0]);
            return modify(results[0]);
        }
    }

    /**
     * 獲取指定參數名的所有值的數組,如:checkbox的所有數據
     * 接收數組變量 ,如checkbox類型
     */
    @Override
    public String[] getParameterValues(String name) {
        String[] results = parameterMap.get(name);
        if (results == null || results.length <= 0)
            return null;
        else {
            int length = results.length;
            for (int i = 0; i < length; i++) {
                System.out.println("before modify2: " + results[i]);
                results[i] = modify(results[i]);
            }
            return results;
        }
    }

    /**
     * 自定義的一個簡單修改原參數的方法,即:給原來的參數值前面添加了一個修改標誌的字符串
     *
     * @param string 原參數值
     * @return 修改之後的值
     */
    private String modify(String string) {
        return "Modified: " + string;
    }
}
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
	 @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("JwtAuthenticationTokenFilter Begin");
		  // @RequestParam參數修改示例
        ModifyParametersWrapper mParametersWrapper = new ModifyParametersWrapper(request);
        filterChain.doFilter(mParametersWrapper, response);
	}
}
package com.hong.security.controller;

import com.hong.security.common.Result;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

/**
 * @author wanghong
 * @date 2020/05/12 17:27
 * 個人賬號
 **/
@RestController
@RequestMapping("/account")
public class AccountController {
    @PostMapping("/modify")
    public void modify(@RequestParam("name") String name) {
        System.out.println("到達Controller層,after modify:" + name);
    }
}

在這裏插入圖片描述
在這裏插入圖片描述
參考:https://blog.51cto.com/983836259/1877592
https://www.jianshu.com/p/a8c9d45775ea
https://juejin.im/post/5e6aee11e51d452703137ce6

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章