差異處
這裏顯示兩個版本的差異處。
下次修改 | 前次修改 | ||
java:web:restapi:x-http-method-override_filter [2016/02/26 17:13] tony 建立 |
java:web:restapi:x-http-method-override_filter [2016/02/26 22:33] tony [Root Cause] |
||
---|---|---|---|
行 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 ===== | ||
+ | 我們使用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認證 | ||
+ | - 使用X-HTTP-Method-Override | ||
+ | 因此在我有空時,就開始追蹤這問題。 | ||
+ | ===== Trace ===== | ||
+ | Sprint Security提供了DigestAuthentationFilter負責處理Digest認證。在使用者發出第一次請求後,Spring Security在察覺未經過驗證的情況下,會透過AuthenticationEntryPoint送出請求認證資訊。而為了在認證失敗時,能夠輸出xml或json格式的錯誤訊息(可參考上方),我們extend了DigestAuthenticationEntryPoint: | ||
+ | <code java> | ||
+ | public class CustomziedDigestAuthenticationEntryPoint extends DigestAuthenticationEntryPoint { | ||
+ | private static final Log logger = LogFactory.getLog(JsonDigestAuthenticationEntryPoint.class); | ||
+ | |||
+ | @Autowired | ||
+ | private MessageProcessor mMessageProcessor; | ||
+ | |||
+ | @Override | ||
+ | public void commence(HttpServletRequest request, | ||
+ | HttpServletResponse response, AuthenticationException authException) | ||
+ | throws IOException, ServletException { | ||
+ | |||
+ | // ... skip | ||
+ | |||
+ | httpResponse.addHeader("WWW-Authenticate", authenticateHeader); | ||
+ | |||
+ | try { | ||
+ | ErrorInfo erroInfo = ErrorResponseEntityCreator.createErrorInfo(request, HttpStatus.UNAUTHORIZED.value(), | ||
+ | authException.getMessage()); | ||
+ | |||
+ | response.setStatus(HttpStatus.UNAUTHORIZED.value()); | ||
+ | mMessageProcessor.handle(erroInfo, request, response); | ||
+ | } catch (Exception e) { | ||
+ | throw new ServletException(e); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | } | ||
+ | </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~~ |