Ajax跨域問題

  跨域問題簡單的說就是前臺請求一個後臺連接,發送請求的前臺與後臺的地址不在同一個域下,就會產生跨域問題。這裏所指的域包括協議、IP地址、端口等。javascript

1.跨域訪問安全問題

後端代碼:html

package cn.qs.controller;

import java.util.LinkedHashMap;
import java.util.Map;

import org.apache.commons.collections.MapUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/test")
@RestController
public class TestController {

    @GetMapping("/get")
    public Map<String, Object> get(@RequestParam Map<String, Object> condition) {
        if (MapUtils.isEmpty(condition)) {
            condition = new LinkedHashMap<>();
            condition.put("param", null);
        }

        return condition;
    }

}

 

前端代碼:前端

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <script type="text/javascript" src="js/jquery-1.8.3.js" ></script>
    <body>
    </body>
    <script>
        + function test() {
            $.getJSON("http://localhost:8088/test/get.html", {}, function(res) {
                console.log(res);
            });
        }();
        
    </script>
</html>

結果:雖而後端正常響應,可是JS報錯,這就是跨域安全問題,以下:java

 

 js報錯以下:node

  Failed to load http://localhost:8088/test/get.html: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://127.0.0.1:8020' is therefore not allowed access.jquery

 

發生ajax跨域問題的緣由:(三個緣由同時知足纔可能產生跨域問題)

(1)瀏覽器限制nginx

  發生ajax跨域的問題的時候後端是正常執行的,從後臺打印的日誌能夠看出,並且後臺也會正常返回數據。瀏覽器爲了安全進行了限制,說白了就是瀏覽器多管閒事。web

(2)跨域:ajax

  當協議、域名、端口不一致瀏覽器就會認爲是跨域問題。spring

(3)XHR(XMLHttpRequest)請求,也就是ajax請求

  若是不是ajax請求,不存在跨域問題(這個咱們應該能夠理解,瀏覽器直接訪問以及a標籤跳轉等方式都不會產生跨域問題)。

2.解決思路

 針對上面三個緣由能夠對跨域問題進行解決。思路以下:

(1)瀏覽器端:瀏覽器容許跨域請求,這個不太現實,咱們不可能改每一個客戶端

(2)XHR請求使用JSONP(JSON with Padding)方式進行方式。它容許在服務器端集成Script tags返回至客戶端,經過javascript callback的形式實現跨域訪問(這僅僅是JSONP簡單的實現形式)。

(3)針對跨域問題解決:

被調用方:也就是服務器端接口,服務器容許跨域。可是若是某些狀況服務器端不是咱們寫的就不可行了。

調用發:也就是JS客戶端,隱藏跨域。一般是經過代理的形式隱藏跨域請求,使請求都相似於同一域下發出a標籤。

 

3.瀏覽器禁止檢查-從瀏覽器層次解決

 好比chrom啓動的時候設置參數關閉安全檢查,以下:

chrome --disable-web-security --user-data-dir=g:/test

 

  設置以後能夠正常進行訪問,這也進一步證實了跨域問題與後臺無關。

4..採用JSONP解決,針對XHR緣由

  JSONP(JSON with Padding) 是一種變通的方式解決跨域問題。JSONP是一種非官方的協議,雙方進行約定一個請求的參數。該協議的一個要點就是容許用戶傳遞一個callback參數給服務端,而後服務端返回數據時會將這個callback參數做爲函數名來包裹住JSON數據,這樣客戶端就能夠隨意定製本身的函數來自動處理返回數據了。

  JSONP發出的請求類型是script,不是XHR請求,因此能夠繞過瀏覽器的檢查。JSONP返回的是application/javascript,普通的xhr請求返回的是application/json。

  JSONP的原理:經過向界面動態的添加script標籤來進行發送請求。script標籤會加上callback參數以及_,_是爲了防止請求被緩存。

    好比咱們發送一個請求地址是http://localhost:8088/test/get.html?name=zhangsan&callback=handleCallback&_=123。後端看到有約定的參數callback,就認爲是JSONP請求,若是XHR正常請求的響應是{success: true},那麼後端會將回傳的JSON數據做爲參數,callback的值做爲方法名,如: handleCallback({success: true}), 並將響應頭的Content-Type設爲application/javascript,瀏覽器看到是JS響應,則會執行對應的handleCallback(data)方法。

1.JSONP弊端

