面试官:跨域的解决办法有哪些

深刻解析各类解决跨域的方法

同源策略

同源策略,它是由Netscape提出的一个著名的安全策略。如今全部支持JavaScript 的浏览器都会使用这个策略来对脚本和请求进行校验,若不一样源,则禁止使用。html

同源的定义

那若是判断是否同源?主要根据三个维度,域名,协议,端口三个都相同才算同源。
举个🌰:前端

网站A 网站B 结果
http://www.zhenai.com http://i.zhenai.com 不一样源,域名不一样
http://www.zhenai.com http://www.zhenai.cn 不一样源,域名不一样
http://www.zhenai.com https://www.zhenai.com 不一样源,协议不一样
http://www.zhenai.com http://www.zhenai.com:3000 不一样源,端口不一样(默认端口80)

同源策略的做用

①没法用js读取非同源的Cookie、LocalStorage 和 IndexDB

这个主要是为了防止恶意网站经过js获取用户其余网站的cookie等用户信息。vue

②没法用js获取非同源的DOM

防止恶意网站经过iframe获取页面dom,从而窃取页面的信息。node

③没法用js发送非同源的AJAX请求

防止恶意的请求攻击服务器窃取数据信息。nginx

那是否是说非同源的请求就没法实现呢?也不是,这就引出了咱们本文主要阐述的解决跨域请求问题的方法。ajax

jsonp

jsonp能实现跨域是利用了img、script和link标签自身的跨域能力。
咱们知道当img或者script中的src是一个连接的时候,浏览器会请求这个连接获取资源,那么这个连接若是是跨域的,浏览器也会请求,从而达到了跨域请求的一个功能。npm

用法

var script = document.createElement('script');
script.src = 'http://localhost:3000/api/test.do?a=1&b=2&callback=cb';
$('body').append(script);

function cb(res){
    // do something
    console.log(res)
}

能够看到,咱们建立一个script标签,将src改为咱们要请求的接口,并将script添加在body中,那么当浏览器解析到这个script时,会想src对应的服务器发送一个get请求,并将参数带过去。
而后当浏览器接收到服务端返回的数据,就会触发参数中callbak对应的回调函数cb,从而完成整个get请求。json

优势

简单粗暴segmentfault

缺点

①只支持get请求
②须要后台配合,将返回结果包装成callback(res)的形式后端

防范

那若是黑客植入script脚本经过jsonp的方式对服务器进行攻击,怎么办?
能够经过页面设置的内容安全协议csp进行防范。

cors跨域

cors 是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing),它容许浏览器向跨源服务器发送XMLHttpRequest请求,从而克服了 AJAX 只能同源使用的限制
cors 须要浏览器和服务器同时支持,整个 CORS通讯过程,都是浏览器自动完成不须要用户参与,对于开发者来讲,cors的代码和正常的 ajax 没有什么差异,浏览器一旦发现跨域请求,就会添加一些附加的头信息
可是,cors不支持ie10及如下版本。

简单请求和复杂请求

浏览器将cors请求分为简单请求和复杂请求。
简单请求则直接发送请求到服务器,只请求一次。
而复杂请求在正式请求前都会有预检请求,在浏览器中都能看到有OPTIONS请求,用于向服务器请求权限信息的,须要请求两次。

那如何区分是简单请求仍是复杂请求呢?

简单请求

简单请求必需要同时知足下面三个条件:

  1. 请求方式只能是:GET、POST、HEAD
  2. HTTP请求头限制这几种字段:Accept、Accept-Language、Content-Language、Content-Type、Last-Event-ID
  3. Content-type只能取:application/x-www-form-urlencoded、multipart/form-data、text/plain

content-type的类型

类型 描述
application/json 消息主体是序列化后的 JSON 字符串
application/x-www-form-urlencoded 数据被编码为键值对。这是标准的编码格式
multipart/form-data 须要在表单中进行文件上传时,就须要使用该格式。常见的媒体格式是上传文件之时使用的
text/plain 数据以纯文本形式(text/json/xml/html)进行编码,其中不含任何控件或格式字符

application/json:

  • 做用: 告诉服务器请求的主题内容是json格式的字符串,服务器端会对json字符串进行解析,
  • 好处: 前端人员不须要关心数据结构的复杂度,只要是标准的json格式就能提交成功。

application/x-www-form-urlencoded:是Jquery的Ajax请求默认方式

  • 做用:在请求发送过程当中会对数据进行序列化处理,以键值对形式?key1=value1&key2=value2的方式发送到服务器。
  • 好处: 全部浏览器都支持。

复杂请求

不知足简单请求的条件,那么就是复杂请求。
复杂请求会在正式请求发送以前,先发一个预检请求进行校验,校验经过后才能进行正式请求。
举个🌰
浏览器如今要发送一个put的复杂请求,那么在put请求发送以前,浏览器先发送一个options请求。
options请求头信息:

