拦截器中读取request中的流后,controller 无法获取到数据解决方案

本文介绍了一种解决拦截器中验证Token与Controller接收@RequestBody数据冲突的方法。通过自定义HttpServletRequest包装类及过滤器,实现了多次读取请求体的需求。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一般我们会在InterceptorAdapter拦截器中对请求的token进行验证

如果是content-type 是 application/x-www-form-urlencoded  则没有什么问题

如果我们的接口是用@RequestBody来接受数据,那么我们在拦截器中验证token时

需要读取request的输入流  ,因为 ServletRequest中getReader()和getInputStream()只能调用一次

这样就会导致controller 无法拿到数据。


解决方法 :


自定义一个类 BodyReaderHttpServletRequestWrapper.java 


import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

import com.microdata.core.util.Encodes;

public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {
	 private Map<String, String[]> paramsMap;

	    @Override
	    public Map getParameterMap() {
	        return paramsMap;
	    }

	    @Override
	    public String getParameter(String name) {// 重写getParameter,代表参数从当前类中的map获取
	        String[] values = paramsMap.get(name);
	        if (values == null || values.length == 0) {
	            return null;
	        }
	        return values[0];
	    }

	    @Override
	    public String[] getParameterValues(String name) {// 同上
	        return paramsMap.get(name);
	    }

	    @Override
	    public Enumeration getParameterNames() {
	        return Collections.enumeration(paramsMap.keySet());
	    }

	    private String getRequestBody(InputStream stream) {
	        String line = "";
	        StringBuilder body = new StringBuilder();
	        int counter = 0;

	        // 读取POST提交的数据内容
	        BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
	        try {
	            while ((line = reader.readLine()) != null) {
	                if (counter > 0) {
	                    body.append("rn");
	                }
	                body.append(line);
	                counter++;
	            }
	        } catch (IOException e) {
	            e.printStackTrace();
	        }

	        return body.toString();
	    }

	    private HashMap<String, String[]> getParamMapFromPost(HttpServletRequest request) {

	        String body = "";
	        try {
	            body = getRequestBody(request.getInputStream());
	        } catch (IOException e) {
	            e.printStackTrace();
	        }
	        HashMap<String, String[]> result = new HashMap<String, String[]>();

	        if (null == body || 0 == body.length()) {
	            return result;
	        }

	        return parseQueryString(body);
	    }

	    // 自定义解码函数
	    private String decodeValue(String value) {
	        if (value.contains("%u")) {
	            return Encodes.urlDecode(value);
	        } else {
	            try {
	                return URLDecoder.decode(value, "UTF-8");
	            } catch (UnsupportedEncodingException e) {
	                return "";// 非UTF-8编码
	            }
	        }
	    }

	    public HashMap<String, String[]> parseQueryString(String s) {
	        String valArray[] = null;
	        if (s == null) {
	            throw new IllegalArgumentException();
	        }
	        HashMap<String, String[]> ht = new HashMap<String, String[]>();
	        StringTokenizer st = new StringTokenizer(s, "&");
	        while (st.hasMoreTokens()) {
	            String pair = (String) st.nextToken();
	            int pos = pair.indexOf('=');
	            if (pos == -1) {
	                continue;
	            }
	            String key = pair.substring(0, pos);
	            String val = pair.substring(pos + 1, pair.length());
	            if (ht.containsKey(key)) {
	                String oldVals[] = (String[]) ht.get(key);
	                valArray = new String[oldVals.length + 1];
	                for (int i = 0; i < oldVals.length; i++) {
	                    valArray[i] = oldVals[i];
	                }
	                valArray[oldVals.length] = decodeValue(val);
	            } else {
	                valArray = new String[1];
	                valArray[0] = decodeValue(val);
	            }
	            ht.put(key, valArray);
	        }
	        return ht;
	    }

	    private Map<String, String[]> getParamMapFromGet(HttpServletRequest request) {
	        return parseQueryString(request.getQueryString());
	    }

	    private final byte[] body; // 报文

	    public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
	        super(request);
	        body = readBytes(request.getInputStream());

	        // 首先从POST中获取数据
	        if ("POST".equals(request.getMethod().toUpperCase())) {
	            paramsMap = getParamMapFromPost(this);
	        } else {
	            paramsMap = getParamMapFromGet(this);
	        }

	    }

	    @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 arg0) {

				}
	        };
	    }

	    private static byte[] readBytes(InputStream in) throws IOException {
	        BufferedInputStream bufin = new BufferedInputStream(in);
	        int buffSize = 1024;
	        ByteArrayOutputStream out = new ByteArrayOutputStream(buffSize);

	        byte[] temp = new byte[buffSize];
	        int size = 0;
	        while ((size = bufin.read(temp)) != -1) {
	            out.write(temp, 0, size);
	        }
	        bufin.close();

	        byte[] content = out.toByteArray();
	        return content;
	    }

}


