Spring WS如何计算请求体字符串的特定哈希?获取请求体遇阻求助
Spring WS端点获取请求体并传递哈希值方案
问题背景
我们运行多个签名类似的Spring WS端点,方法签名如下:
JAXBElement<SomeResponseType> doSomthing(@RequestPayload final JAXBElement<SomeRequestType> request)
需要计算请求体字符串的特定哈希,但以下两种方式均不理想:
- 将刚解组的对象重新编组
- 在方法签名中直接使用字符串类型请求体并手动解组
此前尝试的两种方式都无效:
- 通过
RequestContextHolder获取HttpServletRequest读取输入流时,body始终为空;调用getReader()则抛出“该方法已被调用”的异常 - 为方法添加
@RequestBody参数会导致端点失效,不再是合法的WS端点
可行解决方案
方案1:使用ContentCachingRequestWrapper结合Filter
这是最可靠的实现方式,核心思路是缓存请求体让其可被多次读取,步骤如下:
- 编写缓存请求体的Filter
创建Filter,将原始HttpServletRequest包装为ContentCachingRequestWrapper,Spring提供的这个包装类会自动缓存请求体内容:
import org.springframework.web.util.ContentCachingRequestWrapper; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; public class RequestBodyCachingFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper((HttpServletRequest) request); chain.doFilter(wrappedRequest, response); } }
注意要将该Filter注册到Spring容器,并且确保它在Spring WS的处理Filter之前执行。
- 在Filter中计算哈希并存储
修改Filter逻辑,在调用后续处理链前读取缓存的请求体计算哈希,并存入请求属性:
@Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper((HttpServletRequest) request); // 读取缓存的请求体字节数组并转为字符串 byte[] requestBodyBytes = wrappedRequest.getContentAsByteArray(); String requestBody = new String(requestBodyBytes, wrappedRequest.getCharacterEncoding()); // 计算目标哈希(这里以MD5为例,替换为你的哈希算法) String hash = calculateHash(requestBody); // 将哈希存入请求属性,供后续端点方法读取 wrappedRequest.setAttribute("REQUEST_BODY_HASH", hash); chain.doFilter(wrappedRequest, response); } private String calculateHash(String content) { try { java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5"); byte[] hashBytes = md.digest(content.getBytes()); StringBuilder sb = new StringBuilder(); for (byte b : hashBytes) { sb.append(String.format("%02x", b)); } return sb.toString(); } catch (Exception e) { throw new RuntimeException("哈希计算失败", e); } }
- 在端点方法中读取哈希值
通过RequestContextHolder获取当前请求,再取出属性中的哈希值:
import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.xml.bind.JAXBElement; import org.springframework.ws.server.endpoint.annotation.Endpoint; import org.springframework.ws.server.endpoint.annotation.PayloadRoot; import org.springframework.ws.server.endpoint.annotation.RequestPayload; import org.springframework.ws.server.endpoint.annotation.ResponsePayload; @Endpoint public class SampleEndpoint { private static final String NAMESPACE_URI = "http://your-namespace.com"; @PayloadRoot(namespace = NAMESPACE_URI, localPart = "SomeRequestType") @ResponsePayload public JAXBElement<SomeResponseType> doSomthing(@RequestPayload JAXBElement<SomeRequestType> request) { // 从请求属性中获取哈希值 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes(); String requestBodyHash = (String) attributes.getRequest().getAttribute("REQUEST_BODY_HASH"); // 后续业务逻辑中使用该哈希 // ... // 构造并返回响应 SomeResponseType response = new SomeResponseType(); return new JAXBElement<>(new QName(NAMESPACE_URI, "SomeResponseType"), SomeResponseType.class, response); } }
方案2:自定义消息处理器
通过扩展Spring WS的MarshallingPayloadMethodProcessor,在解组请求体前读取内容计算哈希,并存入MessageContext供端点方法获取:
- 自定义处理器实现
import org.springframework.ws.server.endpoint.adapter.method.MarshallingPayloadMethodProcessor; import org.springframework.ws.context.MessageContext; import javax.xml.transform.Source; import java.io.ByteArrayOutputStream; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.stream.StreamResult; public class HashCalculatingPayloadProcessor extends MarshallingPayloadMethodProcessor { public HashCalculatingPayloadProcessor(Marshaller marshaller, Unmarshaller unmarshaller) { super(marshaller, unmarshaller); } @Override protected Object unmarshalRequest(MessageContext messageContext, Object[] arguments) throws Exception { // 将请求体Source转为字符串 Source requestSource = messageContext.getRequest().getPayloadSource(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); Transformer transformer = TransformerFactory.newInstance().newTransformer(); transformer.transform(requestSource, new StreamResult(baos)); String requestBody = baos.toString(messageContext.getRequest().getCharacterEncoding()); // 计算哈希 String hash = calculateHash(requestBody); // 将哈希存入MessageContext messageContext.setProperty("REQUEST_BODY_HASH", hash); // 调用父类方法完成正常解组逻辑 return super.unmarshalRequest(messageContext, arguments); } private String calculateHash(String content) { // 同Filter中的哈希实现逻辑 try { java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5"); byte[] hashBytes = md.digest(content.getBytes()); StringBuilder sb = new StringBuilder(); for (byte b : hashBytes) { sb.append(String.format("%02x", b)); } return sb.toString(); } catch (Exception e) { throw new RuntimeException("哈希计算失败", e); } } }
- 注册自定义处理器
在Spring配置类中替换默认的MarshallingPayloadMethodProcessor:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.ws.server.endpoint.adapter.DefaultMethodEndpointAdapter; import org.springframework.ws.server.endpoint.adapter.method.MethodArgumentResolver; import org.springframework.ws.server.endpoint.adapter.method.MethodReturnValueHandler; import javax.xml.bind.JAXBContext; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import java.util.ArrayList; import java.util.List; @Configuration public class WebServiceConfig { @Bean public DefaultMethodEndpointAdapter defaultMethodEndpointAdapter() throws Exception { DefaultMethodEndpointAdapter adapter = new DefaultMethodEndpointAdapter(); // 初始化JAXB相关实例 JAXBContext jaxbContext = JAXBContext.newInstance(SomeRequestType.class, SomeResponseType.class); Marshaller marshaller = jaxbContext.createMarshaller(); Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); // 创建自定义处理器 HashCalculatingPayloadProcessor processor = new HashCalculatingPayloadProcessor(marshaller, unmarshaller); // 设置参数解析器和返回值处理器 List<MethodArgumentResolver> argumentResolvers = new ArrayList<>(); argumentResolvers.add(processor); adapter.setMethodArgumentResolvers(argumentResolvers); List<MethodReturnValueHandler> returnValueHandlers = new ArrayList<>(); returnValueHandlers.add(processor); adapter.setMethodReturnValueHandlers(returnValueHandlers); return adapter; } }
- 在端点方法中读取哈希
通过MessageContext参数直接获取哈希值:
@PayloadRoot(namespace = NAMESPACE_URI, localPart = "SomeRequestType") @ResponsePayload public JAXBElement<SomeResponseType> doSomthing(@RequestPayload JAXBElement<SomeRequestType> request, MessageContext messageContext) { String requestBodyHash = (String) messageContext.getProperty("REQUEST_BODY_HASH"); // 业务逻辑处理 // ... }
此前尝试无效的原因
- 直接读取
HttpServletRequest的输入流时,Spring WS已经先读取过一次输入流进行解组操作,而输入流只能被读取一次,因此后续读取会得到空内容或抛出异常。 @RequestBody是Spring MVC的专属注解,Spring WS并不支持该注解,添加后会破坏WS端点的处理逻辑,导致端点失效。
内容的提问来源于stack exchange,提问作者dermoritz
相关产品推荐
相关产品推荐