OPTIONS /cors HTTP/1.1
Origin: localhost:3000
Access-Control-Request-Method: PUT // 表示使用的什么HTTP请求方法
Access-Control-Request-Headers: X-Custom-Header // 表示浏览器发送的自定义字段
Host: localhost:3000
Accept-Language: zh-CN,zh;q=0.9
Connection: keep-alive
User-Agent: Mozilla/5.0...

服务器收到options请求之后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段之后,确认容许跨源请求,就能够作出回应
options响应头信息

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://localhost:3000 // 表示http://localhost:3000能够访问数据
Access-Control-Allow-Methods: GET, POST, PUT      
Access-Control-Allow-Headers: X-Custom-Header    
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain

当options请求经过以后发出正式的HTTP请求,假若options请求不经过,则服务器不容许这次访问,从而抛出错误

options请求经过以后的,浏览器发出发请求

PUT /cors HTTP/1.1
Origin: http://api.zhenai.com
Host: api.alice.com
X-Custom-Header: value
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

options请求缓存

那这样的话,若是页面存在大量的复杂请求,岂不是每一个请求前面都要进行一次options的请求,那不会形成大量资源的浪费么?
若是基于cors请求的方法来解决跨域问题,那么复杂请求以前是须要进行一个options的请求的,但咱们能够经过对options请求进行缓存来减轻请求的压力。

在options请求中,咱们能够经过设置响应头的参数Access-Control-Max-Age来对结果进行缓存
好比: Access-Control-Max-Age: 600 表示对options检验结果进行十分钟的缓存

  1. url变化会致使缓存失效,须要从新验证options请求的返回值
  2. 预检不关心post data
  3. header变化,若是是去掉了自定义的header使得请求变成简单请求,不会发送options请求。若是是增长其余的header,是会从新验证Access-Control-Allow-Headers的值。
  4. cookie变化,只要后端容许发送cookie,cookie值变化不会致使缓存失效。

该字段的兼容性以下:
image.png

nginx

nginx解决跨域的问题跟以前的方法有所不一样,它是经过服务器的方向代理,将前端访问域名跟后端服务域名映射到同源的地址下,从而实现前端服务和后端服务的同源,那天然不存在跨域的问题了。
举个🌰:
前端服务:http://localhost:3000
前端页面路由:http://localhost:3000/page.html
后端服务:http://localhost:3001
后端接口路由:http://localhost:3001/api/test.do
能够看出,两个服务处于跨域的状态
经过nginx的配置进行反向代理,便可实现先后端服务同源,以下:

server
{
    listen 80;
    server_name localhost;

    location = / {
        proxy_pass http://localhost:3000;
    }

   location /api {
        proxy_pass http://localhost:3001;

        #指定容许跨域的方法,*表明全部
        add_header Access-Control-Allow-Methods *;

        #预检命令的缓存,若是不缓存每次会发送两次请求
        add_header Access-Control-Max-Age 3600;
        #带cookie请求须要加上这个字段,并设置为true
        add_header Access-Control-Allow-Credentials true;

        #表示容许这个域跨域调用(客户端发送请求的域名和端口) 
        #$http_origin动态获取请求客户端请求的域   不用*的缘由是带cookie的请求不支持*号
        add_header Access-Control-Allow-Origin $http_origin;

        #表示请求头的字段 动态获取
        add_header Access-Control-Allow-Headers 
        $http_access_control_request_headers;

        #OPTIONS预检命令,预检命令经过时才发送请求
        #检查请求的类型是否是预检命令
        if ($request_method = OPTIONS){
            return 200;
        }
   }
}

其实nginx不只仅只是用于解决跨域问题,而是涉及到不少服务器资源分配的处理,在此就不详细探讨了。

vue proxyTable

其实,在咱们主流使用的MVVM框架中,配置项里面也提供解决跨域问题的能力,继续举个🌰,以vue2.x为例,咱们能够经过在config/index.js中添加配置项实现跨域请求:

proxyTable: {
    '/apis': {
        // 测试环境
        target: 'http://www.zhenai.com/',  // 接口域名
        changeOrigin: true,  //是否跨域
        pathRewrite: {
            '^/apis': ''   //须要rewrite重写的,
        } 
    }             
}

原理

其实原理很简单,就是在咱们使用npm run dev命中,启动了一个node服务,而后将前端发出的请求发送到node服务,再将该服务转发到本来的后台服务,在这过程当中实现了一层代理,由一个node服务发送一个请求到另一个后台服务,天然也没有了浏览器所限制的跨域问题。

参考文献

https://blog.csdn.net/yingwang9/article/details/90716623
https://www.jianshu.com/p/d89c62572acd
https://segmentfault.com/a/1190000019227927?utm_source=tag-newest

相关文章
相关标签/搜索