差異處

這裏顯示兩個版本的差異處。

連向這個比對檢視

Both sides previous revision 前次修改
下次修改
前次修改
java:web:restapi:x-http-method-override_filter [2016/02/26 21:45]
tony [Trace]
java:web:restapi:x-http-method-override_filter [2023/06/25 09:48] (目前版本)
行 1: 行 1:
 +{{tag>​java spring rest}}
 ====== Incorrect response(401) when using X-HTTP-Method-Override ====== ====== Incorrect response(401) when using X-HTTP-Method-Override ======
 ===== Problem ===== ===== Problem =====
-我們使用Spring的Rest,先前為了支援X-HTTP-Method-Override,在已存在的Filter做了此功能。某天,同事發現使用apache client api操作我們的API發生了401錯誤。而要發生這個問題有兩個條件,+我們使用Spring的Rest,先前為了支援X-HTTP-Method-Override,在已存在的Filter做了此功能。某天,同事發現使用apache client api操作我們的API發生了401錯誤
 +<​code>​  
 +{"​code":​401,"​message":"​Incorrect response","​links":​[{"​rel":"​more info","​href":"​http://​192.168.1.110:​8080/​TestWeb/​api/​documents"​}]} 
 +</​code>​ 
 +要發生這個問題有兩個條件,
   - 使用Digest認證   - 使用Digest認證
   - 使用X-HTTP-Method-Override   - 使用X-HTTP-Method-Override
 因此在我有空時,就開始追蹤這問題。 因此在我有空時,就開始追蹤這問題。
 ===== Trace ===== ===== Trace =====
-Sprint Security提供了DigestAuthentationFilter負責處理Digest認證。在使用者發出第一次請求後,Spring Security在察覺未經過驗證的情況下,會透過AuthenticationEntryPoint送出請求認證資訊。為了在認證失敗時,能夠輸出xml或json格式的錯誤訊息, +Sprint Security提供了DigestAuthentationFilter負責處理Digest認證。在使用者發出第一次請求後,Spring Security在察覺未經過驗證的情況下,會透過AuthenticationEntryPoint送出請求認證資訊。為了在認證失敗時,能夠輸出xml或json格式的錯誤訊息(可參考上方),我們extend了DigestAuthenticationEntryPoint:​
-<​code>​  +
-{"​code":​401,"​message":"​Incorrect response","​links":​[{"​rel":"​more info","​href":"​http://​192.168.1.110:​8080/​TestWeb/​api/​documents"​}]} +
-</​code>​ +
-我們extend了DigestAuthenticationEntryPoint:​+
 <code java> <code java>
-public class JsonDigestAuthenticationEntryPoint ​extends DigestAuthenticationEntryPoint {+public class CustomziedDigestAuthenticationEntryPoint ​extends DigestAuthenticationEntryPoint {
  
  private static final Log logger = LogFactory.getLog(JsonDigestAuthenticationEntryPoint.class);​  private static final Log logger = LogFactory.getLog(JsonDigestAuthenticationEntryPoint.class);​
行 41: 行 42:
 } }
 </​code>​ </​code>​
 +在這裡的authException,就是我們所看到的Incorrect Response。於上我就往上trace,發現DigestAuthentationFilter會透過request method去算reponse值。
 +===== Root Cause =====
 +仔細看一下[[https://​en.wikipedia.org/​wiki/​Digest_access_authentication|Digest認證流程]],其中吸引我目光的是:​
 +<​code>​
 +HA1=MD5(username:​realm:​password)
 +HA2=MD5(method:​digestURI)
 +response=MD5(HA1:​nonce:​HA2)
 +</​code>​
 +接著套入我們的情境:​\\
 +{{:​java:​web:​restapi:​rest_digest_filter_with_override_method_bug.png?​600|}}\\
 +  - Client收到Server認證請求。
 +  - Client對Server發送認證內容,其中包含response值。
 +  - Server的RestHeaderFilter首先處理X-HTTP-Method-Override請求,將Request method由Post改為Delete。
 +  - Server的DigestAuthenticationFilter發現response不相同。
 +  - Server回應錯誤訊息給Client。
 +這問題就在於:​ Client使用POST當method去計算HA2;而Server由於經過Request method的取代,會用DELETE去計算HA2。
 +===== Solution =====
 +那調整一下順序不就好了嗎?​ 的確,在我將RestHeaderFilter設定在Security相關Filter後,請求就可以正常處理了。但其實我們的RestHeaderFilter,還身兼將Rest API版本資訊塞到Header的責任。因此最後調整的順序為:​
 +  - RestHeaderFilter
 +  - SpringSecurityFilter
 +  - HttpMethodOverrideFilter
 +<code java>
 +public class HttpMethodOverrideFilter extends OncePerRequestFilter implements Filter {
 + private static final Logger logger = LoggerFactory.getLogger(HttpMethodOverrideFilter.class);​
 + protected static final String X_HTTP_METHOD_OVERRIDE_HEADER = "​X-HTTP-Method-Override";​
 +
 + @Override
 + protected void doFilterInternal(HttpServletRequest aRequest, HttpServletResponse aResponse, FilterChain aFilterChain)
 + throws ServletException,​ IOException {
 +
 + String methodOverrideValue = aRequest.getHeader(X_HTTP_METHOD_OVERRIDE_HEADER);​
 + if (methodOverrideValue != null && !methodOverrideValue.isEmpty()) {
 + String overrideMethod = methodOverrideValue.toUpperCase(Locale.ENGLISH);​
 + aRequest = new HttpMethodRequestWrapper(aRequest,​ overrideMethod);​
 + }
 + aFilterChain.doFilter(aRequest,​ aResponse);
 + }
  
 + private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
 + private final String method;
 +
 + public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
 + super(request);​
 + this.method = method;
 + }
 +
 + @Override
 + public String getMethod() {
 + return this.method;​
 + }
 + }
 +}
 +</​code>​
 +===== Note =====
 +如果有使用PostMan,可以仔細看看選擇不同的HTTP method,是否會產生不同的digest response。
 +=====    =====
 +----
 +\\
 +~~DISQUS~~