熟悉又陌生的跨域訪問與CORS
說到跨域訪問,是既熟悉又陌生,熟悉是因為只要做過web專案,尤其是前後端分離的專案,都碰到過ajax跨域訪問的麻煩,跨域訪問就如字面意思,只要協議、域名、埠有任何一個不同,都被當作是不同的域。對於跨域訪問,是有限制的;陌生的是,很多跨域無法訪問的問題只能一味網上找解決方案,而不知道跨域乃至CORS的原理,配置出了問題不知道如何下手, 所以這次記錄一下徹底解決下跨域的問題。
為什麼要有跨域限制
拒絕其它域名的訪問想想理由也很簡單,如果沒有跨域訪問限制,別人拿你的cookie偽造你的身份,任何人都能偽造請求,安全性會出現問題。
CORS跨域資源共享
如果要放開限制,可以採用CORS跨域資源共享,CORS 基本思想就是使用自定義的HTTP頭部讓瀏覽器與伺服器進行溝通,從而決定請求或響應是應該成功還是失敗。JSONP方案我沒用過,聽說有限制,不如CORS。
CORS 在後端的設定主要在於檢查 請求頭,檢查的內容包括但不限於
- Origin:請求源:傳送請求的伺服器域名
- Methods:請求方式:如POST,GET
- Headers:可以攜帶的請求頭資訊:包括 “Content-Type”,“Origin“,”Accept“ 等
- Credentials:可否攜帶cookies
具體的檢查程式碼:
@Component
public class CorsFilter implements Filter {
final static org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(CorsFilter.class);
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
HttpServletRequest reqs = (HttpServletRequest) req;
//檢查Origin是否符合,因為這裡直接取的 請求頭的Origin地址,所以相當於不限制請求源是哪裡
response.setHeader("Access-Control-Allow-Origin", reqs.getHeader("Origin"));
//這裡設定伺服器允許帶cookie
response.setHeader("Access-Control-Allow-Credentials", "true");
//這裡設定伺服器允許POST, GET, OPTIONS, DELETE四種請求Method來請求
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
//這裡設定伺服器允許額外攜帶的請求頭Origin, header1, header2, header3, X-Requested-With, Content-Type, Accept等欄位
response.setHeader("Access-Control-Allow-Headers", "Origin, header1, header2, header3, X-Requested-With, Content-Type, Accept");
chain.doFilter(req, res);
}
@Override
public void init(FilterConfig filterConfig) {
}
@Override
public void destroy() {
}
}
CORS跨域訪問分兩種情況:
第一種情況
:如果Content-Type是application/x-www-form-urlencoded, multipart/form-data, 或者 text/plain,或者請求方法不是PUT或DELETE,則是普通情況處理
下圖是普通情況跨域的請求和返回資訊:
傳送的Request Header除了常見的,額外的就是”Origin“欄位;後臺也會返回Access-Control-Allow-Origin的設定值,瀏覽器發現Origin欄位不在Access-Control-Allow-Origin的設定值範圍內,那就會報錯,跨域訪問就會失敗。
第二種情況
前面說到,如果Content-Type是application/x-www-form-urlencoded, multipart/form-data, 或者 text/plain,或者請求方法不是PUT或DELETE,則是普通情況處理;
那相反的,如果Content-Type不是application/x-www-form-urlencoded, multipart/form-data, 或者 text/plain,比如application/json,或者請求方法是PUT或DELETE,就會在正式訪問前增加一次HTTP OPTIONS查詢請求,大白話就是瀏覽器會先詢問伺服器,當前網頁所在的域名是否在伺服器的許可名單之中,以及可以使用哪些HTTP Method和頭資訊欄位(Headers)。只有得到肯定答覆,瀏覽器才會發出正式的請求,否則就報錯。
jquery的ajax文件中也有類似描述
那我們再看下第二種情況下 跨域的請求和返回資訊:
截圖的是OPTIONS請求,後面正式的請求跟普通情況下的無異,就不討論。OPTIONS請求你會發現多了兩個Access-Control-Request-Methods和Access-Control-Request-Headers。這兩個內容跟後臺設定的Access-Control-Allow-Methods和Access-Control-Allow-Headers匹配
圖中返回資訊的Allow就是伺服器返回的支援的方法。特別也要注意Access-Control-Allow-Headers的設定,在之前的一個專案中,我就是因為後臺在設定Access-Control-Allow-Headers的時候,沒有加上”Content-Type“,導致當前臺傳的Access-Control-Request-Headers帶有”Content-Type“的時候,遊覽器會因為匹配不上而報錯。
結語
以上就是跨域和CORS流程的梳理,知己知彼,百戰百勝,只有瞭解機制才能方便我們在跨域訪問時早點定位問題所在。在這裡再推薦一篇文章,對於跨域和CORS有更細緻的講解,特分享給大家:
http://www.ruanyifeng.com/blog/2016/04/cors.html