(1)服務器端代碼須要改動

(2)只支持get方法,因爲JSONP原理是經過script標籤實現的,因此只能發送get請求

(3)不是XHR異步請求。因此不能使用XHR的一些特性,好比異步等。  

 

2.測試JSONP  

後端:增長一個advice

package cn.qlq.aspect;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.AbstractJsonpResponseBodyAdvice;

@ControllerAdvice
public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice {

    public JsonpAdvice() {
        super("callback");
    }
}

 

前端:採用JSON包裝的JSONP請求

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <script type="text/javascript" src="js/jquery-1.8.3.js" ></script>
    <body>
    </body>
    <script>
        + function test() {
                $.ajax({  
                    type : "get",  
                    async:false,  
                    url : "http://localhost:8088/test/get.html?name=zhangsan",  
                    dataType : "jsonp",//數據類型爲jsonp  
                    jsonp: "callback",//服務端用於接收callback調用的function名的參數  
                    success : function(data){  
                        console.log(data);
                    },  
                    error:function(){  
                        alert('fail');  
                    }  
                }); 
    
        }();
    </script>
</html>

結果:

(1)請求是script

 

 請求頭:

 

(2)查看響應數據頭和數據:

 

 數據以下:

/**/jQuery18309128178844464243_1575299406254({"name":"zhangsan","callback":"jQuery18309128178844464243_1575299406254","_":"1575299406287"});

 

 補充:JSONP也能夠本身定義返回的方法名稱,默認是JSON生成的隨機字符串

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <script type="text/javascript" src="js/jquery-1.8.3.js" ></script>
    <body>
    </body>
    <script>
        var handleJSONPresponse = function (res) {
            console.log(1);
            console.log(res);
            console.log(2);
        }
        function test() {
                $.ajax({  
                    type : "get",  
                    async:false,  
                    url : "http://localhost:8088/test/get.html?name=zhangsan",  
                    dataType : "jsonp",//數據類型爲jsonp  
                    jsonp: "callback",//服務端用於接收callback調用的function名的參數  
                    jsonpCallback: "handleJSONPresponse", // callbacl的value值,不傳由jquery隨機生成
                    error:function(){  
                        alert('fail');  
                    }  
                }); 
        }
        
        test();
    </script>
</html>

 查看請求數據:參數加_是爲了防止瀏覽器緩存JS請求

 

 

查看響應數據:

 結果:

 

5.跨域解決-被調用方解決(服務端容許跨域)

  這裏所說的被調用方通常也就是指的是服務端。

1.常見J2EE應用架構

 

 

   客戶端發送請求到http服務器,一般是nginx/Apache;http服務器判斷是靜態請求仍是動態請求,靜態請求就直接響應,動態請求就轉發到應用服務器(Tomcat\weblogic\jetty等)。

  固然也有省去中間靜態服務器的應用,就變爲客戶端直接請求應用服務器。

2.被調用方解決

  被調用方經過請求頭告訴瀏覽器本應用容許跨域調用。能夠從tomcat應用服務器響應請求頭,也能夠從中間服務器向請求頭添加請求頭。

(1)瀏覽器先執行仍是先判斷請求是XHR請求?

   查看下面的簡單請求與非簡單請求的解釋。

(2)瀏覽器如何判斷?

 分析普通請求和跨域請求的區別:

普通請求的請求頭以下:

XHR的請求以下:

 

  能夠看出XHR請求的請求頭會多出一個Origin參數(也就是域),瀏覽器就是根據這個參數進行判斷的,瀏覽器會拿響應頭中容許的。若是不容許就產生跨域問題,會報錯。

 

補充:關於XHR請求頭中攜帶X-Requested-With與Origin

  我本身測試,若是用jquery的ajax訪問本身站內請求是會攜帶X-Requested-With參數、不帶Origin參數,若是訪問跨域請求不會攜帶X-Requested-With參數,會攜帶Origin參數。

                    if ( !options.crossDomain && !headers["X-Requested-With"] ) {
                        headers["X-Requested-With"] = "XMLHttpRequest";
                    }

 

1.被調用方過濾器中實現支持跨域

package cn.qs.filter;

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.annotation.WebFilter;
import javax.servlet.http.HttpServletResponse;

/**
 * 容許跨域請求
 */
