本文由云+社区发表

做过 web 开发的同学,应该都遇到过跨域的问题,当我们从一个域名向另一个域名发送 Ajax 请求的时候,打开浏览器控制台就会看到跨域错误,今天我们就来聊聊跨域的问题。

1. 浏览器的同源策略

同源的定义是:如果两个页面的*协议*端口(如果有指定)和*域名都相同,则两个页面具有相同的*。同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。

2. 跨域错误信息产生的原因

为了说明问题,我们可以做如下实验,我们在本地搭建了开发环境, 由客户端 http://localhost:3001 向服务器 http://localhost:3000 发送两个请求,一个使用 javascript 异步请求数据,另一个使用 img 标签请求数据,服务器收到请求后,打印接收到请求的日志,如下图所示:

客户端发送两个请求

服务端打印日志并处理请求

代开客户端浏览器的控制台,可以看到发出了两个请求,并且都收到了状态码为 200 的响应,同时控制台报了一个错误,即 xhr 请求报错。由此我们可以知道,之所以产生跨域错误信息,原因有以下三条:

  • 浏览器端的限制(服务端收到了请求并正确返回)
  • 发送的是 XMLHttpRequest 请求(使用 img 标签发送的请求为 json 类型,并不会报错)
  • 请求了不同域的资源

只有同时满足了这三个条件,浏览器才会产生跨域错误。

3. 解决跨域的思路

既然我们知道了跨域错误产生的原因,那么解决思路就很直观了,针对出错的三个原因进行相应的处理即可,相应的解决思路也有三个方向:

  • 打破浏览器的限制
  • 不发送 XHR 请求
  • 解决跨域

下文将分别进行阐述。

3.1 打破浏览器的限制

由上面分析结论可知,之所以出现跨域的错误,实际上是客户端浏览器所做的限制,服务器并未进行限制,因此我们可以通过设置浏览器,使其不进行跨域检查。实际上浏览器也提供了对应的设置选项。

以 MacOS 下的 Chrome 浏览器为例,在终端中使用命令

  1. open -n /Applications/Google\ Chrome.app/ --args --disable-web-security --user-data-dir=/Users/your-computer-account/MyChromeDevUserData/

打开浏览器,即可禁用 Chrome 浏览器的安全检查功能,同时也会禁用跨域安全检查功能,这样再次拿前面的例子进行测试,发现此时不会报错,同时也可以正确拿到服务端返回的数据。

禁用浏览器安全检查功能

这种方式虽然可以实现跨域,但是需要每个用户都对浏览器进行设置,同时可能导致潜在的安全隐患,正常情况下不实用。但这个例子充分说明了,跨域错误是前端浏览器所做的限制,与后台服务无关。

3.2 JSONP实现跨域

根据思路2,既然跨域问题产生的原因是因为客户端发送了 Ajax 请求,那么我们打破这个条件即可。具体实现方式就是使用 JSONP 来进行跨域请求。

  1. JSONP,是 JSON with Padding 的简称,它是 json 的一种补充使用方式,利用 script 标签来解决跨域问题。JSONP 是非官方协议,他只是前后端一个约定,如果请求参数带有约定的参数,则后台返回 javascript 代码而非 json 数据,返回代码是函数调用形式,函数名即约定值,函数参数即要返回的数据。

JSONP 的实现原理如下图所示:

JSONP实现原理

首先在客户端 js 中定义一个函数(假设名为 handler),然后动态创建一个 script 标签插入页面中,script 标签的 src 属性即要调用的地址,同时,在调用的 url 中加入一个服务端约定的参数(假设名为 callback,参数值为已定义的函数名 handler),服务端收到请求,如果发现请求的 url 中带有约定的参数,那么就返回一段函数调用形式的 javascript 代码,该段代码的函数名即为 callback 参数的值 handler,函数的参数即为待返回的数据。这样,客户端拿到返回结果后就会执行 handler 函数,对返回的数据进行处理。

我们使用 jquery 向服务端发送一个 JSONP 格式的请求,从浏览器控制台可以看到请求和对应的响应,如下图所示:

JSONP请求

JSONP请求的响应

由上图可以看到,发送JSONP请求时,请求的 Type 为 script 类型而非 xhr 类型,这样就打破了跨域报错的三个必要条件,不会产生跨域错误,同时也验证了服务端返回的数据格式为 javascript 代码调用的形式,其中 Jquery331045** 这一长串函数名是 jquery 自动生成的。

由于 JSONP 的原理是使用 script 标签来加载数据,所以它的兼容性很好,但是使用 JSONP 来解决跨域问题存在以下缺陷:

  1. 只能发送 GET 请求
  2. 发送的不是 XHR 请求,这样导致 XHR 请求中的很多事件都无法进行处理
  3. 服务端需要改动

3.3 跨域资源共享CORS

CORS 是一个 W3C 标准,全称是"跨域资源共享"(Cross-origin resource sharing)。它允许浏览器向跨源服务器,发出 XMLHttpRequest 请求,从而克服了 AJAX 只能同源使用的限制。CORS 基于 http 协议关于跨域方面的规定,使用时,客户端浏览器直接异步请求被调用端服务端,在响应头增加响应的字段,告诉浏览器后台允许跨域。

