解决跨域问题

解决跨域问题

  介绍一下如何解决跨域问题。
  目录:


为什么会有跨域问题

  为了阻止一个页面上的恶意脚本通过页面的DOM对象获得访问另一个页面上敏感信息的权限,浏览器采用了同源策略。
  在这个策略下,只有在两个页面有相同的时,web 浏览器允许一个页面的脚本访问另一个页面里的数据。
  浏览器的同源策略又分为两种:

  • DOM同源策略:禁止对不同源页面DOM进行操作。这里主要场景是 iframe 跨域的情况,不同域名的 iframe 是限制互相访问的。
  • XmlHttpRequest同源策略:禁止使用XHR对象向不同源的服务器地址发起HTTP请求。

  同源策略限制的范围:

  • 无法读取CookieLocalStorageIndexDB
  • 无法获取DOM
  • 不能发送 Ajax 请求

判断是否同源

  假设一个 URL 为 http://www.example.com/dir/page.html,以其为例判断以下 URL 是否同源:

URL 对比结果原因
http://www.example.com/dir/page2.html同源相同的协议,主机和端口
http://www.example.com/dir2/other.html同源相同的协议,主机和端口
http://username:password@www.example.com/dir2/other.html同源相同的协议,主机和端口
http://www.example.com:81/dir/other.html不同源相同的协议和主机但端口不同
https://www.example.com/dir/other.html不同源协议不同
http://en.example.com/dir/other.html不同源不同主机
http://example.com/dir/other.html不同源不同主机(需要精确匹配)
http://www.example.com:80/dir/other.html待定端口明确,依赖浏览器实现

JSONP实现跨域

  jsonp 利用了 <script> 标签中 src 属性能够跨域访问的特性,先定义了一个回调方法,然后将其当作 url 参数的一部分发送到服务端,服务端通过字符串拼接的方式将用户想要的数据包裹在回调方法中,再传回来,返回的 js 脚本直接就会执行了

<script type="text/javascript">
    // 定义一个回调函数函数
    function callback(data) {
        console.log(data);
    };

    // 创建一个脚本,并且告诉服务端端回调函数名叫callback
    var script = document.createElement('script');
    var url = 'http://localhost:3000/jsonp?callback=callback';
    script.setAttribute("type","text/javascript");
    script.src = url;
        document.body.appendChild(script);
</script>

  使用 node.js 写服务端代码响应请求:

/* GET jsonp listing. */
router.get('/', function(req, res, next) {
    // 要返回的数据
    var data = {
        "name": "kaelyn"
    };
    // 把json数据转化成字符串,方便字符串拼接
    data = JSON.stringify(data);
    // 拼接回调函数的字符串
    var callback = req.query.callback+'('+data+');';
    res.end(callback);
});

  这样就实现了通过 jsonp 跨域访问:
  jsonp返回输出

  值得注意的是,回调函数需要是全局的。
  req.query.callback 获取 URL 的键名为‘callback’ 的查询参数串。
  关于 node.jsExpress 框架的基本使用可以看看这里

  在前端除了上面的一种写法之外,还有其他写法:

<script type="text/javascript">
    function callback(data) {
        console.log(data);
    };
</script>
<!--直接插入一个 script 标签-->
<script type="text/javascript" src="http://localhost:3000/jsonp?callback=callback"></script>

  还可以使用 jquery 来实现:

<script type="text/javascript">
    $.ajax({
        type:"get",
        url:"http://localhost:3000/jsonp?",
        dataType: "jsonp",
        jsonp: "callback",  //在一个jsonp请求中重写回调函数的名字(key)
        jsonpCallback:"showData",   //为jsonp请求指定一个回调函数名(value)
        async:false,
        success:function(data){
            console.log(data);  
        }
    });
</script>

  在 AJAX 请求设置中,jsonpjsonpCallback 选项可以不写,jquery都会帮我们写好的,默认直接回调调用请求成功后的回调函数 success
  如果像上面的代码一样定义,则在服务端接收到的请求 URL 将会加上 ?&callback=showData,如果我们改了AJAX 请求设置中 jsonp 的值,那么服务端也需要做出一些修改:

var callback = req.query.callback+'('+data+');';

  上面代码中的 req.query.callback 要进行相应的修改,因为 URL 中的参数串的键名已经改了,如果还是原来的代码将会获取到 undefined

虽然是使用了 jquery 帮我们封装好的方法,但是 jsonp 和 ajax 在本质上是不一样的东西,ajax 的核心是通过 XmlHttpRequest 获取非本页内容,而 jsonp 的核心则是动态添加<script>标签来调用服务器提供的 js 脚本。


CORS(跨源资源分享)

  CORS(Cross-origin resource sharing)是一个W3C标准,是跨源AJAX请求的根本解决方法
  在服务端启用CORS:

