1. 程式人生 > >[Web] Access-Control-Allow-Origin 與 WithCredentials

[Web] Access-Control-Allow-Origin 與 WithCredentials

Access-Control-Allow-Origin 與 WithCredentials

遇到的問題

在我們一個商城專案當中,前後端分離,前端使用Ajax(XMLHttpRequest),後臺是Spring專案,需要實現跨域訪問。

Access-Control-Allow-Origin

我之前從網上找到實現跨域的方法,在後臺添加了如下Filter:

public class CrossDomainFilter extends OncePerRequestFilter {
	  @Override
	  protected void doFilterInternal
(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String referer = request.getHeader("referer"); if (StringUtils.isNotBlank(referer)) { URL url = new URL(referer); String origin = url.getProtocol() + "://" + url.
getHost(); if(url.getPort()!=-1){ origin+=":"+url.getPort(); } response.addHeader("Access-Control-Allow-Origin", origin); } else { response.addHeader("Access-Control-Allow-Origin", "*"); } response.addHeader("Access-Control-Allow-Credentials", "true"); response.addHeader
("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE"); response.addHeader("Access-Control-Allow-Headers", "Content-Type"); filterChain.doFilter(request, response); } }

這樣一般的跨域都沒有問題了,但是當前端發起POST請求並向後臺傳Json格式資料時,瀏覽器控制檯卻報如下錯誤:

The value of the ‘Access-Control-Allow-Origin’ header in the response must not be the wildcard ‘*’ when the request’s credentials mode is ‘include’. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

而且我們注意到前端的這個請求如果不報錯會發送兩次,現在是第一次請求報瞭如上錯誤,第二次請求就沒有傳送了。

Preflight Request

經過查詢,我們瞭解到瀏覽器傳送的第一次請求是preflight request(預檢驗請求),用來獲知伺服器是否允許該跨域請求,如果允許,第二次才傳送帶資料的真實請求。

withCredentials

而通過查詢上述的錯誤,我們也瞭解到前端需要和後臺同步cookie,需要設定XMLHttpRequestwithCredentials屬性為true,同時要求後臺設定響應頭Access-Control-Allow-Credentialstrue,並且響應頭Access-Control-Allow-Origin不能為*,必須指定域名。

解決

顯然,我的問題就在於響應頭Access-Control-Allow-Origin*。通過檢查程式碼和前端請求,我發現preflight request的請求頭中沒有Referer,所以我程式碼沒有走到設定域名的分支,於是我改用了從請求頭的Origin( 標識跨域資源請求)中獲取請求源。修改後的程式碼如下:

public class CrossDomainFilter extends OncePerRequestFilter {
	  @Override
	  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) 
			  throws ServletException, IOException {
		String referer = request.getHeader("origin");
		if (StringUtils.isNotBlank(referer)) {
		  URL url = new URL(referer);
		  String origin = url.getProtocol() + "://" + url.getHost();
		  if(url.getPort()!=-1){
			  origin+=":"+url.getPort();
		  }
		  response.addHeader("Access-Control-Allow-Origin", origin);
		  response.addHeader("Access-Control-Allow-Credentials", "true");
		} else {
		  response.addHeader("Access-Control-Allow-Origin", "*");
		}
	    response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
	    response.addHeader("Access-Control-Allow-Headers", "Content-Type");
	    filterChain.doFilter(request, response);
	  }
}

參考資料

[1] Access-Control-Allow-Origin https://blog.csdn.net/blogdevteam/article/details/84874036
[2] preflight request https://www.jianshu.com/p/b55086cbd9af
[3] withCredentials https://www.jianshu.com/p/af1fc0fab4c5
[4] 前端必備HTTP技能之HTTP請求頭響應頭中常用欄位詳解 https://www.jianshu.com/p/6e86903d74f7
[5] 徹底搞清referrer和origin 徹底搞清referrer和origin https://blog.csdn.net/zdavb/article/details/51161130