前端面试——跨域问题

跨域问题

浏览器的同源策略
  • 同源是指"协议+域名+端口"三者都相同,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。

  • http://www.a.com:3000/index.html这个网址,协议是http,域名是www.a.com,端口是3000(我们经常看的网址没有,是因为默认端口80可以省略)

  • 同源策略限制一下几种行为:

    • cookie(同ip不同端口可共享)、localStorage、sessionStorage和indexDB无法读取
    • DOM和JS对象无法获得
    • AJAX请求不能发送
跨域解决方案

跨域:从一个网页去请求另一个网页的资源,只要协议、域名、端口其中一个不同,就被当作是跨域

跨域资源共享(CORS)

浏览器将CORS跨域请求分为简单请求非简单请求,对这两种请求的处理是不一样的

只要满足两个条件就属于简单请求,否则属于非简单请求:

使用以下方法之一:
  head、get、post
请求的Header是:
  Accept
  Accept-Language
  Content-Language
  Content-Type(只限于三个值:application/x-www-form-urlencoded、multipart/form-data、text/plain)
简单请求

对于简单请求,浏览器直接发出CORS请求。

在头信息中,增加一个Origin字段,用来说明本次请求来自哪个源(协议+域名+端口),服务器根据这个值,决定是否同意这次请求。

  • 如果Origin指定的源,不在许可范围内,服务器返回的responseHeader中就不会包含Access-Control-Allow-Origin字段,浏览器抛出错误,被XMLHttpRequest的onerror回调函数捕获

  • 如果Origin指定的源在许可范围内,服务器返回响应(会多出几个字段)

    Access-Control-Allow-Origin: http://api.bob.com
    必须。值为请求时的Origin或*,*表示接受任意域名的请求
    
    Access-Control-Allow-Credentials: true
    可选。是否允许发送cookie
    
    Access-Control-Expose-Headers: FooBar //getREsponseHeader('FooBar')可以返回FooBar的值
    可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:
    Cache-Control、Expires、Last-Modified、Content-Type、Content-Language、Pragma
    如果想拿到其他字段就必须在Access-Control-Expose-Headers里面指定
    
    Content-Type: text/html; charset=utf-8
    

withCredentials属性:

CORS请求默认不发送cookie和http认证信息,若要带上cookie,需要服务端和客户端同时设置,否则即使服务器同意带上cookie,浏览器也不会发送,或者浏览器发送,服务器也不会处理

但是如果省略withCredentials设置,有的浏览器还是会发送cookie,这种情况下需要显式关闭withCredentials

服务端设置:Access-Control-Allow-Credentials: true

客户端打开withCredentials属性:
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

注意:如果要发送cookie,Access-Control-Allow-Origin就不能设为*,必须指明与请求网页一致的域名,并且cookie也遵循同源策略,只有服务器域名设置的cookie才会上传,并且跨源原网页代码中的document.cookie也无法读取服务器域名下的cookie

非简单请求

对服务器有特殊要求的请求,比如请求方法是PUT、DELETE,或Content-Type字段的类型是application/json

预检请求:非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为预检请求

浏览器先询问服务器,当前网页所在的域名是否在服务器许可名单之中,以及可以使用哪些HTTP动词和Headers,只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则报错

例如:

var url = 'http://api.alice.com/cors';
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.setRequestHeader('X-Custom-Header', 'value');
xhr.send();

浏览器发现这是一个非简单请求,于是先发送一个预检请求

预检请求的HTTP头信息:

OPTIONS /cors HTTP/1.1  - 请求方法是OPTIONS,表示这个请求是用来询问的
Origin: http://api.bob.com  - 关键字段,表示请求来自哪个源
Access-Control-Request-Method: PUT - 必须。列出浏览器CORS请求会用到哪些方法
Access-Control-Request-Headers: X-Custom-Header - 指定浏览器CORS请求会额外发送的Headers
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

服务器收到预检请求之后,检查了OriginAccess-Control-Request-MethodAccess-Control-Request-Headers之后,确认允许跨源请求就可以做出回应,responseHeader中关键是Access-Control-Allow-Origin,若服务器否定了预检请求,返回一个正常http回应,没有任何CORS相关的头信息字段,浏览器随之报错,同普通请求

其他CORS相关字段:

Access-Control-Allow-Methods: GET, POST, PUT
必须。值为逗号分隔的字符串,表示服务器支持的所有(为避免多次预检请求)跨域请求的方法

Access-Control-Allow-Headers: X-Custom-Header
若requestHeader有Access-Control-Request-Headers,则responseHeader就有Access-Control-Allow-Headers
值为逗号分隔的字符串,表示服务器支持的所有Header字段

