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