跨域错误

回到文章开始的这个跨域错误信息,可以看到错误的具体信息是:服务端没有设置Access-Control-Allow-Origin 这个响应头从而导致报错,通过设置 Access-Control-Allow-Origin: * 这个响应头,我们可以解决问题。但是,这种设置能满足所有情况吗? 更进一步,使用 CORS 时浏览器如何检查跨域错误? 前面我们有讲到,虽然浏览器报错,但是在这之前服务端已经接受了请求,那么,浏览器总是先发出请求后再进行判断吗?下面我们一一讨论。

3.3.1 浏览器如何检查跨域错误

浏览器检查跨域错误的基本原理是:

  1. 浏览器检测到 ajax 请求的域与当前域不一致,会在请求头中增加 Origin 字段,然后检查服务端响应头 Access-Control-Allow-Origin,如果不存在或不匹配,则报跨域错误。

浏览器检查跨域错误原理

3.3.2 浏览器总是先发出请求,然后根据是否有 Access-Control-Allow-Origin 响应头来判断吗

答案是,对于简单请求,是;而对于非简单请求,不是。非简单请求的情况下,浏览器并不是直接请求所需资源,而是会先发出一个预检请求,预检请求通过后才会对所需资源进行请求。

非简单请求预检请求

这里涉及到的简单请求和非简单请求的概念,那么简单请求和非简单请求有什么区别呢?MDN 对非简单请求进行了定义,满足下列条件之一,即为非简单请求:

  1. 使用了下列 HTTP 方法:PUT、DELETE、CONNECT、OPTIONS、TRACE、PATCH
  2. 使用了除以下首部之外的其他首部:Accept、Accept-Language、Content-Language、Content-Type
  3. Content-Type首部的值不属于下列其中一个: application/x-www-form-urlencoded、 multipart/form-data、 text/plain
  4. 请求中的 XMLHttpRequestUpload 对象注册了任意多个事件监听器
  5. 请求中使用了ReadableStream对象

简单来说,除了我们平时使用最多的 GET 和 POST 方法,以及最常使用的 Accept、Accept-Language、Content-Language 和 类型为 application/x-www-form-urlencoded、 multipart/form-data、 text/plain 的 Content-Type 请求头,其他基本都是非简单请求。对于这些非简单请求,浏览器会发出两个请求,第一个为 OPTIONS 遇见请求,遇见请求的响应检查通过后才会发出对资源的请求。

非简单请求过程

生产环境下,如果需要发送非简单跨域请求,每次两个请求会增加响应时间,为此,W3C 标准中增加了另一个响应头 Access-Control-Max-Age 参数,该响应头表明了对于非简单请求的预检请求浏览器的缓存时间,在缓存有效期内,非简单请求可以不发送预检请求,另外,实际开发中,可以在服务端设置接收到的请求方法是 OPTIONS 时,直接返回 200,这样也能加快响应。

3.3.3 设置 Access-Control-Allow-Origin: * 就行吗

带cookie的跨域

当我们需要发送带 cookie 的请求时,Access-Control-Allow-Origin 直接设置为通配符 * 时是无法通过浏览器的检查的,此时该响应头的值必须与发出请求的域完全匹配才行,另外,还需要设置 Access-Control-Allow-Credentials 响应头的值为 true,表示支持带 cookie 的跨域请求。

3.3.4 CORS请求头和响应头总结

请求头:

  • Origin: 浏览器发出 Ajax 跨域请求之前会添加此头部,值为发送请求的域
  • Access-Control-Request-Method:使用了除 GET、POST 请求方法之外的方法,浏览器会添加此头部,值为当前请求方法
  • Access-Control-Request-Headers:使用了自定义头部或除了Accept、Accept-Language、Content-Language、Content-Type 之外的头部,浏览器会添加此头部,值为当前的请求方法

响应头:

  • Access-Control-Allow-Origin: 表示服务端允许哪些域请求资源
  • Access-Control-Allow-Methods: 当客户端包含 Access-Control-Request-Method 请求头时,服务端需要响应该头部,值通常由 Reauest 的 header 中 Access-Control-Request-Method 取得
  • Access-Control-Allow-Headers: 当客户端包含 Access-Control-Request-Headers 请求头时,服务端需要响应该头部,值通常由 Reauest 的 header 中 Access-Control-Request-Headers 取得
  • Access-Control-Expose-Headers: 指出客户端通过 XHR 对象的 getResponseHeaders 方法可以获取的响应头有哪些
  • Access-Control-Allow-Credentials: 允许带 cookie 的跨域请求
  • Access-Control-Max-Age: 预检请求的缓存时间

4. 总结

本文介绍了跨域的原因,重点介绍了使用 JSONP 和 CORS 解决跨域问题的方法。除此之外,实际开发中还其他各种解决跨域问题的思路,本质上,这些方法都是打破跨域错误的三个条件,大家可以自行查资料了解一下。

此文已由作者授权腾讯云+社区在各渠道发布

搜索关注公众号「云加社区」,第一时间获取技术干货,关注后回复1024 送你一份技术课程大礼包!

