搞定所有的跨域請求問題: jsonp & CORS

th (4).jpg

網上各種跨域教程,各種實踐,各種問答,除了簡單的 jsonp 以外,很多說 CORS 的都是行不通的,老是缺那麼一兩個關鍵的配置。本文只想解決問題,所有的代碼經過親自實踐。

本文解決跨域中的 get、post、data、cookie 等這些問題。

本文只會說 get 請求和 post 請求,讀者請把 post 請求理解成除 get 請求外的所有其他請求方式。

JSONP

jsonp 的原理很簡單,利用了【前端請求靜態資源的時候不存在跨域問題】這個思路。

但是 只支持 get,只支持 get,只支持 get

注意一點,既然這個方法叫 jsonp,後端數據一定要使用 json 數據,不能隨便的搞個字符串什麼的,不然你會覺得結果莫名其妙的。

前端 jQuery 寫法

$.ajax({
  type: "get",
  url: baseUrl + "/jsonp/get",
  dataType: "jsonp",
  success: function(response) {
    $("#response").val(JSON.stringify(response));
  }
});

dataType: "jsonp"。除了這個,其他配置和普通的請求是一樣的。

後端 SpringMVC 配置

如果你也使用 SpringMVC,那麼配置一個 jsonp 的 Advice 就可以了,這樣我們寫的每一個 Controller 方法就完全不需要考慮客戶端到底是不是 jsonp 請求了,Spring 會自動做相應的處理。

@ControllerAdvice
public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice {
    public JsonpAdvice(){
        // 這樣如果請求中帶 callback 參數,Spring 就知道這個是 jsonp 的請求了
        super("callback");
    }
}

以上寫法要求 SpringMVC 版本不低於 3.2,低於 3.2 的我只能說,你們該升級了。

後端非 SpringMVC 配置

以前剛工作的時候,Struts2 還紅遍天,幾年的光景,SpringMVC 就基本統治下來了國內市場。

偷懶一下,這裏貼個僞代碼吧,在我們的方法返回前端之前調一下 wrap 方法:

public Object wrap(HttpServletRequest request){
    String callback = request.getParameter("callback");
    if(StringUtils.isBlank(callback)){
        return result;
    } else {
        return callback+"("+JSON.toJSONString(result)+")";
    }
}

CORS

Cross-Origin Resource Sharing

畢竟 jsonp 只支持 get 請求,肯定不能滿足我們的所有的請求需要,所以才需要搬出 CORS。

國內的 web 開發者還是比較苦逼的,用戶死不升級瀏覽器,老闆還死要開發者做兼容。

CORS 支持以下瀏覽器,目前來看,瀏覽器的問題已經越來越不重要了,連淘寶都不支持 IE7 了~~~

  • Chrome 3+

  • Firefox 3.5+

  • Opera 12+

  • Safari 4+

  • Internet Explorer 8+

前端 jQuery 寫法

直接看代碼吧:

$.ajax({
    type: "POST",
    url: baseUrl + "/jsonp/post",
    dataType: 'json',
    crossDomain: true,
    xhrFields: {
        withCredentials: true
    },
    data: {
        name: "name_from_frontend"
    },
    success: function (response) {
        console.log(response)// 返回的 json 數據
        $("#response").val(JSON.stringify(response));
    }
});

dataType: "json",這裏是 json,不是 jsonp,不是 jsonp,不是 jsonp。

crossDomain: true,這裏代表使用跨域請求

xhrFields: {withCredentials: true},這樣配置就可以把 cookie 帶過去了,不然我們連 session 都沒法維護,很多人都栽在這裏。當然,如果你沒有這個需求,也就不需要配置這個了。

後端 SpringMVC 配置

對於大部分的 web 項目,一般都會有 mvc 相關的配置類,此類繼承自 WebMvcConfigurerAdapter。如果你也使用 SpringMVC 4.2 以上的版本的話,直接像下面這樣添加這個方法就可以了:

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**/*").allowedOrigins("*");
    }
}

如果很不幸你的項目中 SpringMVC 版本低於 4.2,那麼需要「曲線救國」一下:

public class CrossDomainFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        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);
    }
}

在 web.xml 中配置下 filter:

<filter>
    <filter-name>CrossDomainFilter</filter-name>
    <filter-class>com.javadoop.filters.CrossDomainFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>CrossDomainFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

有很多項目用 shiro 的,也可以通過配置 shiro 過濾器的方式,這裏就不介紹了。

注意了,我說的是很籠統的配置,對於大部分項目是可以這麼籠統地配置的。文中類似 “*” 這種配置讀者應該都能知道怎麼配。

如果讀者發現瀏覽器提示不能用 ‘*’ 符號,那讀者可以在上面的 filter 中根據 request 對象拿到請求頭中的 referer(request.getHeader("referer")),然後動態地設置 "Access-Control-Allow-Origin":

String referer = request.getHeader("referer");
if (StringUtils.isNotBlank(referer)) {
    URL url = new URL(referer);
    String origin = url.getProtocol() + "://" + url.getHost();
    response.addHeader("Access-Control-Allow-Origin", origin);
} else {
    response.addHeader("Access-Control-Allow-Origin", "*");
}

2018-04-28:今天終於知道爲什麼有時候會提示我們 * 不支持了,原來是隻要前端寫了 withCredentials: true 那麼瀏覽器就會提示這個,一種辦法就是這裏說的使用動態構造 origin 的方式,另一種辦法就是跨域不傳 cookie,讓前端把 cookie 要傳的信息(如 sessionId/accessKey) 放到 header 中或者直接寫在 request 的參數裏。

前端非 jQuery 寫法

jQuery 一招鮮吃遍天的日子是徹底不在了,這裏就說說如果不使用 jQuery 的話,怎麼解決 post 跨域的問題。大部分的 js 庫都會提供相應的方案的,大家直接找相應的文檔看看就知道怎麼用了。

來一段原生 js 介紹下:

function createCORSRequest(method, url) {
    var xhr = new XMLHttpRequest();
    if ("withCredentials" in xhr) {
        // 如果有 withCredentials 這個屬性,那麼可以肯定是 XMLHTTPRequest2 對象。看第三個參數
        xhr.open(method, url, true);
    } else if (typeof XDomainRequest != "undefined") {
        // 此對象是 IE 用來跨域請求的
        xhr = new XDomainRequest();
        xhr.open(method, url);
    } else {
        // 如果是這樣,很不幸,瀏覽器不支持 CORS
        xhr = null;
    }
    return xhr;
}

var xhr = createCORSRequest('GET', url);
if (!xhr) {
    throw new Error('CORS not supported');
}

其中,Chrome,Firefox,Opera,Safari 這些「程序員友好」的瀏覽器使用的是 XMLHTTPRequest2 對象。IE 使用的是 XDomainRequest。

我想,對於 95% 的讀者來說,說到這裏就夠了,我就不往下說了,讀者如果有需要補充的,請在評論區留言。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章