自定义Filter   HttpServletRequestReplacedFilter.java

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import com.microdata.core.request.BodyReaderHttpServletRequestWrapper;
 
public class HttpServletRequestReplacedFilter implements Filter {
	@Override
	public void destroy() {

	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {

		ServletRequest requestWrapper = null;
		if (request instanceof HttpServletRequest) {
			HttpServletRequest httpServletRequest = (HttpServletRequest) request;
			if ("POST".equals(httpServletRequest.getMethod().toUpperCase())
					&& httpServletRequest.getContentType().equalsIgnoreCase(
							"application/json; charset=utf-8")) {
				requestWrapper = new BodyReaderHttpServletRequestWrapper(
						(HttpServletRequest) request);
			}
		}

		if (requestWrapper == null) {
			chain.doFilter(request, response);
		} else {
			chain.doFilter(requestWrapper, response); 
		}
	}

	@Override
	public void init(FilterConfig arg0) throws ServletException {

	}

}


在web.xml 配置 


	<filter>
		<filter-name>HttpServletRequestReplacedFilter</filter-name>
		<filter-class>com.microdata.core.filter.HttpServletRequestReplacedFilter</filter-class>
		<init-param>
			<param-name>encoding</param-name>
			<param-value>utf-8</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>HttpServletRequestReplacedFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>


Encodes 类

package com.microdata.core.util;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;

import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.StringEscapeUtils;

/**
 * 封装各种格式的编码解码工具类.
 * 
 * 1.Commons-Codec的 hex/base64 编码
 * 2.自制的base62 编码
 * 3.Commons-Lang的xml/html escape
 * 4.JDK提供的URLEncoder
 * 
 */
public class Encodes {

	private static final String DEFAULT_URL_ENCODING = "UTF-8";
	private static final char[] BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".toCharArray();

	/**
	 * Hex编码.
	 */
	public static String encodeHex(byte[] input) {
		return Hex.encodeHexString(input);
	}

	/**
	 * Hex解码.
	 */
	public static byte[] decodeHex(String input) {
		try {
			return Hex.decodeHex(input.toCharArray());
		} catch (DecoderException e) {
			throw Exceptions.unchecked(e);
		}
	}

	/**
	 * Base64编码.
	 */
	public static String encodeBase64(byte[] input) {
		return Base64.encodeBase64String(input);
	}

	/**
	 * Base64编码, URL安全(将Base64中的URL非法字符'+'和'/'转为'-'和'_', 见RFC3548).
	 */
	public static String encodeUrlSafeBase64(byte[] input) {
		return Base64.encodeBase64URLSafeString(input);
	}

	/**
	 * Base64解码.
	 */
	public static byte[] decodeBase64(String input) {
		return Base64.decodeBase64(input);
	}

	/**
	 * Base62编码。
	 */
	public static String encodeBase62(byte[] input) {
		char[] chars = new char[input.length];
		for (int i = 0; i < input.length; i++) {
			chars[i] = BASE62[(input[i] & 0xFF) % BASE62.length];
		}
		return new String(chars);
	}

	/**
	 * Html 转码.
	 */
	public static String escapeHtml(String html) {
		return StringEscapeUtils.escapeHtml4(html);
	}

	/**
	 * Html 解码.
	 */
	public static String unescapeHtml(String htmlEscaped) {
		return StringEscapeUtils.unescapeHtml4(htmlEscaped);
	}

	/**
	 * Xml 转码.
	 */
	public static String escapeXml(String xml) {
		return StringEscapeUtils.escapeXml(xml);
	}

	/**
	 * Xml 解码.
	 */
	public static String unescapeXml(String xmlEscaped) {
		return StringEscapeUtils.unescapeXml(xmlEscaped);
	}

	/**
	 * URL 编码, Encode默认为UTF-8.
	 */
	public static String urlEncode(String part) {
		try {
			return URLEncoder.encode(part, DEFAULT_URL_ENCODING);
		} catch (UnsupportedEncodingException e) {
			throw Exceptions.unchecked(e);
		}
	}