//设置跨域访问  
app.all('*', function(req, res, next) {  
    res.header("Access-Control-Allow-Origin", "*");  
    res.header("Access-Control-Allow-Headers", "X-Requested-With");  
    res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");  
    res.header("X-Powered-By",' 3.2.1')  
    res.header("Content-Type", "application/json;charset=utf-8");  
    next();  
});

上面代码的all方法表示所有请求都必须通过该中间件,参数中的“*”表示对所有路径有效。

  在网页中发起 ajax 请求:

$.ajax({
    type:"get",
    url:"http://localhost:3000/query",
    dataType: "json",
    success:function(data){
        console.log(data);  
    }
});

  结果当然是能成功访问啦:
  CORS返回输出
  可以做个比较,如果没有在服务端没有加上上面的允许其他源访问的代码,浏览器将会报错:
  跨域失败
  服务端返回的 Access-Control-Allow-Origin: * 表明,该资源可以被任意外域访问。如果服务端仅允许来自 http://foo.example 的访问,该首部字段的内容如下:Access-Control-Allow-Origin: http://foo.example
  如果希望允许多个域名访问,则可以这样设置:

app.all('*', function(req, res, next) {  
    // 允许访问的域名列表
    var originList = ["http://localhost:8020", "https://www.baidu.com", "http://www.google.com"];
    // 访问的域名
    var reqOrigin = req.headers.origin;
    // 判断访问的域名是否在允许访问的域名列表中
    if(!!reqOrigin && originList.indexOf(reqOrigin) != -1){
        console.log(reqOrigin);
        res.header("Access-Control-Allow-Origin", reqOrigin);  
        res.header("Access-Control-Allow-Headers", "X-Requested-With");  
        res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");  
        res.header("X-Powered-By",' 3.2.1')  
        res.header("Content-Type", "application/json;charset=utf-8");  
    }
    next();  
});

  这样我们就可以通过判断访问的域名是否在我们允许的域名列表中,如果存在就允许跨域访问,否则不允许。

  Array.prototype.indexOf() 方法返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1。

  关于 CORS 的知识可以看看这里


CORS与JSONP比较

  • 相比 JSONP 只能发 GET 请求,CORS 允许任何类型的请求。
  • JSONP 的优势在于支持老式浏览器(IE浏览器不能低于IE10才能兼容 CORS),以及可以向不支持 CORS 的网站请求数据。

服务器代理实现跨域

  因为浏览器端的同源策略才产生了跨域问题,所以我们可以使用服务器代理的方法绕开浏览器端:前端向与自己同源的服务器发起请求,该同源服务器再向不同源的服务器发送请求(请求转发),把同源服务器请求的数据再返回给前端就大功告成了。
  服务器代理

  首先看看端口号为3000的 node.js 服务器的代码:

/* GET home page. */
router.get('/', function(req, res, next) {
     res.sendfile('./views/proxy.html');
});

router.get('/proxy', function(req, res, next) {
    var headers = req.headers;
    var options = {
        host: 'localhost',
        port: 5000,
        path: '/query',
        method: 'GET',
        headers: headers    
    };
    // 发起 HTTP 请求
    var req = http.request(options, function(res) {
        res.setEncoding('utf8');
        res.on('data', function (data) {
          //从端口号为5000的服务器中获取 data
          var data = JSON.parse(data);
          //获取数据后传回浏览器
          success(data);
        });
    }); 
    req.on('error', function(e){
       console.log("problem with request:" + e.message);
    });
    req.end();

    //获取数据后传回浏览器
    function success(data){
        res.send(data);
    }
});

  当开始服务器后访问http://localhost:3000/,服务器传回一个proxy.html文件给浏览器:

<!--proxy.html-->
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>proxy</title>
    </head>
    <body>
        <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
        <script type="text/javascript">
            $.ajax({
                type:"get",
                url:"http://localhost:3000/proxy",
                dataType: "json",
                success:function(data){
                    console.log(data);  
                }
            });
        </script>
    </body>
</html>

  proxy.html网页中发起 ajax 请求http://localhost:3000/proxy获取数据,然后端口号为3000的服务器接受到请求后再发起 HTTP 请求获取http://localhost:5000/query的数据。
  显然,网页和端口号为3000的服务器就是同源的,这样子 ajax 请求肯定没问题,而且因为服务器端之间不存在跨域问题,所以端口号为3000的服务器向端口号为5000的服务器发送请求也没问题。
  再来看看端口号为5000的 node.js 服务器的代码:

router.get('/query', function(req, res, next) {
    var data = {
        "name": "kaelyn"
    }
    res.json(data);
});

  就这样,当我们启动两个服务器后,打开浏览器访问http://localhost:3000/,就可以看到在控制台上打印出了返回的数据,这就是通过服务器代理解决跨域问题的方法。

关于 node.js 的 http.request 方法的知识可以看看这里

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值