跨域解决方案实践cors及jsonp

4,820 阅读5分钟
原文链接: github.com

了解几个跨域的方案,并且通过简单实践进行体会。

如何实践?

但是,我们如何进行实践呢?在哪发请求?向什么服务器发请求?很简单,就在当前网页,打开控制台,输入请求的代码

var url = 'http://127.0.0.1:8888/';
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.send();

那么我们就可以以当前页面url作为origin,向http://127.0.0.1:8888/ ,发送请求GET请求了。
同时在本地创建一个node服务

var http = require('http');

http.createServer(function (request, response) {

    response.writeHead(200, {
      'Content-Type': 'text/plain'
    });

    response.end('request success!!!');
}).listen(8888);

console.log('Server running at http://127.0.0.1:8888/');

这样我们就有服务器了,你可以很轻松的跟着这遍文章来实践了,然后从当前网页发送get请求到本地服务,理所当然跨域了。

ps: github网站不行(本文最初再github上编写),会引发csp错误,此错误是用于防止内容注入攻击的,不得不说,大网站安全措施做得就是好,转战segmentfault做实践。

cors_error

1. cors

cors(跨域资源共享 Cross-origin resource sharing),它允许浏览器向跨域服务器发出XMLHttpRequest请求,从而克服跨域问题,它需要浏览器和服务器的同时支持。

cors 分为两种请求,简单请求和非简单请求,关于cors的更详细介绍,推荐阮一峰老师的跨域资源共享 CORS 详解,本文注重实践。

简单请求

正如上方的例子便是一个简单请求

var url = 'http://127.0.0.1:8888/';
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.send();

如何解决此案例的跨域问题呢?

  1. 浏览器端,浏览器会自动在请求头中添加 origin 字段,我们不需要操作。
Request Headers: 
Origin: https://github.com
  1. 服务端,Access-Control-Allow-Origin属性,我们需要服务端设置此属性,指定允许的请求源域名,可以通过指定为 *来指定所以域名。后端动起来:
var http = require('http');

http.createServer(function (request, response) {

    response.writeHead(200, {
      'Content-Type': 'text/plain',
      'Access-Control-Allow-Origin': '*'
    });

    response.end('request success!!!');
}).listen(8888);

console.log('Server running at http://127.0.0.1:8888/');

重启服务,再尝试

cors_error
这次没有再报错了,我们看看服务器放回了什么
response
nice!跨域成功!

非简单请求

同样我们在控制台输入一下代码进行put(非简单请求)

var url = 'http://127.0.0.1:8888/';
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.send();

毫无意外的报错

error_image

在进行非简单请求的时候,浏览器会先发送一次OPTION请求来“预检”(preflight)该请求是否被允许,请求头中会通过Access-Control-Request-MethodAccess-Control-Request-Headers来告诉服务器我需要用到的方法和字段,服务器通过返回的头部信息中的Access-Control-Allow-OriginAccess-Control-Allow-Method来告诉浏览器该跨域请求是否被允许。修改后端代码:

var http = require('http');

http.createServer(function (request, response) {

    response.writeHead(200, {
      'Content-Type': 'text/plain',
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'GET, POST, PUT'
    });

    response.end('request success!!!');
}).listen(8888);

console.log('Server running at http://127.0.0.1:8888/');

可以看到浏览器会先发送一个预检

option

当确认允许跨域之后,以后再发送该请求,就会省去预检处理,之间当作简单请求来操作。很明显,修改了后端代码后,这次的put请求时成功的。这里就不继续上图了。

cors总结

cors(跨域资源共享 Cross-origin resource sharing),它允许浏览器向跨域服务器发出XMLHttpRequest请求,从而克服跨域问题,它需要浏览器和服务器的同时支持。

  1. 浏览器端会自动向请求头添加origin字段,表明当前请求来源。
  2. 浏览器端需要设置响应头的Access-Control-Allow-MethodsAccess-Control-Allow-HeadersAccess-Control-Allow-Origin等字段,指定允许的方法,头部,源等信息。
  3. 请求分为简单请求和非简单请求,非简单请求会先进行一次OPTION方法进行预检,看是否允许当前跨域请求。

2. jsonp

jsonp的原理就是利用就是利用script标签没有跨域限制,可以通过script标签的src属性发送GET请求。我们继续尝试,先把后端有关跨域的设置去掉,并重启服务

var http = require('http');

http.createServer(function (request, response) {

    response.writeHead(200, {
      'Content-Type': 'text/plain'
    });

    response.end('request success!!!');
}).listen(8888);

console.log('Server running at http://127.0.0.1:8888/');

打开我们的控制台输入一下代码,利用script标签进行jsonp请求

var script = document.createElement('script');
script.type = 'text/javascript';
script.src = `http://127.0.0.1:8888/`;
document.head.appendChild(script);

可以看到,后端正常的返回了

request success !!!

而且该请求为GET请求

Request URL: http://127.0.0.1:8888/
Request Method: GET
Status Code: 200 OK
Remote Address: 127.0.0.1:8888
Referrer Policy: no-referrer-when-downgrade

但是我们现在只是成功发送了一个跨域请求,但是我们不像XMLHttpRequest那样可以在res.responseText中拿到数据,通过jsonp我们该怎么拿到请求的数据呢?方法就是前后端约定一个callback字段名,来传递函数名,前端通过该函数来拿到数据。前端代码修改为:

var script = document.createElement('script');
script.type = 'text/javascript';
script.src = `http://127.0.0.1:8888/?callback=onBack`;
document.head.appendChild(script);
function onBack (res) {
  console.log(JSON.stringify(res));
  // 请求完后删除添加到页面上的script标签
  var head = document.head
  head.removeChild(script)
}

通过callback字段来传递函数名onBack,后端代码修改为

var http = require('http')
var urlTool = require('url')
// json 数据
var data = {'methods': 'jsonp', 'result': 'success'};

http.createServer(function (request, response) {
    var params = urlTool.parse(request.url, true)
    console.log(params)
    response.writeHead(200, {
      'Content-Type': 'text/plain'
    });
    if (params.query && params.query.callback) {

      // callback(data)
      var str = `${params.query.callback}(${JSON.stringify(data)})`
    }

    response.end(str);
}).listen(8888);

console.log('Server running at http://127.0.0.1:8888/');

重启后端服务,并且在控制台输入代码,可以看到结果:

jsonp

我们拿到了数据,并且通过onBack函数将他输出到了控制台上!

总结

  1. jsonp是一种跨域方案,他利用script标签没有跨域限制的特点,通过script标签的的src属性发送GET请求。
  2. 可以通过前后端约定一个字段名,比如callback,来传递一个函数名,从而使得前端可以使用对应的callback函数,拿到数据,处理数据。

jsonp和cors比较

  1. CORS与JSONP的使用目的相同,但是比JSONP更强大。
  2. JSONP只支持GET请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。

同源策略:同源策略限制了一个源(origin)中加载文本或脚本与来自其它源(origin)中资源的交互方式,这是一个用于隔离潜在恶意文件的重要安全机制。如果两个页面拥有 相同 的 协议(protocol),端口(如果指定),和 主机,那么这两个页面就属于同一个源(origin)。