1

我正在开发使用类型请求的 Web 服务 application/x-www-form-urlencoded。以下是此类请求正文的示例:

loginId=tester&action=add&requestId=987654321&data=somedata

请求SHA1withRSA由客户端签名(使用 ),并且签名作为 HTTP 标头发送。

问题是我总是以不同的顺序获取参数,例如:

action=add&loginId=tester&requestId=987654321&data=somedata

因此验证签名总是失败。

有趣的是,只有当Content-Typeapplication/x-www-form-urlencoded并且如果我切换Content-Typetext/plain然后一切正常时才会发生这种情况。

我使用了不同类型的客户端(甚至使用 TCP 监视器监视流量),我确信问题不是由客户端应用程序引起的。

这是我的自定义消息转换器的一部分(请注意,我直接将传入的请求打印到控制台):

@Override
protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException,
        HttpMessageNotReadableException {

    log.debug("> readInternal - message to Object");
    InputStream inputStream = inputMessage.getBody();
    byte[] bytes = IOUtils.toByteArray(inputStream);
    String body = new String(bytes, charset);

    log.debug("Body: {}", body);
}

还有我的 Spring MVC 配置:

<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" />

<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
    <property name="messageConverters">
        <array>
            <bean class="converter.NvpHttpMessageConverter">
                <property name="charset" value="UTF-8" />
                <property name="nvpConverter" ref="nvpConverter" />
            </bean>
        </array>
    </property>

</bean>

我相信 Spring 以某种方式检测到内容类型application/x-www-form-urlencoded并使用某种预处理。我的假设正确吗?这个可以关掉吗?

我正在使用Tomcat 7。

4

2 回答 2

1

您的消息转换器不适用于本机请求,而是使用HttpInputMessage参数。那是一个Spring类。

你的inputMessage.getBody()问题出现了。默认情况下,使用一个ServletServerHttpRequest(另一个 Spring 类),它的方法中有这样的内容getBody()

public InputStream getBody() throws IOException {
    if (isFormSubmittal(this.servletRequest)) {
        return getFormBody(this.servletRequest);
    }
    else {
        return this.servletRequest.getInputStream();
    }
}

它委托给这样的私有实现:

private InputStream getFormBody(HttpServletRequest request) throws IOException {
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    Writer writer = new OutputStreamWriter(bos, FORM_CHARSET);

    Map<String, String[]> form = request.getParameterMap();
    for (Iterator<String> nameIterator = form.keySet().iterator(); nameIterator.hasNext();) {
        String name = nameIterator.next();
        List<String> values = Arrays.asList(form.get(name));
        for (Iterator<String> valueIterator = values.iterator(); valueIterator.hasNext();) {
            String value = valueIterator.next();
            writer.write(URLEncoder.encode(name, FORM_CHARSET));
            if (value != null) {
                writer.write('=');
                writer.write(URLEncoder.encode(value, FORM_CHARSET));
                if (valueIterator.hasNext()) {
                    writer.write('&');
                }
            }
        }
        if (nameIterator.hasNext()) {
            writer.append('&');
        }
    }
    writer.flush();

    return new ByteArrayInputStream(bos.toByteArray());
}

这是你的问题发生:

...
Map<String, String[]> form = request.getParameterMap();
...

您提到您使用的是 Tomcat 7,因此在这种情况下,request.getParameterMap()返回的 aorg.apache.catalina.util.ParameterMap只是 a HashMap,它对其内容顺序没有任何具体保证。因此,遍历参数并重新组合请求正文会打乱参数的原始顺序。

Spring 是一个灵活的框架,您可能会尝试对其做一些事情,但您会修复结果,而不是原因。原因是您的签名方法很脆弱

例如,这些是否会导致不同的签名?

aaa=1&bbb=2
bbb=2&aaa=1

还是这些?

aaa=Hello+world
aaa=Hello%20world

服务器通常不关心参数的顺序,并且编码值的不同方式最终成为相同的解码值。出于这个原因,在签署某些东西时,您通常会先执行规范化。您可以从URL 规范化或一些可用的 API(如 Twitter 的.

您会注意到在签名之前对参数进行排序是一个重要的步骤,它将消除您当前的问题。

于 2013-08-25T12:02:01.903 回答
0

这是我解决问题的方法:

我已经扩展了AnnotationMethodHandlerAdapter并覆盖了 AnnotationMethodHandlerAdapter#createHttpInputMessage

public class MyCustomAnnotationMethodHandlerAdapter extends AnnotationMethodHandlerAdapter {

    @Override
    protected HttpInputMessage createHttpInputMessage(HttpServletRequest servletRequest) throws Exception {
        return new ServletServerHttpRequest(servletRequest) {

            @Override
            public InputStream getBody() throws IOException {
                return getServletRequest().getInputStream();
            }
        };
    }
}

然后你只需要注册你的自定义 AnnotationMethodHandlerAdapter 而不是原来的:

<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" />

<bean class="webservice.handler.MyCustomAnnotationMethodHandlerAdapter">
    <property name="messageConverters">
        <array>
            <bean class="converter.NvpHttpMessageConverter">
                <property name="charset" value="UTF-8" />
                <property name="nvpConverter" ref="nvpConverter" />
            </bean>
        </array>
    </property>

</bean>
于 2013-08-26T15:00:23.083 回答