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

Java知音發表於2018-10-31

網上各種跨域教程,各種實踐,各種問答,除了簡單的 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% 的讀者來說,說到這裡就夠了,我就不往下說了,讀者如果有需要補充的,請在評論區留言。



來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31555151/viewspace-2218301/,如需轉載,請註明出處,否則將追究法律責任。

相關文章