最近要重構一個基於spring boot的后端API服務,需要再本地測試。在本地測試時,運行在本地的前端頁面發送一個ajax請求訪問后端API,然后瀏覽器報錯blocked CORS policy。
Access to XMLHttpRequest at 'http://127.0.0.1:1234/api/' from origin 'http://dev.couchbase.cloud.qiyi.domain' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
最后的解決方法很簡單,只需要在相應的Controller上加一個注解@CrossOrigin就可以了,也可以將注解加到相應的方法上面。
為什么有些時候調用服務器的接口時沒有發生這個報錯?
原因:瀏覽器發送ajax請求必須是同源的,也就是瀏覽器訪問服務器接口需要考慮同源,而服務器與服務器之間訪問接口是不需要考慮同源的。
(一)同源策略(SOP策略)
同源策略(Same-Origin-Policy):URL由協議、域名、端口和路徑組成,如果兩個URL的協議、域名和端口相同,則表示他們同源。相反,只要協議,域名,端口有任何一個的不同,就被當作是跨域。
瀏覽器采用同源策略,禁止頁面加載或執行與自身來源不同的域的任何文檔或腳本,換句話說瀏覽器禁止的是來自不同源的"document"或腳本。在一個http請求中,http頭部Referer或Origin字段標識了當前域名,Host字段標識了此時請求的域名。因此我們在當前的js頁面,通過ajax請求第三方的數據,就會出現瀏覽器的跨域問題。
(二)跨域解決方法
1. 使用CORS策略
CORS策略:跨域資源共享(Corss Origin Resource Sharing)通過服務器增加一個特殊的Header[Access-Control-Allow-Origin]來告知客戶端跨域的限制,如果瀏覽器支持CORS的話,當判斷Origin通過的話,就會允許請求。使用這個Header返回被允許請求跨域請求的來源域,例如網站duelist.cn設置了下面的Header
Access-Control-Allow-Origin: http://smdcn.net
這樣設置之后,通過http://smdcn.net下的頁面對於duelist.cn進行ajax請求就會被允許,而其他網站對duelist.cn依舊會被阻攔,通過這種方式網站擁有者可以自己對此進行限制。當然,如果不想限制來源,可以通過來允許任何站點對該資源進行跨域請求
Access-Control-Allow-Origin: *
CORS規范中常見的頭信息:
常見的頭信息包括: Request Headers: Origin、Access-Control-Request-Method、Access-Control-Request-Headers Response Headers: 1. 允許向該服務器提交請求的URI Access-Control-Allow-Origin: <origin> | * 2. 瀏覽器允許訪問的服務器的頭信息的白名單 Access-Control-Expose-Headers: ..., ... 3. 請求有效期(單位:秒): Access-Control-Max-Age: <seconds> 4. 允許的請求方法: Access-Control-Allow-Methods 5. 實際的請求中,可以使用的自定義HTTP請求頭 Access-Control-Allow-Headers 6. 告知客戶端,當請求XHR的withCredientials屬性是true的時候,響應是否可以被得到。(從而使得下一次請求時,上一次的Cookies可以隨着請求發送) Access-Control-Allow-Credentials:
在傳統的Spring MVC中的使用方法:(網上較多資料都是這種方法)
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"); chain.doFilter(req, res); } public void init(FilterConfig filterConfig) {} public void destroy() {} }
<filter> <filter-name>cors</filter-name> <filter-class>com.app.filter.SimpleCORSFilter</filter-class> </filter> <filter-mapping> <filter-name>cors</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
在Spring boot中的使用方法就是添加注解@CrossOrigin
向@RequestMapping注解的Controller方法處添加一個@CrossOrigin注解:
@RestController @RequestMapping("/account") public class AccountController { @CrossOrigin @GetMapping("/{id}") public Account retrieve(@PathVariable Long id) { // ... } @DeleteMapping("/{id}") public void remove(@PathVariable Long id) { // ... } }
為整個controller啟用@CrossOrigin:
@CrossOrigin(origins = "http://domain2.com", maxAge = 3600) @RestController @RequestMapping("/account") public class AccountController { @GetMapping("/{id}") public Account retrieve(@PathVariable Long id) { // ... } @DeleteMapping("/{id}") public void remove(@PathVariable Long id) { // ... } }
其中@CrossOrigin中的2個參數:
origins : 允許可訪問的域列表
maxAge:准備響應前的緩存持續的最大時間(以秒為單位)。
2. 使用jsonp
jsonp解決跨域問題的原理是,瀏覽器的script標簽是不受同源策略限制的,我們可以在script標簽中訪問任何域名下的資源文件。利用這一特性,用script標簽從服務器中請求數據,同時服務器返回一個帶有方法和數據的js代碼,請求完成,調用本地的js方法,來完成數據的處理。
不推薦使用jsonp,首先jsonp是一種非官方的方法,而且這種方法只支持GET方法,不如POST方法安全;而且一般前后端分離最通用的方法是返回統一的json格式。
3. 服務器代理
這種方式運用的就是服務器的反向代理技術,控制客戶端和服務器的訪問都從代理服務器經過,比如用nginx作為服務器代理,在nginx上配置客戶端和第三方服務的反向代理,這樣就可保證客戶端、第三方是同源的了,同一個源,都來自代理服務器。