	/**
	 * URL 解码, Encode默认为UTF-8.
	 */
	public static String urlDecode(String part) {
		try {
			return URLDecoder.decode(part, DEFAULT_URL_ENCODING);
		} catch (UnsupportedEncodingException e) {
			throw Exceptions.unchecked(e);
		}
	}
}


<think>好的,我现在需要解答用户的问题:“过滤器或拦截器读取请求中的token会消费请求体嘛”。首先,我得理解这个问题的核心是什么。用户想知道在过滤器或拦截器读取请求中的token时,是否会导致请求体被消费,也就是后续的处理中无法再次读取请求体。 接下来,我需要回忆一下HTTP请求的结构。HTTP请求通常由请求头和请求体组成。请求头包含诸如Content-Type、Authorization(可能携带token)等信息,而请求体一般用于POST、PUT等方法传输数据。 然后,关于过滤器和拦截器,不同的框架可能有不同的实现。比如,Java中的Servlet过滤器和Spring的拦截器。需要明确它们的处理程。在Servlet过滤器中,请求体是通过ServletRequest对象获取的,而一旦读取了输入,就会消费掉,导致后续无法再次读取。同样,Spring的拦截器可能也会有类似的问题,因为底层也是基于Servlet的机制。 但是,用户提到的是读取token,而token通常存放在请求头里,比如Authorization头。这种情况下,读取请求头的信息不会触及请求体的输入,因此不会消费请求体。这时候应该不会影响后续对请求体读取。 不过,如果token是放在请求体中的,比如某些API设计可能将token放在JSON数据里,这时候读取请求体就会消费输入。这时候如果在过滤器或拦截器读取请求体中的token,后续处理就无法再次读取请求体,导致错误。 接下来需要考虑解决方案。如果必须从请求体获取token,可以使用包装请求对象的方法,比如HttpServletRequestWrapper,缓存请求体的内容,使得可以多次读取。这样在过滤器中读取后,后续处理还能获取请求体数据。 还需要验证一下不同框架中的行为是否一致。例如,在Spring MVC中,使用@RequestBody注解时,输入只能读取一次,如果过滤器已经读取过,这里会抛出异常。因此,正确的做法是使用内容缓存包装器。 总结起来,回答需要分情况讨论:token在请求头还是请求体读取方式的不同导致是否消费请求体,以及对应的解决方法。同时,要确保回答结构清晰,逐步引导用户理解问题所在和解决方案。</think>在过滤器或拦截器读取请求中的 **Token** 是否会消费请求体,取决于 **Token 的存储位置**和**读取方式**。以下是分步分析: --- ### 1. **Token 的常见存储位置** - **请求头**(如 `Authorization` 头):这是最常见的方式,例如 `Bearer Token`。 - **请求参数**:如 URL 参数或表单参数。 - **请求体**:例如 JSON 或 XML 格式的请求体中包含 Token。 --- ### 2. **读取 Token 是否消费请求体** #### 情况 1:Token 在请求头或 URL 参数中 - **不会消费请求体** 如果通过以下方式获取 Token: ```java // 从请求头读取 String token = request.getHeader("Authorization"); // 从 URL 参数读取 String token = request.getParameter("token"); ``` **原因**:HTTP 请求头和 URL 参数与请求体是独立的部分,读取它们不会触发请求体的解析。 #### 情况 2:Token 在请求体中(如 JSON) - **会消费请求体** 如果通过以下方式获取 Token: ```java // 从输入读取请求体 InputStream inputStream = request.getInputStream(); String body = IOUtils.toString(inputStream, StandardCharsets.UTF_8); ``` **原因**:HTTP 请求的输入(`InputStream`)是单向的,一旦被读取,后续代码无法再次读取。 **后果**:后续 Controller 或业务逻辑中使用 `@RequestBody` 注解时会抛出异常(如 `Stream closed`)。 --- ### 3. **解决方案:避免请求体被消费** 如果必须从请求体读取 Token,可通过以下方法复用请求体: #### 方法 1:使用包装类缓存请求体 - 自定义 `HttpServletRequestWrapper`,将请求体内容缓存到内存: ```java public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper { private byte[] cachedBody; public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException { super(request); this.cachedBody = IOUtils.toByteArray(request.getInputStream()); } @Override public ServletInputStream getInputStream() { return new CachedBodyServletInputStream(this.cachedBody); } } ``` - **过滤器/拦截器中**:将原始请求替换为包装后的请求: ```java CachedBodyHttpServletRequest cachedRequest = new CachedBodyHttpServletRequest(request); chain.doFilter(cachedRequest, response); // 传递包装后的请求 ``` #### 方法 2:框架提供的工具 - 在 Spring 中,可使用 `ContentCachingRequestWrapper`: ```java ContentCachingRequestWrapper wrapper = new ContentCachingRequestWrapper(request); String body = new String(wrapper.getContentAsByteArray(), wrapper.getCharacterEncoding()); chain.doFilter(wrapper, response); ``` --- ### 4. **总结** | Token 位置 | 是否消费请求体 | 解决方案 | |----------------|--------------|-----------------------| | 请求头/URL 参数 | 否 | 无需处理 | | 请求体 | 是 | 使用请求体缓存包装类 | **关键原则**: - 优先将 Token 放在请求头中(符合 RESTful 设计规范)。 - 避免在过滤器/拦截器中直接读取请求体输入,除非使用缓存机制。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值