@WebFilter(filterName = "corsFilter", urlPatterns = "/*")
public class CorsFilter implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        HttpServletResponse response2 = (HttpServletResponse) response;
        response2.setHeader("Access-Control-Allow-Origin", "http://127.0.0.1:8020");
        response2.setHeader("Access-Control-Allow-Methods", "GET");

        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {

    }

    @Override
    public void init(FilterConfig arg0) throws ServletException {

    }

}

 

  上面Access-Control-Allow-Origin是容許跨域請求的域, Access-Control-Allow-Methods 是容許的方法。

咱們再次查看XHR請求頭和響應頭:

 

若是容許全部的域和方法能夠用:

        response2.setHeader("Access-Control-Allow-Origin", "*");
        response2.setHeader("Access-Control-Allow-Methods", "*");

 

再次查看請求頭和響應頭:

 

  這種跨域是不支持攜帶cookie發送請求的。

2.簡單請求和非簡單請求

  簡單請求是先執行後判斷,非簡單請求是先發一個預檢命令,成功以後纔會發送請求。

(1)簡單請求:請求的方法爲GET\POST\HEAD方法中的一種;請求的header裏面無自定義頭,而且Content-Type爲:text/plain、multipart/form-data、application/x-www-form-urlencoded中的一種。

  只有同時知足以上兩個條件時,纔是簡單請求,不然爲非簡單請求

(2)非簡單請求:put、delete方法的ajax請求;發送json格式的ajax請求;帶自定義頭的ajax請求。最多見的是發送json格式的ajax請求。非簡單會發送兩次請求:一個options的預檢請求、預檢請求根據響應頭判斷正確以後發送數據請求。

發送一個非簡單請求:

後端:

package cn.qs.controller;

import java.util.LinkedHashMap;
import java.util.Map;

import org.apache.commons.collections.MapUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/test")
@RestController
public class TestController {

    @GetMapping("/get")
    public Map<String, Object> get(@RequestParam Map<String, Object> condition) {
        if (MapUtils.isEmpty(condition)) {
            condition = new LinkedHashMap<>();
            condition.put("param", null);
        }

        return condition;
    }

    @PostMapping("/getJSON")
    public String getJSON(@RequestBody String param) {
        System.out.println(param);
        return param;
    }

}

 

前端

        function test() {
            $.ajax({
                url: "http://localhost:8088/test/getJSON.html",
                type: "POST",
                data: JSON.stringify({name : "張三"}),
                contentType: "application/json;charset=utf-8",
                success: function(res) {
                    console.log(res);
                }
            });
        }
        
        test();

結果:(發送預檢請求的時候報錯)

 控制檯報錯: (發送預檢的響應頭未設置須要的響應頭)

Failed to load http://localhost:8088/test/getJSON.html: Request header field Content-Type is not allowed by Access-Control-Allow-Headers in preflight response.

 

修改filter

package cn.qs.filter;

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.annotation.WebFilter;
import javax.servlet.http.HttpServletResponse;

/**
 * 容許跨域請求
 */
@WebFilter(filterName = "corsFilter", urlPatterns = "/*")
public class CorsFilter implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        HttpServletResponse response2 = (HttpServletResponse) response;
        // 容許請求的域(協議://IP:port)
        response2.setHeader("Access-Control-Allow-Origin", "*");
        // 容許請求的方法
        response2.setHeader("Access-Control-Allow-Methods", "*");
        // 正確的響應預檢請求
        response2.setHeader("Access-Control-Allow-Headers", "Content-Type");

        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {

    }

    @Override
    public void init(FilterConfig arg0) throws ServletException {

    }

}

再次前端發送請求:(響應頭增長Access-Control-Allow-Headers預檢請求會正常響應,預檢成功以後會發送正常的數據請求,因此看到是發出兩個請求)

 

補充:預檢命令能夠緩存,過濾器向響應頭增長以下響應頭:(瀏覽器會緩存1個小時的預檢請求)

        // 緩存預檢命令的時長,單位是s
        response2.setHeader("Access-Control-Max-Age", "3600");

1小時內發送非簡單請求只會預檢請求1次。咱們能夠用chrom的disable cache 禁掉緩存測試:

3.帶cookie的跨域請求

  同域下發送ajax請求默認會攜帶cookie;不一樣域發送cookie須要進行設置,先後臺都須要設置。

(1)首先明白跨域請求須要後臺進行設置:請求頭的值  Access-Control-Allow-Origin 不能是*,必須是具體的域。須要根據請求頭的Origin獲取到請求的域以後寫到響應頭中。

