浏览器跨域问题和如何跨域访问

什么是浏览器跨域问题

  1. 跨域只存在于浏览器中,当A网站访问的资源不是同源的,就会产生跨域问题
  2. 同源策略是浏览器的一种安全机制,要求协议、域名、端口一致
  3. 这是前后端分离后的一个热门话题,在单体应用年代,程序员很少接触这个问题,但浏览器一直有这个安全机制
  4. localhost和127.0.0.1都指向本机,但也不同源,也就是说,要dns的域名一样,ip一样也不行(why?)
  5. js引用脚本是可以跨域访问的,如<script />、<img />

为什么要限制跨域访问

浏览器同源策略包括AJAX同源策略、DOM同源策略

  • AJAX同源策略主要用来防止CSRF攻击。如果没有AJAX同源策略,相当危险,我们发起的每一次HTTP请求都会带上请求地址对应的cookie,那么可以做如下攻击:
    1. 用户访问银行网站http://bank.com,并登录后,http://bank会向cookie中添加用户标记,为了后续的访问不用每次都登录
    2. 用户访问恶意网站http://evil.com,如果可以跨域访问,http://evil.com会用ajax发起对http?/bank.com的请求,如转账等!
    3. 因为请求时,浏览器默认会把http://bank.com对应的cookie发送过去,这样,http://bank.com会拿到之前的cookie,提取用户标志,验证用户无误,认为这个是本人的合法操作
    4. cookie 是明文的,不法网站一样可以利用,如何保证安全?
  • DOM同源策略也一样,如果iframe之间可以跨域访问,可以这样攻击:
    1. 做一个假网站,里面用iframe嵌套一个银行网站 http://mybank.com
    2. 把iframe宽高啥的调整到页面全部,这样用户进来除了域名,别的部分和银行的网站没有任何差别。
    3. 这时如果用户输入账号密码,我们的主网站可以跨域访问到http://mybank.com的dom节点,就可以拿到用户的输入了,那么就完成了一次攻击。
    4. 有一天你刚睡醒,收到一封邮件,说是你的银行账号有风险,赶紧点进 www.yinghang.com 改密码。你吓尿了,赶紧点进去,还是熟悉的银行登录界面,你果断输入你的账号密码,登录进去看看钱有没有少了。 2. 睡眼朦胧的你没看清楚,平时访问的银行网站是 www.yinhang.com,而现在访问的是 www.yinghang.com,这个钓鱼网站做了什么呢?
// HTML

<iframe ></iframe>

// JS

// 由于没有同源策略的限制,钓鱼网站可以直接拿到别的网站的Dom

const iframe = window.frames['yinhang']

const node = iframe.document.getElementById('你输入账号密码的Input')

console.log(`拿到了这个${node},我还拿不到你刚刚输入的账号密码吗`)
  • 同源策略确实能规避一些危险,不是说有了同源策略就安全,只是说同源策略是一种浏览器最基本的安全机制,毕竟能提高一点攻击的成本。其实没有刺不穿的盾,只是攻击的成本和攻击成功后获得的利益成不成正比。

如何进行跨域访问

推荐4和5

  1. jsonp,只支持get,不支持post,需要调用前端和被调用后端配合
  2. 后端HttpClient进行转发,两次请求,效率低,安全(类似Nginx反向代理)
  3. 设置响应头,允许跨域,适于小公司快速解决问题
  4. Nginx搭建API接口网关
  5. Zuul搭建API接口网关

1. jsonp,只支持get,不支持post,需要调用前端和被调用后端配合

1.1 使用《script src=""》来完成一个跨域请求

当点击"跨域获取数据"的按钮时,添加一个<script>标签,用于发起跨域请求;注意看请求地址后面带了一个callback=showData的参数;

showData即是回调函数名称,传到后台,用于包裹数据。数据返回到前端后,就是showData(result)的形式,因为是script脚本,所以自动调用showData函数,而result就是showData的参数。