web开发的跨域问题详解的更多相关文章

  1. jQuery中getJSON跨域原理详解

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytp28 jQuery中getJSON跨域原理详解 前几天我再开发一个叫 河蟹工 ...

  2. 前台主页搭建、后台主页轮播图接口设计、跨域问题详解、前后端互通、后端自定义配置、git软件的初步介绍

    今日内容概要 前台主页 后台主页轮播图接口 跨域问题详解 前后端打通 后端自定义配置 git介绍和安装 内容详细 1.前台主页 Homeviwe.vue <template> <di ...

  3. 「JavaScript」四种跨域方式详解

    超详细并且带 Demo 的 JavaScript 跨域指南来了! 本文基于你了解 JavaScript 的同源策略,并且了解使用跨域跨域的理由. 1. JSONP 首先要介绍的跨域方法必然是 JSON ...

  4. 「JavaScript」JS四种跨域方式详解

    原文地址https://segmentfault.com/a/1190000003642057 超详细并且带 Demo 的 JavaScript 跨域指南来了! 本文基于你了解 JavaScript ...

  5. day78_淘淘商城项目_11_单点登录系统实现 + 用户名回显 + ajax请求跨域问题详解_匠心笔记

    课程计划 1.SSO注册功能实现 2.SSO登录功能实现 3.通过token获得用户信息 4.ajax跨域请求解决方案--jsonp 1.服务接口实现   SSO系统就是解决分布式环境下登录问题的,本 ...

  6. jsonp 跨域原理详解

    JavaScript是一种在Web开发中经常使用的前端动态脚本技术.在JavaScript中,有一个很重要的安全性限制,被称为“Same-Origin Policy”(同源策略).这一策略对于Java ...

  7. Asp.NetCore Web开发之跨域问题

    在前后端分离的web开发中,解决跨域问题是不可避免的,为什么会出现跨域问题呢,这主要是因为web中的"同源策略",浏览器出于安全原因,不让用户随便访问不同于当前站点的资源,也就是说 ...

  8. AJAX请求和跨域请求详解(原生JS、Jquery)

    一.概述 AJAX 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术. AJAX = 异步 JavaScript 和 XML,是一种用于创建快速动态网页的技术.通过在后台与服务器进行少量数 ...

  9. Web开发之跨域问题

    最近在工作上遇到了跨域方面的问题,借此温习巩固. 跨域是受到浏览器的同源策略引起的,为了防止某些文档或脚本加载别的域下的未知内容造成泄露隐私,破坏系统等安全行为. 那什么是同源的呢? 同源是指:应用协 ...

随机推荐

  1. select、poll、epoll

    1.概念 select.poll.epoll都是事件触发机制,当等待的事件发生就触发进行处理,用于I/O复用 2.简单例子理解 3.select函数 3.1函数详解 int select(int ma ...

  2. Python开发——15.协程与I/O模型

    一.协程(Coroutine) 1.知识背景 协程又称微线程,是一种用户态的轻量级线程.子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用了C,C执行完毕返回,B执行完 ...

  3. 20155205 郝博雅 Exp9 Web安全基础

    20155205 郝博雅 Exp9 Web安全基础 一.实验内容 一共做了13个题目. 1.WebGoat 输入java -jar webgoat-container-7.1-exec.jar 在浏览 ...

  4. 网络编程 —— UPD

    UDP协议(数据报协议:SOCK_DGRAM) udp是无链接的,先启动哪一端都不会报错 udp协议的特点: 传输数据不可靠,发送完后没有确认就删除 传送内容可为空 对于查寻速度快 支持的是一对多的模 ...

  5. 记一次Weblogic发布Springboot遇到的坑

    项目使用的是Springboot,之前直接使用JAR包的方式发布,但在客户这边实施发布的时候,客户使用的容器是weblogic,版本为 10.3.6.  痛苦就此开始! 不过项目组还有另外一个也同样使 ...

  6. Nginx 教程(2):性能

    tcp_nodelay, tcp_nopush 和 sendfile tcp_nodelay 在 TCP 发展早期,工程师需要面对流量冲突和堵塞的问题,其中涌现了大批的解决方案,其中之一是由 John ...

  7. 26.HashCode

      在前面三篇博文中讲解了(HashMap.HashSet.HashTable),在其中不断地讲解他们的put和get方法,在这两个方法中计算key的hashCode应该是最重要也是最精华的部分,所以 ...

  8. wxPyhon 的控件(摘抄)

    一.静态文本控件 wx.StaticText(parent, id, label, pos=wx.DefaultPosition,    size=wx.DefaultSize, style=0, n ...

  9. git学习笔记 看廖大神视频小记

    1.创建一个空目录 $ mkdir gittemp $cd gittemp $pwd //x显示当前目录 2.$ git init 把这个目录变成git可以管理的仓库 多的一个隐藏的.git 目录 可 ...

  10. 第二节:Web前端-ASP.NET之C#基础

    第二节:Web前端-ASP.NET之C#基础 学习ASP.NET,要掌握学习语言,控件等技能, <div style="text-align: center; line-height: ...