[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,需要設定XMLHttpRequest
的withCredentials
屬性為true
,同時要求後臺設定響應頭Access-Control-Allow-Credentials
為true
,並且響應頭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