至此,我们算是跨域把数据请求回来了,但是比较麻烦,需要自己写脚本发起请求,然后写个回调函数处理数据,不是很方便。

<%@ page pageEncoding="utf-8" contentType="text/html;charset=UTF-8"  language="java" %>
<html>
<head>
    <title>跨域测试</title>
    <script src="js/jquery-1.7.2.js"></script>
    <script>
        //回调函数
        function showData (result) {
            var data = JSON.stringify(result); //json对象转成字符串
            $("#text").val(data);
        }

        $(document).ready(function () {

            $("#btn").click(function () {
                //向头部输入一个脚本,该脚本发起一个跨域请求
                $("head").append("<script src='http://localhost:9090/student?callback=showData'><\/script>");
            });

        });
    </script>
</head>
<body>
    <input id="btn" type="button" value="跨域获取数据" />
    <textarea id="text" style="width: 400px; height: 100px;"></textarea>
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    response.setCharacterEncoding("UTF-8");
    response.setContentType("text/html;charset=UTF-8");

    //数据
    List<Student> studentList = getStudentList();


    JSONArray jsonArray = JSONArray.fromObject(studentList);
    String result = jsonArray.toString();

    //前端传过来的回调函数名称
    String callback = request.getParameter("callback");
    //用回调函数名称包裹返回数据,这样,返回数据就作为回调函数的参数传回去了
    result = callback + "(" + result + ")";

    response.getWriter().write(result);
}

1.2 再来看jquery的jsonp方式跨域请求

服务端代码不变,js代码如下:最简单的方式,只需配置一个dataType:‘jsonp’,就可以发起一个跨域请求。jsonp指定服务器返回的数据类型为jsonp格式,可以看发起的请求路径,自动带了一个callback=xxx,xxx是jquery随机生成的一个回调函数名称。

这里的success就跟上面的showData一样,如果有success函数则默认success()作为回调函数。

<%@ page pageEncoding="utf-8" contentType="text/html;charset=UTF-8"  language="java" %>
<html>
<head>
    <title>跨域测试</title>
    <script src="js/jquery-1.7.2.js"></script>
    <script>

        $(document).ready(function () {

            $("#btn").click(function () {

                $.ajax({
                    url: "http://localhost:9090/student",
                    type: "GET",
                    dataType: "jsonp", //指定服务器返回的数据类型
                    success: function (data) {
                        var result = JSON.stringify(data); //json对象转成字符串
                        $("#text").val(result);
                    }
                });

            });

        });
    </script>
</head>
<body>
    <input id="btn" type="button" value="跨域获取数据" />
    <textarea id="text" style="width: 400px; height: 100px;"></textarea>

</body>
</html>

1.3 再看看如何指定特定的回调函数:第30行代码

回调函数你可以写到<script>下(默认属于window对象),或者指明写到window对象里,看jquery源码,可以看到jsonp调用回调函数时,是调用的window.callback。

然后看调用结果,发现,请求时带的参数是:callback=showData;调用回调函数的时候,先调用了指定的showData,然后再调用了success。所以,success是返回成功后必定会调用的函数,就看你怎么写了。

<%@ page pageEncoding="utf-8" contentType="text/html;charset=UTF-8"  language="java" %>
<html>
<head>
    <title>跨域测试</title>
    <script src="js/jquery-1.7.2.js"></script>
    <script>

        function showData (data) {
            console.info("调用showData");

            var result = JSON.stringify(data);
            $("#text").val(result);
        }

        $(document).ready(function () {

//            window.showData = function  (data) {
//                console.info("调用showData");
//
//                var result = JSON.stringify(data);
//                $("#text").val(result);
//            }

            $("#btn").click(function () {

                $.ajax({
                    url: "http://localhost:9090/student",
                    type: "GET",
                    dataType: "jsonp",  //指定服务器返回的数据类型
                    jsonpCallback: "showData",  //指定回调函数名称
                    success: function (data) {
                        console.info("调用success");
                    }
                });
            });

        });
    </script>
