完美解決request請(qǐng)求流只能讀取一次的問題
解決request請(qǐng)求流只能讀取一次的問題
實(shí)際開發(fā)碰到的問題
解決request請(qǐng)求流中的數(shù)據(jù)二次或多次使用問題
實(shí)際開發(fā)碰到的問題
springboot項(xiàng)目中,為了防止sql注入,采用Filter攔截器對(duì)所有請(qǐng)求流中的json數(shù)據(jù)進(jìn)行校驗(yàn),請(qǐng)求數(shù)據(jù)沒問題則繼續(xù)向下執(zhí)行,在后邊的代碼中應(yīng)用到請(qǐng)求參數(shù)值時(shí),發(fā)現(xiàn)request中的json數(shù)據(jù)為空;
除上邊描述的情況,嘗試過兩次從request中獲取json數(shù)據(jù),第二次同樣是獲取不到的。
解決request請(qǐng)求流中的數(shù)據(jù)二次或多次使用問題
繼承HttpServletRequestWrapper,將請(qǐng)求體中的流copy一份,覆寫getInputStream()和getReader()方法供外部使用。每次調(diào)用覆寫后的getInputStream()方法都是從復(fù)制出來的二進(jìn)制數(shù)組中進(jìn)行獲取,這個(gè)二進(jìn)制數(shù)組在對(duì)象存在期間一直存在,這樣就實(shí)現(xiàn)了流的重復(fù)讀取。
//增強(qiáng)類
public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {
//保存流
private byte[] requestBody = null;
public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
requestBody = StreamUtils.copyToByteArray(request.getInputStream());
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(requestBody);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return bais.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
@Override
public BufferedReader getReader() throws IOException{
return new BufferedReader(new InputStreamReader(getInputStream()));
}
}
//過濾器
@Component
@WebFilter
public class RequestSqlValidFilter implements Filter {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
ServletRequest requestWrapper = new BodyReaderHttpServletRequestWrapper((HttpServletRequest)request);
//請(qǐng)求參數(shù)合法,無sql注入
if((sqlValid(request, response))){
chain.doFilter(requestWrapper, response);//requestWrapper中保存著供二次使用的請(qǐng)求數(shù)據(jù)
}else {
logger.error("RequestSqlValidFilter sqlValid param error");
}
}
@Override
public void destroy() {
}
補(bǔ)充知識(shí):【java web】解決流讀完一次就不能再次獲取body數(shù)據(jù)的問題
問題來自我工作業(yè)務(wù)上的需求:前端請(qǐng)求時(shí)需要將json用RSA算法加密,數(shù)據(jù)經(jīng)過后端過濾器進(jìn)行自動(dòng)解密,這樣做的好處是以后不需要在每一個(gè)方法里都手動(dòng)解密一次,增加代碼的簡(jiǎn)潔性、可維護(hù)性。
但這樣一來便會(huì)面臨一個(gè)問題:http的request請(qǐng)求的輸入流在過濾器中就已經(jīng)被讀取了(因?yàn)樾枰x取并解密request body 里被前端加密了的json數(shù)據(jù)),流只能被讀取一次,這樣一來數(shù)據(jù)便傳不進(jìn)controller里,導(dǎo)致接下來的業(yè)務(wù)無法進(jìn)行。
于是我上網(wǎng)找了一些資料并成功解決了這個(gè)問題,基本思路是封裝原生的HttpServletRequest請(qǐng)求,將其輸入流里的數(shù)據(jù)保存在字節(jié)數(shù)組里,最后重寫getInputStream方法,使其之后每次讀取數(shù)據(jù)都是從字節(jié)數(shù)組里讀取的。
第一步:寫一個(gè)Request包裝類BodyReaderHttpServletRequestWrapper
public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {
private byte[] body;
public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
body = HttpHelper.getBodyString(request).getBytes(Charset.forName("UTF-8"));
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return bais.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
public void setInputStream(byte[] body){
this.body = body;
}
里面涉及一個(gè)HttpHelper類,順便也貼出來
public class HttpHelper {
/**
* 獲取請(qǐng)求Body
* @param request
* @return
*/
public static String getBodyString(ServletRequest request) {
StringBuilder sb = new StringBuilder();
InputStream inputStream = null;
BufferedReader reader = null;
try {
inputStream = request.getInputStream();
reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
String line = "";
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return sb.toString();
}
}
第二步:編寫包裝Filter
這個(gè)filter類可以將經(jīng)過的原生Request請(qǐng)求自動(dòng)包裝成BodyReaderHttpServletRequestWrapper
/**
* desc : 用于包裝原生request, 解決流讀完一次就不能再次獲取body數(shù)據(jù)的問題
* Created by Lon on 2018/3/9.
*/
public class RequestWrapperFilter implements Filter{
private static final Logger LOGGER = LoggerFactory.getLogger(RequestWrapperFilter.class);
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
ServletRequest requestWrapper = null;
if(request instanceof HttpServletRequest) {
requestWrapper = new BodyReaderHttpServletRequestWrapper((HttpServletRequest) request);
}
if(null == requestWrapper) {
LOGGER.error("包裝request失敗!將返回原來的request");
chain.doFilter(request, response);
} else {
LOGGER.info("包裝request成功");
chain.doFilter(requestWrapper, response);
}
}
@Override
public void destroy() {
}
}
第三步:在web.xml上配置過濾器
<filter> <filter-name>RequestWrapperFilter</filter-name> <filter-class>com.kx.security.filter.RequestWrapperFilter</filter-class> </filter> <filter-mapping> <filter-name>RequestWrapperFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
這里要注意的是這個(gè)包裝過濾器可能要寫在web.xml配置文件里某些過濾器的前面,比如解密過濾器,否則被解密過濾器先讀取流的話,包裝過濾器就讀取不了流了。
至此一個(gè)簡(jiǎn)單的解決方法就完成啦!
以上這篇完美解決request請(qǐng)求流只能讀取一次的問題就是小編分享給大家的全部?jī)?nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringAOP 如何通過JoinPoint獲取參數(shù)名和值
這篇文章主要介紹了SpringAOP 通過JoinPoint獲取參數(shù)名和值的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06
java實(shí)現(xiàn)簡(jiǎn)單的學(xué)生信息管理系統(tǒng)代碼實(shí)例
這篇文章主要介紹了java實(shí)現(xiàn)簡(jiǎn)單的學(xué)生信息管理系統(tǒng),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04
簡(jiǎn)單了解Spring Framework5.0新特性
這篇文章主要介紹了簡(jiǎn)單了解Spring Framework5.0新特性,涉及了核心框架修訂,核心容器更新,使用Kotlin進(jìn)行函數(shù)式編程等幾個(gè)方面的介紹,具有一定參考價(jià)值,需要的朋友可以了解下。2017-11-11
SpringBoot項(xiàng)目docker容器部署實(shí)現(xiàn)
本文主要介紹了SpringBoot項(xiàng)目docker容器部署實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-03-03
Spring詳細(xì)講解事務(wù)失效的場(chǎng)景
實(shí)際項(xiàng)目開發(fā)中,如果涉及到多張表操作時(shí),為了保證業(yè)務(wù)數(shù)據(jù)的一致性,大家一般都會(huì)采用事務(wù)機(jī)制,好多小伙伴可能只是簡(jiǎn)單了解一下,遇到事務(wù)失效的情況,便會(huì)無從下手,下面這篇文章主要給大家介紹了關(guān)于Spring事務(wù)失效場(chǎng)景的相關(guān)資料,需要的朋友可以參考下2022-07-07
Spring Boot報(bào)錯(cuò):No session repository could be auto-configured
這篇文章主要給大家介紹了關(guān)于Spring Boot報(bào)錯(cuò):No session repository could be auto-configured, check your configuration的解決方法,文中給出了詳細(xì)的解決方法,對(duì)遇到這個(gè)問題的朋友們具有一定參考價(jià)值,需要的朋友下面來一起看看吧。2017-07-07
Disconf實(shí)現(xiàn)分布式配置管理的原理與設(shè)計(jì)
這篇文章主要為大家介紹了Disconf實(shí)現(xiàn)分布式配置管理的原理與設(shè)計(jì)分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2022-03-03

