Ajax跨域問題


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

1.跨域訪問安全問題

后端代碼:

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報錯,這就是跨域安全問題,如下:

 

 js報錯如下:

  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.

 

發生ajax跨域問題的原因:(三個原因同時滿足才可能產生跨域問題)

(1)瀏覽器限制

  發生ajax跨域的問題的時候后端是正常執行的,從后台打印的日志可以看出,而且后台也會正常返回數據。瀏覽器為了安全進行了限制,說白了就是瀏覽器多管閑事。

(2)跨域:

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

(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");  接收。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM