目錄
一、產生跨域的原因
二、錯誤描述
三、HTTP訪問控制(CORS)
四、解決方案
1、@CrossOrigin 註解
2、設置頭部信息
3、配置攔截器
4、web.xml配置
5、HttpClient 轉發請求
6、spring-context.xml配置
五、帶token的跨域問題解決方案
一、產生跨域的原因
我們在項目開發中,經常會使用前後端分離的技術,把前端代碼和後端代碼分別部署到不同的服務器上,這樣在數據交互的時候,由於瀏覽器的同源策略,就會產生跨域問題。
同源策略,它是由Netscape提出的一個著名的安全策略。
現在所有支持JavaScript 的瀏覽器都會使用這個策略。
所謂同源是指,域名,協議,端口相同。
如果非同源,那麼在請求數據時,瀏覽器會在控制檯中報一個異常,提示拒絕訪問。
同源策略是瀏覽器的行爲,是爲了保護本地數據不被JavaScript代碼獲取回來的數據污染,因此攔截的是客戶端發出的請求回來的數據接收,即請求發送了,服務器響應了,但是無法被瀏覽器接收。
二、錯誤描述
Failed to load https://example.com/: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘https://anfo.pl’ is therefore not allowed access. If an opaque response serves your needs, set the request’smode to ‘no-cors’ to fetch the resource with CORS disabled.
三、HTTP訪問控制(CORS)
跨域資源共享(CORS) 是一種機制,它使用額外的 HTTP 頭來告訴瀏覽器 讓運行在一個指定域名上的Web應用被准許訪問來自不同源服務器上的指定的資源。
- 簡單請求:不會觸發 CORS 預檢請求(GET、HEAD、POST)
- 預檢請求:必須首先使用 OPTIONS 方法發起一個預檢請求到服務器,以獲知服務器是否允許該實際請求。預檢請求“的使用,可以避免跨域請求對服務器的用戶數據產生未預期的影響。(PUT、DELETE、CONNECT、OPTIONS、TRACE PATCH)
- HTTP 響應首部字段:Access-Control-Allow-Origin、Access-Control-Expose-Headers、Access-Control-Max-Age、Access-Control-Allow-Credentials、Access-Control-Allow-Methods、Access-Control-Allow-Headers(在下文使用時,再做詳細介紹)
- HTTP 請求首部字段:Origin、Access-Control-Request-Method、Access-Control-Request-Headers(在下文使用時,再做詳細介紹)
四、解決方案
1、@CrossOrigin 註解
- 適用於jdk 1.8及以上
- @CrossOrigin:允許所有ip跨域訪問
- @CrossOrigin(origins=“xxx.xxx.xxx.xxx”):只允許指定ip跨域
直接在Controller類或者方法上添加
@CrossOrigin
@Controller
public class BookController {
//do something...
}
2、設置頭部信息
首先要了解一下以下幾個名詞:
1、 CacheController(http協議的緩存控制),常見的值有:
- no-cache:告訴瀏覽器、緩存服務器,不管本地副本是否過期,使用資源副本前,一定要到源服務器進行副本有效性校驗。
- must-revalidate:告訴瀏覽器、緩存服務器,本地副本過期前,可以使用本地副本;本地副本一旦過期,必須去源服務器進行有效性校驗。
- no-store:用於防止重要的信息被無意的發佈,在請求消息中發送將使得請求和響應消息都不使用緩存。
- 注意:僅適用於http 1.1版本
2、Pragma:
- no-cache:指示請求或響應消息不能緩存。即,資源不能從cache中獲取,而必須回源獲取。
- 兼容http 1.0 和 http 1.1版本
3、Expires:瀏覽器會在指定過期時間內使用本地緩存,指明應該在什麼時候認爲文檔已經過期,從而不再緩存它,時間爲格林威治時間GMT。
4、Access-Control-Allow-Origin:指定允許訪問該資源的外域 URI,它的值可爲:
- 一個完整的域名(例如,http://www.baidu.com)
- " * " :允許任意域名
接下來是實現代碼:
1、定義Controller類的父類,對頭部信息進行設置
package test;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
public class SetHeaderController {
protected void writeJsonResponse(Object responseObj, HttpServletResponse response) {
response.setCharacterEncoding("UTF-8");
//禁用緩存,確保網頁信息是最新數據
// HTTP 1.1
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
// HTTP 1.0
//response.setHeader("Pragma", "no-cache");
response.setHeader("Access-Control-Allow-Origin", "*");
response.setDateHeader("Expires", 0);
PrintWriter writer = getWriter(response);
writeJsonResponse(responseObj, writer);
}
protected PrintWriter getWriter(HttpServletResponse response) {
if (null == response) {
return null;
}
PrintWriter writer = null;
try {
writer = response.getWriter();
} catch (Exception e) {
e.printStackTrace();
}
return writer;
}
protected void writeJsonResponse(Object responseObj, PrintWriter writer) {
if (writer == null || responseObj == null) {
return;
}
try {
writer.write(JSON.toJSONString(responseObj, SerializerFeature.DisableCircularReferenceDetect));
} finally {
writer.flush();
writer.close();
}
}
}
實現功能的Controller調用代碼:
Message類是自己寫的,用於封裝返回給前端的信息
package test;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import po.Message;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
@Controller
public class BookController extends SetHeaderController {
@RequestMapping("/addBook.do")
public void addBook(HttpSession httpSession, HttpServletRequest httpServletRequest, HttpServletResponse
httpServletResponse){
Message message = new Message();
//do something...
writeJsonResponse(message, httpServletResponse);
}
}
3、配置攔截器
首先介紹攔截器使用到的幾個名詞:
1、Access-Control-Allow-Methods:首部字段用於預檢請求的響應。其指明瞭實際請求所允許使用的 HTTP 方法。
2、Access-Control-Max-Age:指定了preflight請求的結果能夠被緩存多久。(以秒爲單位)
3、Access-Control-Allow-Headers: 用於預檢請求的響應。其指明瞭實際請求中允許攜帶的首部字段。
4、Access-Control-Allow-Credentials :指定了當瀏覽器的credentials設置爲true時是否允許瀏覽器讀取response的內容。
- 當用在對preflight預檢測請求的響應中時,它指定了實際的請求是否可以使用credentials。
- 請注意:簡單 GET 請求不會被預檢;如果對此類請求的響應中不包含該字段,這個響應將被忽略掉,並且瀏覽器也不會將相應內容返回給網頁。
Access-Control-Allow-Origin已經在前面解釋過,這裏就不再贅述。
攔截器代碼:
package test;
import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class CORSFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletResponse resp = (HttpServletResponse) response;
resp.setContentType("text/html;charset=UTF-8");
resp.setHeader("Pragma","No-cache");
resp.setHeader("Cache-Control","no-cache");
resp.setHeader("Access-Control-Allow-Origin", "*");
resp.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, HEAD, DELETE, PUT");
resp.setHeader("Access-Control-Max-Age", "3600");
resp.setHeader("Access-Control-Allow-Headers",
"X-Requested-With, Content-Type, Authorization, Accept, Origin, User-Agent, Content-Range, Content-Disposition, Content-Description");
resp.setDateHeader("Expires", -10);
chain.doFilter(request, resp);
}
@Override
public void init(FilterConfig filterConfig) {}
@Override
public void destroy() {}
}
在web.xml中聲明該過濾器:
<filter>
<filter-name>cors</filter-name>
<filter-class>test.CORSFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>cors</filter-name>
<!-- 開放的接口前綴 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
- 注意:放在其他過濾器的前面