</head>
<body>
    <input id="btn" type="button" value="跨域获取数据" />
    <textarea id="text" style="width: 400px; height: 100px;"></textarea>

</body>
</html>

1.4 再看看如何改变callback这个名称:第23行代码

指定callback这个名称后,后台也需要跟着更改。

<%@ page pageEncoding="utf-8" contentType="text/html;charset=UTF-8"  language="java" %>
<html>
<head>
    <title>跨域测试</title>
    <script src="js/jquery-1.7.2.js"></script>
    <script>

        function showData (data) {
            console.info("调用showData");

            var result = JSON.stringify(data);
            $("#text").val(result);
        }

        $(document).ready(function () {

            $("#btn").click(function () {

                $.ajax({
                    url: "http://localhost:9090/student",
                    type: "GET",
                    dataType: "jsonp",  //指定服务器返回的数据类型
                    jsonp: "theFunction",   //指定参数名称
                    jsonpCallback: "showData",  //指定回调函数名称
                    success: function (data) {
                        console.info("调用success");
                    }
                });
            });

        });
    </script>
</head>
<body>
    <input id="btn" type="button" value="跨域获取数据" />
    <textarea id="text" style="width: 400px; height: 100px;"></textarea>

</body>
</html>
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    response.setCharacterEncoding("UTF-8");
    response.setContentType("text/html;charset=UTF-8");

    //数据
    List<Student> studentList = getStudentList();


    JSONArray jsonArray = JSONArray.fromObject(studentList);
    String result = jsonArray.toString();

    //前端传过来的回调函数名称
    String callback = request.getParameter("theFunction");
    //用回调函数名称包裹返回数据,这样,返回数据就作为回调函数的参数传回去了
    result = callback + "(" + result + ")";

    response.getWriter().write(result);
}

1.5 最后看看jsonp是否支持POST方式:ajax请求指定POST方式

可以看到,jsonp方式不支持POST方式跨域请求,就算指定成POST方式,会自动转为GET方式;而后端如果设置成POST方式了,那就请求不了了。

jsonp的实现方式其实就是<script>脚本请求地址的方式一样,只是ajax的jsonp对其做了封装,所以可想而知,jsonp是不支持POST方式的。

2. 后端HttpClient进行转发,两次请求,效率低,安全(类似Nginx反向代理)

@WebServlet("/getServlet")
public class ServletTest extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        /**
         * 创建请求
         */
        CloseableHttpClient aDefault = HttpClients.createDefault();
        /**
         * 创建get请求
         */
        HttpGet httpGet = new HttpGet("http://127.0.0.1/app.ZMTManage/?m=zmtmanage&c=index&a=init");
        /**
         * 执行请求
         */
        CloseableHttpResponse execute = aDefault.execute(httpGet);
        /**
         * 获取状态
         */
        int statusCode = execute.getStatusLine().getStatusCode();
        if (200==statusCode){
            /**
             * 解析请求头
             */
            String entity = EntityUtils.toString(execute.getEntity());
            /**
             * 写入response
             */
            resp.getWriter().print(entity);
            /**
             * 关闭请求
             */
            execute.close();
            aDefault.close();

        }
    }
}

3. 设置响应头,允许跨域,适于小公司快速解决问题

@RequestMapping("/origin")
public String setHeader(HttpServletResponse response) {
    // * 表示允许任何域名跨域访问
    response.setHeader("Access-Control-Allow-Origin", "*");
    // 指定特定域名可以访问
    response.setHeader("Access-Control-Allow-Origin", "http:localhost:8080/");
    // 设置其中一个即可
    // 如果网关或其它地方设置了,1. 在网关过滤,不传递到下下游服务;2. 下游服务设置response.reset()
    return "success";
}

4. Nginx搭建API接口网关(推荐)

  • 这种方式不方便,是

5. Zuul搭建API接口网关(推荐)

服务器接收到请求,并处理了,只是浏览器不接收response?

  • 10
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值