Springboot如何設(shè)置過濾器及重復(fù)讀取request里的body
需求:
request的content-type為applciation/json,進(jìn)入controller之前需要把body中的參數(shù)取出來做一次處理,然后和hearder中的另一個(gè)參數(shù)做對(duì)比。
思路:
加一個(gè)過濾器,在過濾器中取出參數(shù)做處理,然后比較
注意:
body里的數(shù)據(jù)用流來讀取,只能讀取一次
HttpServletRequest的輸入流只能讀取一次的原因
我們先來看看為什么HttpServletRequest的輸入流只能讀一次,當(dāng)我們調(diào)用getInputStream()方法獲取輸入流時(shí)得到的是一個(gè)InputStream對(duì)象,而實(shí)際類型是ServletInputStream,它繼承于InputStream。
InputStream的read()方法內(nèi)部有一個(gè)postion,標(biāo)志當(dāng)前流被讀取到的位置,每讀取一次,該標(biāo)志就會(huì)移動(dòng)一次,如果讀到最后,read()會(huì)返回-1,表示已經(jīng)讀取完了。如果想要重新讀取則需要調(diào)用reset()方法,position就會(huì)移動(dòng)到上次調(diào)用mark的位置,mark默認(rèn)是0,所以就能從頭再讀了。調(diào)用reset()方法的前提是已經(jīng)重寫了reset()方法,當(dāng)然能否reset也是有條件的,它取決于markSupported()方法是否返回true。
InputStream默認(rèn)不實(shí)現(xiàn)reset(),并且markSupported()默認(rèn)也是返回false,這一點(diǎn)查看其源碼便知:

我們?cè)賮砜纯碨ervletInputStream,可以看到該類沒有重寫mark(),reset()以及markSupported()方法:

