解决跨域问题

来源:互联网 发布:mac os x lion系统 编辑:程序博客网 时间:2024/05/22 00:18

解决跨域问题

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

  • 解决跨域问题
    • 为什么会有跨域问题
    • 判断是否同源
    • JSONP实现跨域
    • CORS跨源资源分享
    • CORS与JSONP比较
    • 服务器代理实现跨域


为什么会有跨域问题

  为了阻止一个页面上的恶意脚本通过页面的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 方法的知识可以看看这里。

原创粉丝点击