Access-Control-Allow-Credentials: true
同简单请求

Access-Control-Max-Age: 1728000 //s
可选。本次预检请求的有效期,在缓存期间内不能发出另一条预检请求

一旦服务器通过了"预检"请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段

通过JSONP跨域
  • jsonp的原理是利用<script>标签没有跨域限制,通过<script>标签的src属性,发送带有callback参数的get请求,服务端将接口返回数据拼凑到callback函数中,返回给浏览器,浏览器解析执行,从而前端拿到callback函数返回的数据

  • 注意:服务端返回的是函数调用,如:

    handleCallback({"name": "amethyst", "color": "blueviolet"})
    
  • server.js

  • const express=require("express");
    
    const app=express();
    
    app.all('/login',(request,response)=>{
        var data={
            name:'amethyst',
            color:'blueviolet'
        }
        var str=JSON.stringify(data);
    
        var cb=request.query.callback;
    
        response.send(`${cb}(${str})`);
    });
    
    app.listen(8080,()=>{
        console.log('8080端口监听中...');
    });
    
  • 前台使用:

  • const script=document.createElement('script');
    script.type='text/javascript';
    
    //传参并指定回调函数名为onBack
    script.src='http://localhost:8080/login?callback=onBack';
    document.head.appendChild(script);
    
    //执行回调函数
    function onBack(res){
        console.log(res);
    }
    
  • 执行结果:

  • 在这里插入图片描述

过程:

  • 网页端插入一个script标签,src指向目标url(url后面加上query,?callback=函数名

  • 后端处理函数接收到请求,得到函数名,比如handle(handle函数定义在前端)

  • 后端用handle包装数据,返回给浏览器(返回的content-type必须是text/javascript;charset=utf-8

  • 网页端script内容加载完成得到handle(data)

  • 浏览器发现内容是js(查看content-type),则调用js解释器执行handle(data)

优点:

  • 它不像XMLHttpRequest对象实现的Ajax请求那样受到同源策略的限制
  • 它的兼容性更好,在更加古老的浏览器中都可以运行
  • 不需要XMLHttpRequest或ActiveX的支持,并且在请求完毕后可以通过调用callback的方式回传结果

缺点:

  • 它只支持GET请求而不支持POST等其它类型的HTTP请求
  • 它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行数据通信的问题(JavaScript调用)
  • 安全性低,容易遭受XSS攻击,因为我们拿到的是对方接口的数据作为js执行,如果得到的是一个很危险的js,获取了用户信息和cookies,这时执行了js就会出现安全问题,因此使用jsonp跨域就必须保证jsonp服务是安全可信的
  • 在失败的时候不会返回各种HTTP状态码
Node中间件跨域(http-proxy-middleware),两次跨域

实现原理:同源策略是浏览器需要遵循的标准,而服务器向服务器发送请求则无需遵循同源策略,使用一个代理服务器,设置Access-Control-Allow-Origin等字段便可以解决浏览器和代理服务器之间的跨域问题,而服务器之间没有限制,便实现了浏览器和服务器之间跨域通信,并且可以实现代理多个请求到不同的服务器

img

代理服务器所需要做的:

  • 接受客户端的请求
  • 将请求转发给服务器
  • 拿到服务器响应数据
  • 将响应转发给客户端

在开发环境下配置代理:

方法一

直接在package.json文件中配置proxy

方法二

新建一个setupProxy.js文件,在里面配置proxy,这种方法可以同时设置转发多个请求

const proxy=require('http-proxy-middleware');

module.exports=function(app){
    app.use(
        proxy('/api1',{
            target:'http://localhost:5000',
            changeOrigin:true,
            pathRewrite:{'^/api1':''}
        }),
        proxy('/api2',{
            target:'http://localhost:5001',
            changeOrigin:true,
            pathRewrite:{'^/api2':''}
        })
    );
}
nginx反向代理

实现原理和node中间件代理类似,只需要修改nginx的配置即可解决跨域问题,支持所有浏览器,支持session,可修改cookie信息

// proxy服务器
server {
    listen       81;
    server_name  www.domain1.com;
    location / {
        proxy_pass   http://www.domain2.com:8080;  #反向代理
        proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
        index  index.html index.htm;

        # 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,
        #下面的跨域配置可不启用
        add_header Access-Control-Allow-Origin http://www.domain1.com;  
        #当前端只跨域不带cookie时,可为*
        add_header Access-Control-Allow-Credentials true;
    }
}
websocket协议跨域

websocket protocol是html5的一种新的协议。,实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现。原生websocket api使用起来不太方便,我们使用socket.io,它很好地封装了websocket接口,提供了更简单、灵活的接口,也对不支持websocket的浏览器提供了向下兼容

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值