在前端開發過程中,將常出現前端代碼和后台服務不在一個服務器的情況,這時候前端js代碼調用后台接口,會出現跨域問題。:
1、這里的域是通過URL的頭部來識別的。瀏覽器並不會去嘗試判斷相同的ip地址對應着兩個域或者兩個域是否在同一個ip上。URL的頭部指window.location.protocol +window.location.host,也可以理解為“Domains, protocols and ports must match”。
2、因為協議、IP、端口造成的跨域問題,只修改前端代碼是沒用的。
下面我們主要講述前端和后台服務器不在同一域名下,引起的跨域問題。如果前端js跨域訪問后台接口,瀏覽器控制台會報錯。
可用通過兩種方式解決:JSONP和CORS。
一、JSONP
JSONP可以實現GET請求的跨域訪問。
前端代碼:
簡寫形式,效果相同
后台Java代碼:
PrintWriter out = response.getWriter(); JSONObject resultJSON = JSONObject.fromObject(map); //根據需要拼裝json String jsonpCallback = request.getParameter("jsonpCallback");//客戶端請求參數 out.println(jsonpCallback+"("+resultJSON.toString(1,1)+")");//返回jsonp格式數據
二、CORS
CORS定義一種跨域訪問的機制,可以讓AJAX實現跨域訪問。CORS 允許一個域上的網絡應用向另一個域提交跨域 AJAX 請求。
CORS與JSONP相比,無疑更為先進、方便和可靠。
1、 JSONP只能實現GET請求,而CORS支持所有類型的HTTP請求。
2、 使用CORS,開發者可以使用普通的XMLHttpRequest發起請求和獲得數據,比起JSONP有更好的錯誤處理。
3、 JSONP主要被老的瀏覽器支持,它們往往不支持CORS,而絕大多數現代瀏覽器都已經支持了CORS。
使用CORS,需要區分簡單請求和預檢機制。
在CORS規范中,GET、HEAD和POST這三個HTTP是“簡單HTTP方法”,Accept, Accept-Language, Content-Language和報頭Content-Type為multipart/form-data、application/x-www-form-urlencoded、text/plain中一種的稱為“簡單請求報頭”。
簡單請求:請求采用簡單HTTP方法,自定義請求報頭空或者自定義請求報頭均為簡單請求報頭。因為具有這些特性的請求不是以更新(添加、修改和刪除)資源為目的,服務端對請求的處理不會導致自身維護資源的改變。如果是簡單請求,設置Access-Control-Allow-Origin為請求站點IP或者*即可。
預檢機制:瀏覽器在發送真正的跨域資源請求前,先發送一個預檢請求(Preflight Request)。預檢請求為一個采用HTTP-OPTIONS方法的請求,這是一個不包含主體的請求,同時用戶憑證相關的報頭也會被剔除。基於真正資源請求的一些輔助授權的信息會包含在此預檢請求的相應報頭中。
資源的提供者在接收到預檢請求之后,根據其提供的相關報頭進行授權檢驗,確定請求站點是否值得信任,請求采用HTTP方法和自定義報頭是否被允許。如果預檢請求沒有通過授權檢驗,資源提供者一般會返回一個狀態為“400, Bad Reuqest”的響應。如果通過,則會返回一個狀態為“200, OK”的響應,授權相關信息會包含在響應報頭中。除了上面介紹的“Access-Control-Allow-Origin”報頭之外,預檢請求的響應還具有如下3個典型的報頭。
Access-Control-Allow-Methods:跨域資源請求允許采用的HTTP方法列表。
Access-Control-Allow-Headers:跨域資源請求允許攜帶的自定義報頭列表。
Access-Control-Max-Age:瀏覽器可以將響應結果進行緩存的時間(單位為秒),這樣可以讓瀏覽器避免頻繁地發送預檢請求。
瀏覽器在接收到預檢響應之后,會根據響應報頭確定后續發送的真正跨域資源請求是否會被接受,具體的檢驗邏輯如下:
通過請求的“Origin”報頭表示的源站點必須存在於“Access-Control-Allow-Origin”響應報頭標識的站點列表中。
l 響應報頭“Access-Control-Allow-Methods”不存在,或者預檢請求的“Access-Control-Request-Method”報頭表示的請求方法在其列表之內。
l 預檢請求的“Access-Control-Request-Headers”報頭存儲的報頭名稱均在響應報頭“Access-Control-Allow-Headers”表示的報頭列表之內。
只有在確定服務端一定會接受的情況下,瀏覽器才會發送真正跨域資源請求。預檢響應結果會被瀏覽器緩存,在“Access-Control-Max-Age”報頭設定的時間內,緩存的結果將被瀏覽器用戶進行授權檢驗,所以在此期間不會再有預檢請求發送。
在W3C的CORS規范來說,服務端利用響應報頭“Access-Control-Allow-Credentials”來表明自身是否支持用戶憑證。這里的用戶憑證類型包括Cookie、HTTP-Authentication報頭以及客戶端X.509證書(采用支持客戶端證書的TLS/SSL)等。如果設置“Access-Control-Allow-Credentials”為true,那么“Access-Control-Allow-Origin”不能為”*”,必須是指定的站點。
有預檢機制的后台代碼設置:
response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE"); response.setHeader("Access-Control-Max-Age", "3600"); response.setHeader("Access-Control-Allow-Headers","x-requested-with,content-type");
可以建立一個Filter
package com.chinamobile.cmss.bcse.web.interceptor; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletResponse; import org.springframework.stereotype.Component; @Component public class SimpleCORSFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletResponse response = (HttpServletResponse) res; response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE"); response.setHeader("Access-Control-Max-Age","3600"); response.setHeader("Access-Control-Allow-Headers","x-requested-with, content-type"); chain.doFilter(req, res); } public void init(FilterConfig filterConfig) {} public void destroy() {} }
在web.xml中添加代碼:
<filter> <filter-name>cors</filter-name> <filter-class>com.chinamobile.cmss.bcse.web.interceptor.SimpleCORSFilter</filter-class> </filter> <filter-mapping> <filter-name>cors</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
也可以只修改web.xml。在maven框架中添加maven依賴
<dependency> <groupId>com.thetransactioncompany</groupId> <artifactId>cors-filter</artifactId> <version>2.5</version> </dependency>
web.xml加入配置
<filter> <filter-name>CORS</filter-name> <filter-class>com.thetransactioncompany.cors.CORSFilter</filter-class> <init-param> <param-name>cors.allowOrigin</param-name> <param-value>*</param-value> </init-param> <init-param> <param-name>cors.supportedMethods</param-name> <param-value>GET, POST, HEAD, PUT, DELETE,OPTION</param-value> </init-param> <init-param> <param-name>cors.supportedHeaders</param-name> <param-value>Accept, Origin, X-Requested-With, Content-Type, Last-Modified</param-value> </init-param> <init-param> <param-name>cors.exposedHeaders</param-name> <param-value>Set-Cookie</param-value> </init-param> <init-param> <param-name>cors.supportsCredentials</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>cors.maxAge</param-name> <param-value>3600</param-value> </init-param></filter> <filter-mapping> <filter-name>CORS</filter-name> <url-pattern>/*</url-pattern></filter-mapping>