(2)響應頭也須要增長容許攜帶cookie的字段 。

        // 容許cookie
        response2.setHeader("Access-Control-Allow-Credentials", "true");

(3)客戶端發送ajax請求的時候須要withCredentials: true 容許攜帶cookie。A發ajax請求給B, 帶着的是B的cookie, 仍是受限於同源策略, ajax的Request URL是B, cookie就是B的

 

先在C:\Windows\System32\drivers\etc\hosts下面增長虛擬域名:

127.0.0.1 a.com
127.0.0.1 b.com

  上面a.com 用於訪問靜態頁面,b.com 用於接收後端請求。

 

 後端過濾器修改

package cn.qs.filter;

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.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;

/**
 * 容許跨域請求
 */
@WebFilter(filterName = "corsFilter", urlPatterns = "/*")
public class CorsFilter implements Filter {
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        // 容許訪問的源
        String headerOrigin = request.getHeader("Origin");
        if (StringUtils.isNotBlank(headerOrigin)) {
            response.setHeader("Access-Control-Allow-Origin", headerOrigin);
        }
        // 容許訪問的方法
        response.setHeader("Access-Control-Allow-Methods", "*");

        // 正確的響應預檢請求
        response.setHeader("Access-Control-Allow-Headers", "Content-Type");
        // 容許預檢命令緩存的時間
        response.setHeader("Access-Control-Max-Age", "3600");

        // 容許cookie
        response.setHeader("Access-Control-Allow-Credentials", "true");

        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {

    }

    @Override
    public void init(FilterConfig arg0) throws ServletException {

    }

}

 

後端Controller:

    @GetMapping("/getCookie")
    public String getCookie(@CookieValue(value = "cookie1", required = false) String cookie,
            HttpServletRequest request) {

        System.out.println("cookie: " + cookie);
        System.out.println("Origin: " + request.getHeader("Origin"));

        return cookie;
    }

    @GetMapping("/setCookie")
    public String setCookie(HttpServletRequest request, HttpServletResponse response) {
        Cookie cookie2 = new Cookie("cookie1", "value1");
        cookie2.setPath("/");
        response.addCookie(cookie2);

        String cookie = "cookie1=value1";
        return cookie;
    }

前端JS:

    function test() {
        $.ajax({  
            type : "get",  
            async: false,  
            url : "http://b.com:8088/test/getCookie.html", 
              xhrFields: {
                withCredentials: true
            },
            success: function(res) {
                console.log("res: " + res);
            },
            error:function(){  
                alert('fail');  
            }  
        }); 
    }
    
    test();

 

測試:

(1)若是直接執行前端不會傳cookie,由於沒有cookie。以下:(因爲咱們訪問的服務是b.com域名,咱們的cookie須要是b.com域名下的cookie)

首先咱們訪問後臺    http://b.com:8088/test/setCookie.html     獲取cookie,固然能夠經過document.cookie進行設置

 (2)接下來再訪問後臺:

請求頭以下:

 響應頭以下:

 

 (3)後臺控制檯日誌

cookie: value1
Origin: http://a.com:8020

 

4.帶自定義頭的跨域請求 

  過濾器修改,根據自定義請求頭在響應頭中增長容許的請求頭:

package cn.qs.filter;

import java.io.IOException;
import java.util.Enumeration;

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.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;

/**
 * 容許跨域請求
 */
@WebFilter(filterName = "corsFilter", urlPatterns = "/*")
public class CorsFilter implements Filter {
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        // 容許訪問的源
        String headerOrigin = request.getHeader("Origin");
        if (StringUtils.isNotBlank(headerOrigin)) {
            response.setHeader("Access-Control-Allow-Origin", headerOrigin);
        }

        // 容許訪問的方法
        response.setHeader("Access-Control-Allow-Methods", "*");

        // 正確的響應預檢請求
        // response.setHeader("Access-Control-Allow-Headers", "Content-Type");

        // 容許自定義的請求頭(根據自定義請求頭)
        String headers = request.getHeader("Access-Control-Request-Headers");
        if (StringUtils.isNotBlank(headers)) {
            response.addHeader("Access-Control-Allow-Headers", headers);
        }

        // 容許預檢命令緩存的時間
        response.setHeader("Access-Control-Max-Age", "3600");

