• SpringBoot-12-之Ajax跨域访问全解析


    一.什么是跨域呢?

    1.引入:

    先讲个故事:从前一个叫8080的大佬和一个8081的大佬各占一方天地,还有一个叫浏览器的大佬和8080还有8081关系都不错。浏览器和8080做着一件事(8080端应用),浏览器和8081做着另一件事(8081端应用),但8080和8081却没有什么交集。有一天8081什么话也没说,就跑到8080的地盘拿东西(ajax返回的数据),浏览器手下的警卫员说:"这种珍贵的东西,无凭无据的,我们可不能给你"(跨域访问错误)。

    8081气愤离去,心想:"老子可是大佬,还要凭据,于是打电话给8080。"他们商量了一下,8080说:“警卫也是完成自己分内的事,那好吧,我把东西用保险箱(javascript)包裹起来(json转化为jsonp),警卫就不认得了,你回去用我们约定的密码(callback)打开就行了。”(使用jsonp实现跨域)。

    这种方法确实可行,一段时间后,两个大佬觉得挺麻烦的,8080说,给你个令牌(响应头上增加相应字段)算了,那着令牌警卫就不会拦你了。果然,简单了许多。

    又过了一段时间,8081想:"我是大佬哎,让我每天拿着令牌进进出出,这不损我形象吗?"打电话给8080,说:"既然咱们都是大佬,还让警卫操心干嘛,以后我直接去找你,咱俩喝喝茶,聊聊天不是更好。"经过一段时间的接触,8080和8081关系也不错了,8080爽快地答应了。(隐藏跨域,大佬背后交接)

    9414344-b36e6cffd59d2c05.png
    跨域错误.png
    2.为什么?
    [1] 浏览器出于安全的限制,而不是服务器
    [2] 跨域:协议/域名/端口必须一致
    [3] XHR请求(XMLHttpRequest)
    

    二.解决思路

    1: 浏览器放方:8080大佬让浏览器警卫队不要阻拦
    9414344-0659072736a3c120.png
    浏览器不校验跨域.png
    2: jsonp:需要后端修改数据格式,前端修改接受方式
    普通ajax请求的Type是:xhr           返回的是json字符串      
    jsonp的ajax请求的Type是:script     返回的是js脚本          url后有一段callback参数
    
    9414344-518b9a82bed2f7f1.png
    json和jsonp.png

    点击各种url查看:

    jsonp返回的:动态创建是一段js脚本,用完再删除
    /**/jQuery33103437422192155124_1532262222426({"data":"say Ok"});
    json返回的:只是json
    {"data":"say Ok"}
    

    jsonp实现步骤

    后端:AbstractJsonpResponseBodyAdvice方法过时,没查到新的方法,但也能用

    @ControllerAdvice
    public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice {
        public JsonpAdvice() {
            super("callback");
        }
    }
    

    8080端页面中:

    <script>
        var baseUrl = 'http://localhost:8080/ajax';//
        jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000;//每个测试用例耗时时间
            //单元测试用例
            it("jsonp", function (done) {
                var result;//保存返回结果
                $.ajax({
                    url: baseUrl,
                    dataType: "jsonp",//jsonp格式
                    // cache: true,//结果可被缓存
                    jsonp:'callback',//默认参数,与后端的callback字符串相对于
                    success: function (json) {
                        result = json;
                    }
                });
    
                $.getJSON(baseUrl, function (data) {
                    result = data;
                });
    
                setTimeout(function () {
                    expect(result).toEqual({
                        "data": "say Ok"
                    });
                    done()//完成校验
                }, 100);
            })
        });
    </script>
    
    9414344-3fe15d51b97727aa.png
    jsonp.png

    弊端

    [1]服务器需要改动,若后端非己主宰,则无能为力
    [2]只支持GET
    [3]发的不是XHR请求
    

    3.令牌模式:

    被调用方(服务端):响应头上增加相应字段告诉浏览器允许

    8081跨域的请求头有:Origin:http://localhost:8081
    

    服务端打造令牌:Filter
    com.toly1994.ajaxser.AjaxserApplication

    @SpringBootApplication
    public class AjaxserApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(AjaxserApplication.class, args);
        }
    
        @Bean
        public FilterRegistrationBean registerFilter() {
            FilterRegistrationBean bean = new FilterRegistrationBean();
            bean.addUrlPatterns("/*");//所有请求都经过这个Filter
            bean.setFilter(new CrosFilter());//设置过滤器
            return bean;
        }
    }
    

    com.toly1994.ajaxser.CrosFilter

    package com.toly1994.ajaxser;
    
    import javax.servlet.*;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    /**
     * 作者:张风捷特烈
     * 时间:2018/7/22:21:44
     * 邮箱:1981462002@qq.com
     * 说明:CrosFilter
     */
    public class CrosFilter implements javax.servlet.Filter {
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
    
        }
    
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            HttpServletResponse rep = (HttpServletResponse) servletResponse;
            //允许8081访问:"http://localhost:8081"换为*表示允许所有
            rep.addHeader("Access-Control-Allow-Origin", "http://localhost:8081");
            //允许访问方法GET:GET"换为*表示允许所有
            rep.addHeader("Access-Control-Allow-Methods", "GET");
            filterChain.doFilter(servletRequest, rep);
        }
    
        @Override
        public void destroy() {
    
        }
    }
    
    
    9414344-f988c64ab5b82671.png
    跨域方式.png

    4.非简单请求:post json

    4-1:简单请求:先执行,后判断

    方法:GET HEAD POST
    header:无自定义头 
            Content-Type:rext/plain||multipart/form-data||application/x-www-form-urlencoded
    

    4-2:非简单请求:后判断,先执行

    put delete 方法的ajax
    发送带有json格式的ajax请求
    带自定义头的ajax
    

    4-3:Post请求传Json

    8080服务端暴露接口:com.toly1994.ajaxser.controller.AjaxController
        @PostMapping(value = "/postJson")
        public ResultBean postJson(@RequestBody User user) {
            System.out.println(user);
            return new ResultBean("post:"+user.getName());
        }
    
    8081服务端调用接口:
            it("postJson", function (done) {
                var result;//保存返回结果
                    $.ajax({
                        type:"post",//请求类型
                        url: baseUrl+"/postJson",
                        contentType:"application/json;charset=UTF-8",//请求内容类型
                        data:JSON.stringify({name: "toly"}),//数据JOSN.stringify
                        // cache: true,//结果可被缓存
                        success: function (json) {
                            result = json;
                        }
                    });
    
                setTimeout(function () {
                    expect(result).toEqual({
                        "data": "post:toly"//预期结果
                    });
                    done()//完成校验
                }, 100);
            });
        });
    
    9414344-40ac3b3eced13592.png
    post发送带有json格式的ajax请求.png

    4-4:既然是Header原因,那就放行呗:com.toly1994.ajaxser.CrosFilter#doFilter

    rep.addHeader("Access-Control-Allow-Headers", "Content-Type");
    
    9414344-85ad421180264ee9.png
    请求成功.png

    可以看到有两个请求,其中一个是OPTIONS的预检请求,下面一句对这个请求做缓存

    • com.toly1994.ajaxser.CrosFilter#doFilter
     rep.addHeader("Access-Control-Max-Age","3600");//一小时内缓存预检请求
    

    5.带Cookie的跨域
    8080服务端暴露接口:com.toly1994.ajaxser.controller.AjaxController
        @GetMapping("/getCookie")
        private ResultBean getCookie(@CookieValue(value = "cookie") String cookie) {
            System.out.println("//////////////////////");
            return new ResultBean("getCookie:"+cookie);
        }
    
    8081服务端调用接口:
       //单元测试用例
            it("getCookie", function (done) {
                var result;//保存返回结果
                $.ajax({
                    type:"get",//请求类型
                    url: baseUrl+"/getCookie",
                    xhrFields:{
                        withCredentials: true//发送ajax请求时加cookie
                    },
                    success: function (json) {
                        result = json;
                    }
                });
    
                setTimeout(function () {
                    expect(result).toEqual({
                        "data": "getCookie:toly"//预期结果
                    });
                    done()//完成校验
                }, 100);
            });
        });
    
    9414344-342bc37a0b7d9763.png
    8080种cookie.png
    9414344-f4bd83952044fbb9.png
    cookie.png

    既然是Credentials原因,那就放行呗:com.toly1994.ajaxser.CrosFilter#doFilter

    rep.addHeader("Access-Control-Allow-Credentials","true");//允许cookie
    
    9414344-fd8bd16b3e6fee65.png
    请求成功.png

    这只解决了8081的跨域,怎么能实现其他的呢?可以获取请求头中的Origin,动态设置。

            HttpServletRequest req = (HttpServletRequest) servletRequest;
            String origin = req.getHeader("Origin");
            if (!StringUtils.isEmpty(origin)) {
                rep.addHeader("Access-Control-Allow-Origin", origin);
            }
    

    6.跨域带自定义头

    8080服务端暴露接口:com.toly1994.ajaxser.controller.AjaxController
        @GetMapping("/getHeader")//
        private ResultBean getHeader(@RequestHeader("x-header1") String header1,
        @RequestHeader("x-header2") String header2) {
            System.out.println(header1 + " " + header2);
            return new ResultBean(header1 + " " + header2);
        }
    
    8081服务端调用接口:
     it("getHeaders", function (done) {
                var result;//保存返回结果
                $.ajax({
                    type:"get",//请求类型
                    url: baseUrl+"/getHeader",
                    headers:{
                        "x-header1": "AAA"
                    },
                    beforeSend: function (xhr) {
                        xhr.setRequestHeader("x-header2","BBB")
                    },
                    success: function (json) {
                        result = json;
                    }
                });
    
                setTimeout(function () {
                    expect(result).toEqual({
                        "data": "AAA BBB"//预期结果
                    });
                    done()//完成校验
                }, 100);
            });
    
    9414344-825f37de4e628ea1.png
    自定义头错误.png
    解决方案:添加头
            //动态添加自定义头
            String headers = req.getHeader("Access-Control-Request-Headers");
            if (!StringUtils.isEmpty(headers)) {
                System.out.println(headers);
                rep.addHeader("Access-Control-Allow-Headers", headers);
            }
    

    7.调用方:隐藏跨域--越过浏览器
    暂略
    
  • 相关阅读:
    Semaphore
    财报分析
    关于C#中的new的用法
    Linux(CentOS)下Postgresql数据库的安装配置
    CentOS下实现SCP免输密码传送文件
    HiveQL逻辑执行顺序
    CentOS上以源码的方式安装Redis笔记
    Python学习心得(七) 深入理解threading多线程模块
    SQL Server返回两个Date日期相差共多少天零多少小时零多少分钟零多少秒
    Python学习心得(六) 反射机制、装饰器
  • 原文地址:https://www.cnblogs.com/toly-top/p/9781970.html
Copyright © 2020-2023  润新知