2013-08-23 28 views
1

我正在研究使用類型爲application/x-www-form-urlencoded的請求的Web服務。這裏是這樣的請求體的示例:由客戶端Spring MVC中內容類型應用程序/ 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-Type然後text/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> 

我相信春天以某種方式檢測到內容類型爲application/x-www-form-urlencoded,並使用某種預處理。我的假設是否正確?這可以關閉嗎?

我使用Tomcat 7

回答

1

您的留言信息轉換器不能與本地請求,但有HttpInputMessage參數工作。那是一個Spring類。

inputMessage.getBody()是您的問題出現。默認情況下,ServletServerHttpRequest(又一個春天類)使用具有在其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()返回org.apache.catalina.util.ParameterMap這只是一個HashMap它沒有具體保證其內容順序。因此迭代參數並重新構造請求體就是弄亂了參數的原始順序。

春天是一個靈活的框架和你可能會嘗試做一些事情,但你會固定的影響,而不是原因。原因在於您的簽名方法很脆弱

例如,這些應該導致不同的簽名?

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

還是這些?

aaa=Hello+world 
aaa=Hello%20world 

服務器通常不關心參數的順序,不同的編碼方式最終會以相同的解碼值結束。出於這個原因,在簽署某些東西時,您通常首先執行的標準化。你可以借用URL normalizationavailable APIs like Twitter's的一些想法。

您會注意到,在簽名之前對參數進行排序是消除當前問題的重要步驟。

+0

+1。請注意,PayPal對他們的IPN通知犯了同樣的錯誤:有一個簽名步驟假定固定的參數順序。 OP並不孤單。 – EJP

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> 
相關問題