前後端分離,後端用springboot遇到的跨域問題

ben4發表於2018-03-06
對於前後端分離的專案來說,如果前端專案與後端專案部署在兩個不同的域下,那麼勢必會引起跨域問題的出現。

什麼是跨域?

跨域,指的是瀏覽器不能執行其他網站的指令碼。它是由瀏覽器的同源策略造成的,是瀏覽器施加的安全限制。簡單來說就是不同域名之間相互訪問

同源策略:請求的url地址,必須與瀏覽器上的url地址處於同域上,也就是域名,埠,協議相同.

例如:
http://www.a.com 要訪問 http://www.b.com 這樣域名不同,屬於跨域
http://www.a.com:8081/index.html呼叫http://www.a.com:8080的介面 這樣埠號不同 也屬於跨域
http://www.a.com:訪問https://www.a.com 這樣也是屬於跨域 協議不一樣

所以在以前前後端不分離的開發模式中,從來不用關注這個問題,因為所有的請求都是在同一個域內

綜合了網上解決跨域的方式目前有兩種

1.使用JSONP

不過我不太推薦使用,JSONP只支援GET請求,不管是對於前端還是後端來說,寫法與我們平常的ajax寫法不同,同樣後端也需要作出相應的更改。
JSONP的優勢在於支援老式瀏覽器,以及可以向不支援CORS的網站請求資料。

2.通過cors協議解決跨域問題

CORS支援所有型別的HTTP請求。
這個也是我非常推薦大家使用的方法
下面我來說下專案中遇到的問題
前端:vue.js
後端:springboot

前端請求介面時,報錯資訊如下
Origin `http://localhost:8080` is therefore not allowed access.
 The credentials mode of an XMLHttpRequest is controlled by the withCredentials attribute

解決方法:
前端請求開啟 withCredentials: true
後臺介面也需要對應的將 Access-Control-Allow-Credentials 設為 true

前端攜帶token傳後端時報錯
Request header field token is not allowed by 
Access-Control-Allow-Headers in preflight response.

這是由於請求頭內並沒有這個名叫token的請求頭欄位
預設情況下,只有六種 simple response headers (簡單響應首部)可以暴露給外部:Cache-Control Content-Language Content-Type Expires Last-Modified Pragma
如果想要讓客戶端可以訪問到其他的首部資訊,可以將它們在Access-Control-Expose-Headers 裡面列出來。

所以解決方法是:

Access-Control-Allow-Headers新增自定義的請求頭欄位Token
如果還有其他請求頭欄位需要使用,也是需要在Access-Control-Allow-Headers新增,不然伺服器是不允許的

後面還有一個問題:
Response to preflight request doesn`t pass access control check: 
No `Access-Control-Allow-Origin` header is present on the requested resource. 
Origin `http://localhost:8080` is therefore not allowed access. 
The response had HTTP status code 503.

這個是請求頭返回服務不可用

解決方式:

我看了下程式碼原來是我寫了兩個跨域過濾一個是用寫過濾器Filter,一個是繼承webmvcconfigureradapter然後我註釋了其中一個就可以了,我想應該是兩個跨域處理器衝突了

擴充

現在開發客戶端都是發非簡單請求
非簡單請求是那種對伺服器有特殊要求的請求,比如請求方法是PUTDELETE,或者Content-Type欄位的型別是application/json。也就是通過json資料互動
非簡單請求的CORS請求,會在正式通訊之前,增加一次HTTP查詢請求,稱為”預檢”請求(preflight)。

流程

瀏覽器先詢問伺服器,當前網頁所在的域名是否在伺服器的許可名單之中,以及可以使用哪些HTTP動詞和頭資訊欄位。只有得到肯定答覆,瀏覽器才會發出正式的XMLHttpRequest請求,否則就報錯。

“預檢”請求用的請求方法是OPTIONS,表示這個請求是用來詢問的。頭資訊裡面,關鍵欄位是Origin,表示請求來自哪個源。
一般不特殊指定源的話都將Access-Control-Allow-Origin設定為*
除了Origin欄位,”預檢”請求的頭資訊包括兩個特殊欄位。
(1)Access-Control-Request-Method
該欄位是必須的,用來列出瀏覽器的CORS請求會用到哪些HTTP方法
所使用到的方法也是要在跨域處理器中說明
也就是在Access-Control-Allow-Methods中加入各種請求方法
例如:POST PUT, GET, OPTIONS, DELETE
(2)Access-Control-Request-Headers
該欄位是一個逗號分隔的字串,指定瀏覽器CORS請求會額外傳送的頭資訊欄位
這個上面有說到,專案需要什麼額外的請求頭欄位就新增上去
伺服器收到”預檢”請求以後,檢查了OriginAccess-Control-Request-MethodAccess-Control-Request-Headers欄位以後,確認允許跨源請求,就可以做出回應。
說了這麼
我分享下我的跨域處理的程式碼(我這裡是使用過濾器的寫法,也可以使用繼承webmvcconfigureradapter的方法)

@Component
public class CorsFilter implements Filter {

    /*跨域請求配置*/
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) res;

        HttpServletRequest reqs = (HttpServletRequest) req;
        response.setHeader("Access-Control-Allow-Origin","*");
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "5000");
        response.setHeader("Access-Control-Allow-Headers", "Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type, X-E4M-With,Authorization,Token");
        chain.doFilter(req, res);
    }
    @Override
    public void init(FilterConfig filterConfig) {}
    @Override
    public void destroy() {}
}

繼承webmvcconfigureradapter的方法

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**").allowedOrigins("*").allowedMethods("GET", "POST", "OPTIONS", "PUT")
                .allowedHeaders("Content-Type", "X-Requested-With", "accept", "Origin", "Access-Control-Request-Method",
                        "Access-Control-Request-Headers")
                .exposedHeaders("Access-Control-Allow-Origin", "Access-Control-Allow-Credentials")
                .allowCredentials(true).maxAge(3600);
    }
}

注:以上寫法二選一即可,兩個都寫估計還是會衝突,兩者作用一樣只是寫法不同


相關文章