You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring WS如何计算请求体字符串的特定哈希?获取请求体遇阻求助

Spring WS端点获取请求体并传递哈希值方案

问题背景

我们运行多个签名类似的Spring WS端点,方法签名如下:

JAXBElement<SomeResponseType> doSomthing(@RequestPayload final JAXBElement<SomeRequestType> request)

需要计算请求体字符串的特定哈希,但以下两种方式均不理想:

  • 将刚解组的对象重新编组
  • 在方法签名中直接使用字符串类型请求体并手动解组

此前尝试的两种方式都无效:

  • 通过RequestContextHolder获取HttpServletRequest读取输入流时,body始终为空;调用getReader()则抛出“该方法已被调用”的异常
  • 为方法添加@RequestBody参数会导致端点失效,不再是合法的WS端点

可行解决方案

方案1:使用ContentCachingRequestWrapper结合Filter

这是最可靠的实现方式,核心思路是缓存请求体让其可被多次读取,步骤如下:

  1. 编写缓存请求体的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之前执行。

  1. 在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);
    }
}
  1. 在端点方法中读取哈希值
    通过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供端点方法获取:

  1. 自定义处理器实现
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);
        }
    }
}
  1. 注册自定义处理器
    在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;
    }
}
  1. 在端点方法中读取哈希
    通过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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 07:21:34