        // 容許cookie
        response.setHeader("Access-Control-Allow-Credentials", "true");

        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {

    }

    @Override
    public void init(FilterConfig arg0) throws ServletException {

    }

}

 

Controller:

    @GetMapping("/getHeader")
    public JSONResultUtil<String> getHeader(@RequestHeader("x-header1") String header1,
            @RequestHeader("x-header2") String header2) {

        System.out.println(header1 + " " + header2);
        return new JSONResultUtil(true, header1 + " " + header2);
    }

前端:

    <script>
        function test() {
            $.ajax({
                url: "http://localhost:8088/test/getHeader.html",
                type: "get",
                headers: {
                    "x-header1": "header1"
                },
                beforeSend: function(xhr) {
                     xhr.setRequestHeader("x-header2","header2");
                },
                xhrFields: {
                    withCredentials: true
                },
                success: function(res) {
                    console.log(res);
                }
            });
        }
        
        test();
    </script>

結果:

  咱們禁調緩存會發送兩條請求:

(1)預檢請求

(2)第二條請求

 

5. 被調用方解決-nginx解決方案(替代上面的filter的做用)

  這裏用被調用方nginx解決是經過nginx代理以後增長所需的響應頭。

  咱們仍是基於上面的配置的本地域名。 下面 a.com 用於訪問靜態頁面, b.com 用於接收後端請求。

127.0.0.1 a.com
127.0.0.1 b.com

