前端應用為靜態站點且部署在http://web.xxx.com域下,後端應用發布REST API並部署在http://api.xxx.com域下,如何使前端應用程式透過AJAX跨域訪問後端應用呢?這需要使用到CORS技術來實現,這也是目前最好的解決方案了。
[CORS全稱為Cross Origin Resource Sharing(跨域資源共享),服務端只需新增相關回應頭訊息,即可實現客戶端發出AJAX跨域請求。 ]
CORS技術非常簡單,易於實現,目前絕大多數瀏覽器均已支援該技術(IE8瀏覽器也支援了),服務端可透過任何程式語言來實現,只要能將CORS響應頭寫入response對像中即可。
下面我們繼續擴展REST框架,透過CORS技術實現AJAX跨域存取。
首先,我們需要寫一個Filter,用於過濾所有的HTTP請求,並將CORS回應頭寫入response物件中,程式碼如下:
public class CorsFilter implements Filter { private String allowOrigin; private String allowMethods; private String allowCredentials; private String allowHeaders; private String exposeHeaders; @Override public void init(FilterConfig filterConfig) throws ServletException { allowOrigin = filterConfig.getInitParameter("allowOrigin"); allowMethods = filterConfig.getInitParameter("allowMethods"); allowCredentials = filterConfig.getInitParameter("allowCredentials"); allowHeaders = filterConfig.getInitParameter("allowHeaders"); exposeHeaders = filterConfig.getInitParameter("exposeHeaders"); } @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; if (StringUtil.isNotEmpty(allowOrigin)) { List<String> allowOriginList = Arrays.asList(allowOrigin.split(",")); if (CollectionUtil.isNotEmpty(allowOriginList)) { String currentOrigin = request.getHeader("Origin"); if (allowOriginList.contains(currentOrigin)) { response.setHeader("Access-Control-Allow-Origin", currentOrigin); } } } if (StringUtil.isNotEmpty(allowMethods)) { response.setHeader("Access-Control-Allow-Methods", allowMethods); } if (StringUtil.isNotEmpty(allowCredentials)) { response.setHeader("Access-Control-Allow-Credentials", allowCredentials); } if (StringUtil.isNotEmpty(allowHeaders)) { response.setHeader("Access-Control-Allow-Headers", allowHeaders); } if (StringUtil.isNotEmpty(exposeHeaders)) { response.setHeader("Access-Control-Expose-Headers", exposeHeaders); } chain.doFilter(req, res); } @Override public void destroy() { } }
以上CorsFilter將從web.xml中讀取相關Filter初始化參數,並將在處理HTTP請求時將這些參數寫入對應的CORS回應頭中,下面大致描述一下這些CORS回應頭的意義:
Access-Control-Allow-Origin:允許存取的客戶端域名,例如:http://web.xxx.com,若為*,則表示從任意網域都能訪問,即不做任何限制。
Access-Control-Allow-Methods:允許存取的方法名,多個方法名稱以逗號分割,例如:GET,POST,PUT,DELETE,OPTIONS。
Access-Control-Allow-Credentials:是否允許請求帶有驗證訊息,若要取得客戶端網域下的cookie時,需要將其設為true。
Access-Control-Allow-Headers:允許服務端存取的用戶端請求頭,多個請求頭用逗號分割,例如:Content-Type。
Access-Control-Expose-Headers:允許客戶端存取的服務端回應頭,多個回應頭用逗號分割。
需要注意的是,CORS規格中定義Access-Control-Allow-Origin只允許兩種取值,要麼為*,要麼為具體的域名,也就是說,不支援同時配置多個域名。為了解決跨多個域的問題,需要在程式碼中做一些處理,這裡將Filter初始化參數作為一個網域的集合(用逗號分隔),只需從目前請求中取得Origin請求頭,就知道從哪個網域中發出的請求,若該請求在以上允許的網域集合中,則將其放入Access-Control-Allow-Origin回應頭,這樣跨多個網域的問題就輕鬆解決了。
以下是web.xml中配置CorsFilter的方法:
<filter> <filter-name>corsFilter</filter-name> <filter-class>com.xxx.api.cors.CorsFilter</filter-class> <init-param> <param-name>allowOrigin</param-name> <param-value>http://web.xxx.com</param-value> </init-param> <init-param> <param-name>allowMethods</param-name> <param-value>GET,POST,PUT,DELETE,OPTIONS</param-value> </init-param> <init-param> <param-name>allowCredentials</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>allowHeaders</param-name> <param-value>Content-Type</param-value> </init-param> </filter> <filter-mapping> <filter-name>corsFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
完成以上過程即可實現AJAX跨域功能了,但似乎還有另一個問題,由於REST是無狀態的,後端應用發布的REST API可在使用者未登入的情況下被任意調用,這顯然是不安全的,如何解決這個問題?我們需要為REST請求提供安全機制。