綜上,InputStream默認(rèn)不實(shí)現(xiàn)reset的相關(guān)方法,而ServletInputStream也沒有重寫reset的相關(guān)方法,這樣就無法重復(fù)讀取流,這就是我們從request對(duì)象中獲取的輸入流就只能讀取一次的原因。
重復(fù)讀取body中數(shù)據(jù)的方法
這個(gè)自定義的requestWrapper繼承了HttpServletRequestWrapper ,HttpServletRequestWrapper 是一個(gè)ServletRequest的包裝類同時(shí)也是ServletRequest的實(shí)現(xiàn)類。
在這個(gè)自定義的requestWrapper里,用一個(gè)String做緩存,在構(gòu)造方法里先把request的body中的數(shù)據(jù)緩存起來,然后重寫了getInputStream,返回這個(gè)緩存的body,而不是從流中讀取。
這樣,在需要多次讀取body的地方,只需要在過濾器中把原來的request換成這個(gè)自定義的request,然后把這個(gè)自定義的帶緩存功能的request傳到后續(xù)的過濾器鏈中。
public class BodyReaderRequestWrapper extends HttpServletRequestWrapper {
private final String body;
/**
*
* @param request
*/
public BodyReaderRequestWrapper(HttpServletRequest request) throws IOException{
super(request);
StringBuilder sb = new StringBuilder();
InputStream ins = request.getInputStream();
BufferedReader isr = null;
try{
if(ins != null){
isr = new BufferedReader(new InputStreamReader(ins));
char[] charBuffer = new char[128];
int readCount = 0;
while((readCount = isr.read(charBuffer)) != -1){
sb.append(charBuffer,0,readCount);
}
}else{
sb.append("");
}
}catch (IOException e){
throw e;
}finally {
if(isr != null) {
isr.close();
}
}
sb.toString();
body = sb.toString();
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayIns = new ByteArrayInputStream(body.getBytes());
ServletInputStream servletIns = new ServletInputStream() {
@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 byteArrayIns.read();
}
};
return servletIns;
}
}springboot的過濾器
2個(gè)注解:
@WebFilter(過濾器上)@ServletComponentScan(加在啟動(dòng)類上,支持servlet components掃描(為了webfilter))
@Order(999) // 序號(hào)越低,優(yōu)先級(jí)越高
// 加上WebFilter即可成為過濾器
@WebFilter(filterName="myFilter", urlPatterns="/api/workorder/service/selfAppeal")
public class ExternalFilter implements Filter {
private final static Logger logger = LoggerFactory.getLogger(ExternalFilter.class);
@Override
public void init(FilterConfig filterConfig) throws ServletException {
logger.info("filter init");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
ResponseObject object = new ResponseObject();
HttpServletRequest req = (HttpServletRequest)servletRequest;
HttpServletResponse res = (HttpServletResponse)servletResponse;
// 一個(gè)request的包裝類,初始化時(shí)緩存了body,重寫了getInputStream返回緩存的body,實(shí)現(xiàn)重復(fù)讀取body
BodyReaderRequestWrapper requestWrapper = new BodyReaderRequestWrapper(req);
String requestURI = requestWrapper.getRequestURI();
System.out.println("--------------------->過濾器:請(qǐng)求地址" + requestURI);
String md5 = requestWrapper.getHeader("md5") ;
if (md5 == null || !md5.toLowerCase().equals(MD5.md5(ReqGetBody.getBody(requestWrapper)).toLowerCase())) {
object.setStatus(501);
object.setStatusText("數(shù)據(jù)md5校驗(yàn)失敗");
render(object, res);
return;
}
// 這里傳遞下去的就是自定義的request了,所以后續(xù)的Controller才能重復(fù)讀取到body里的參數(shù)
filterChain.doFilter(requestWrapper, res);
}
@Override
public void destroy() {
}
/**
* @Title: render
* @Description: 發(fā)送Response
* @param object
* @param response void
* @author MasterYi
* @date 2020年1月15日上午10:48:45
*/
private void render(ResponseObject object, HttpServletResponse response) {
response.setContentType("application/json;charset=UTF-8");
try {
response.setStatus(200);
response.getWriter().write(JSONObject.toJSON(object).toString());
} catch (IOException e) {
logger.error("ExternalFilter寫入response異常");
}
}
}上面的getBody的代碼
從body中取值的具體操作
public class ReqGetBody {
static public String getBody(HttpServletRequest request) {
try {
ServletInputStream in = request.getInputStream();
String body;
body = StreamUtils.copyToString(in, Charset.forName("UTF-8"));
return body;
} catch (IOException e) {
e.printStackTrace();
return "";
}
}
}以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
- Springboot應(yīng)用中過濾器如何修改response的header和body內(nèi)容
- SpringBoot項(xiàng)目如何設(shè)置權(quán)限攔截器和過濾器
- SpringBoot整合Spring?Security過濾器鏈加載執(zhí)行流程源碼分析(最新推薦)
- springBoot 過濾器去除請(qǐng)求參數(shù)前后空格實(shí)例詳解
- Springboot詳解如何實(shí)現(xiàn)SQL注入過濾器過程
- springboot webflux 過濾器(使用RouterFunction實(shí)現(xiàn))
- Springboot實(shí)現(xiàn)過濾器的兩種方式
相關(guān)文章
使用springboot 獲取控制器參數(shù)的幾種方法小結(jié)
這篇文章主要介紹了使用springboot 獲取控制器參數(shù)的幾種方法小結(jié),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12
解析XML文件時(shí)的嵌套異常SAXParseException問題
這篇文章主要介紹了解析XML文件時(shí)的嵌套異常SAXParseException問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-04-04
java實(shí)現(xiàn)Fibonacci算法實(shí)例
這篇文章主要介紹了java實(shí)現(xiàn)Fibonacci算法的方法,以實(shí)例形式較為詳細(xì)的分析了java實(shí)現(xiàn)Fibonacci算法的原理與相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-09-09
Mybatis在注解上如何實(shí)現(xiàn)動(dòng)態(tài)SQL
這篇文章主要介紹了Mybatis在注解上如何實(shí)現(xiàn)動(dòng)態(tài)SQL,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06
SpringBoot使用classfinal-maven-plugin插件加密Jar包的示例代碼
這篇文章給大家介紹了SpringBoot使用classfinal-maven-plugin插件加密Jar包的實(shí)例,文中通過代碼示例和圖文講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-02-02
使用自定義Json注解實(shí)現(xiàn)輸出日志字段脫敏
這篇文章主要介紹了使用自定義Json注解實(shí)現(xiàn)輸出日志字段脫敏,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12