(1)打開nginx/conf/nginx.conf,在最後的 } 前面增長以下:

    include vhost/*.conf;

  表示引入 當前目錄/vhost/  下面全部後綴爲conf的文件。

 

接下來在當前conf目錄建立vhost目錄,並在下面建立b.com.conf文件,內容以下:

server {
    listen 80;
    server_name b.com;
    
    location /{
        proxy_pass http://localhost:8088/;
        

        add_header Access-Control-Allow-Methods true;
        add_header Access-Control-Allow-Credentials true;
        add_header Access-Control-Max-Age 3600;
        
        
        add_header Access-Control-Allow-Origin $http_origin;
        add_header Access-Control-Allow-Headers
        $http_access_control_request_headers;

        if ($request_method = OPTIONS) {
            return 200;
        }
    }
}

注意

(0)前面的是設置監聽域名是b.com、80端口,轉發到  http://localhost:8088/

(1)nginx中請求頭都是小寫,-要用_代替。

(2)$http_origin能夠取到請求頭的origin。

(3)最後判斷若是是預檢請求,會直接返回200狀態嗎。

 

關於nginx的使用:

nginx檢查語法:

E:\nginx\nginx-1.12.2>nginx.exe -t
nginx: the configuration file E:\nginx\nginx-1.12.2/conf/nginx.conf syntax is ok
nginx: configuration file E:\nginx\nginx-1.12.2/conf/nginx.conf test is successful

 

nginx從新加載配置文件:

nginx.exe -s reload

 

重啓和中止

nginx.exe -s reopen
nginx.exe -s stop

 

註釋掉filter以後修改前臺:異步訪問 b.com, 會被請求轉發到: http://localhost:8088/

        function test() {
            $.ajax({
                url: "http://b.com/test/getCookie.html",
                type: "get",
                headers: {
                    "x-header1": "header1",
                    "x-header3": "header3"
                },
                beforeSend: function(xhr) {
                     xhr.setRequestHeader("x-header2","header2");
                },
                xhrFields: {
                    withCredentials: true
                },
                success: function(res) {
                    console.log(res);
                }
            });
        }
        
        test();

 

(1)預檢命令

 (2)第二次正式請求

 

6. Spring註解跨域:@CrossOrigin

  加在類上表示全部方法容許跨域,加在方法表示方法中容許跨域。

package cn.qs.controller;

import java.util.LinkedHashMap;
import java.util.Map;

import org.apache.commons.collections.MapUtils;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import cn.qs.utils.JSONResultUtil;

@RequestMapping("/test")
@RestController
@CrossOrigin
public class TestController {

    @GetMapping("/get")
    public Map<String, Object> get(@RequestParam Map<String, Object> condition) {
        if (MapUtils.isEmpty(condition)) {
            condition = new LinkedHashMap<>();
            condition.put("param", null);
        }

        return condition;
    }

    @GetMapping("/getCookie")
    public String getCookie(@CookieValue(value = "cookie1") String cookie) {

        return cookie;
    }

    @PostMapping("/getJSON")
    public String getJSON(@RequestBody String param) {
        System.out.println(param);
        return param;
    }

    @GetMapping("/getHeader")
    public JSONResultUtil<String> getHeader(@RequestHeader("x-header1") String header1,
            @RequestHeader("x-header2") String header2) {

        System.out.println(header1 + " " + header2);
        return new JSONResultUtil(true, header1 + " " + header2);
    }

}

 

6.調用方解決-隱藏跨域(重要)

  被調用方解決跨域是經過nginx代理,將被調用方的請求代理出去,隱藏掉跨域請求。

(1)在nginx/conf/vhost下面新建a.com.conf,內容以下:

server {
    listen 80;
    server_name a.com;
    
    location /{
        proxy_pass http://localhost:8020/;
    }
    
    location /server{
        proxy_pass http://b.com:8088/;
    }
}

解釋: 監聽 a.com 的80端口。  默認是/會轉發到本地的8020端口,也就是前臺頁面所用的端口;若是是/server/ 開始的會轉發到後端服務所用的路徑。

(2)Controller修改

    @GetMapping("/getCookie")
    public String getCookie(@CookieValue(value = "cookie1", required = false) String cookie,
            HttpServletRequest request) {

        System.out.println("cookie1: " + cookie);
        System.out.println("=====================");

        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String header = (String) headerNames.nextElement();
            String value = request.getHeader(header);
            System.out.println(header + "\t" + value);
        }

        return cookie;
    }

(3)前端修改:(統一訪問 /server 由nginx轉發到後端服務)

        function test() {
            $.ajax({
                url: "/server/test/getCookie.html",
                type: "get",
                headers: {
                    "x-header1": "header1",
                    "x-header3": "header3"
                },
                beforeSend: function(xhr) {
                     xhr.setRequestHeader("x-header2","header2");
                },
                xhrFields: {
                    withCredentials: true
                },
                success: function(res) {
                    console.log(res);
                }
            });
        }
        
        test();

(4)首先設置cookie:(cookie是設置爲a.com的cookie,nginx訪問轉發請求的時候也會攜帶到b.com)

 查看cookie:

(5)刷新頁面測試:

前端查看:能夠看到前端請求發送至 a.com

請求頭:

 響應頭:

 

後端控制檯:(能夠看到攜帶了x-requested-with參數,仍然是ajax請求,可是至關於同域請求。主機也是b.com(由nginx轉發過來的請求))

cookie1: a.com.cookie
=====================
host b.com:8088
connection close
pragma no-cache
cache-control no-cache
accept */*
x-header3 header3
x-requested-with XMLHttpRequest
x-header2 header2
user-agent Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36
x-header1 header1
referer http://a.com/%E6%99%AE%E9%80%9A%E7%9A%84%E6%B5%8B%E8%AF%95/index.html?__hbt=1575599926569
accept-encoding gzip, deflate
accept-language zh-CN,zh;q=0.9
cookie cookie1=a.com.cookie

 

補充:調用方採用nodejs的express模塊和http-proxy-middleware進行代理

(1)安裝express模塊和http-proxy-middleware模塊:須要以管理員身份運行cmd

cnpm install --save-dev http-proxy-middleware
cnpm install --save-dev express

(2)編寫nodejs代理腳本:

const express = require('express');
const proxy = require('http-proxy-middleware');
 
const app = express();
 
app.use(
    '/server',
    proxy({
        target: 'http://b.com:8088',
        changeOrigin: true,
        pathRewrite: {'/server' : ''}
}));

app.use(
    '/',
    proxy({
        target: 'http://a.com:8020'
}));

app.listen(80);

注意:上面的順序須要先代理/server,再代理/。不然會先匹配/。

(3)測試方法同上面nginx代理測試。

 

總結:

0.所謂的跨域請求是指XHR請求發送的時候 協議、域名、端口不徹底一致的狀況。只要有一個不一樣就是跨域。

1.若是用jquery的ajax訪問本身站內請求是會攜帶X-Requested-With參數、不帶Origin參數;若是訪問跨域請求不會攜帶X-Requested-With參數,會攜帶Origin參數。

2.後端獲取請求頭的時候不區分大小寫,好比說前端發送的請求頭是  x-header1:header1。後端能夠用  request.getHeader("X-HEADER1");  接收。

相關文章
相關標籤/搜索