• 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服务器判断是静态请求还是动态请求,静态请求就直接响应,动态请求就转发到应用服务器(Tomcatweblogicjetty等)。

      当然也有省去中间静态服务器的应用,就变为客户端直接请求应用服务器。

    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)简单请求:请求的方法为GETPOSTHEAD方法中的一种;请求的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:WindowsSystem32driversetchosts下面增加虚拟域名:

    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:
    ginx
    ginx-1.12.2>nginx.exe -t
    nginx: the configuration file E:
    ginx
    ginx-1.12.2/conf/nginx.conf syntax is ok
    nginx: configuration file E:
    ginx
    ginx-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 + "	" + 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");  接收。

  • 相关阅读:
    xx
    RBD 测试用例
    postman Could not get any response 无法请求
    高德地图查询结果返回INVALID_USER_IP错误解决
    FeignClient接口格式
    ES Elasticsearch exception [type=search_phase_execution_exception, reason=all shards failed
    Java规范及异常汇总
    支付宝APP支付 订单已付款成功,请勿重复提交 和 微信H5支付 INVALID_REQUEST 201 商户订单号重复
    JWT相关漏洞介绍
    shiro 550漏洞分析
  • 原文地址:https://www.cnblogs.com/qlqwjy/p/11909165.html
Copyright © 2020-2023  润新知