跨域问题详解

  在JavaScript中,有一个很重要的安全性限制,被称为“Same-Origin Policy”(同源策略)。这一策略对于JavaScript代码能够访问的页面内容做了很重要的限制,即JavaScript只能访问与包含它的文档在同一域下的内容。

  ??跨越是浏览器进行安全限制的一种方法,如果浏览器禁用了这种安全限制就不会出现跨域问题产生跨域的原因(以下三者都满足):

  • 只要调用方访问被调用方的域名、端口、IP不一样
  • 浏览器没有禁用安全限制
  • 采用了XMLHttpRequest的请求

  JavaScript这个安全策略在进行多iframe或多窗口编程、以及Ajax编程时显得尤为重要。根据这个策略,在baidu.com下的页面中包含的JavaScript代码,不能访问在google.com域名下的页面内容;甚至不同的子域名之间的页面也不能通过JavaScript代码互相访问。对于Ajax的影响在于,通过XMLHttpRequest实现的Ajax请求,不能向不同的域提交请求,例如,在abc.example.com下的页面,不能向def.example.com提交Ajax请求,等等。

  为什么浏览器要实现同源限制?我们举例说明:

  比如一个黑客,他利用iframe把真正的银行登录页面嵌到他的页面上,当你使用真实的用户名和密码登录时,如果没有同源限制,他的页面就可以通过javascript读取到你的表单中输入的内容,这样用户名和密码就轻松到手了。又比如你登录了OSC,同时浏览了恶意网站,如果没有同源限制,该恶意网站就可以构造AJAX请求频繁在OSC发广告帖。

一、跨域问题发生场景

  

  • 特别注意两点:

  1、如果是协议和端口造成的跨域问题“前台”是无能为力的

  2、在跨域问题上,域仅仅是通过“URL的首部”来识别而不会去尝试判断相同的ip地址对应着两个域或两个域是否在同一个ip上。比如上面的,http://www.a.com/a.js和http://70.32.92.74/b.js。虽然域名和域名的ip对应,不过还是被认为是跨域。

  *“URL的首部”指window.location.protocol +window.location.host。其中,window.location.protocol:指含有URL第一部分的字符串,如http: ,window.location.host:指包含有URL中主机名:端口号部分的字符串.如//www.cenpok.net/server/

二、跨域问题的解决方案

  ??解决跨域的思路

  • 被调用方解决: 被调用方解决-支持跨域(根据http协议关于跨域方面的要求,增加响应头信息,告诉浏览器允许被跨域调用)(因为在发生跨域请求时首先调用方发送一个预检请求(OPTIONS请求),这个请求就会被带上允许跨越的请求头信息)
  • 调用方解决:使用代理做调用解决跨域问题-隐藏跨域(利用nginx的反向代理,使访问同一个域名不同的资源路径会代理到不同的服务器上,每个跨域的请求都会带上origin请求头字段,因为访问的资源都是同域名下的,所以不会产生跨越问题)

1、JSONP跨域

?   JSONP(JSON with Padding)是数据格式JSON的一种“使用模式”,可以让网页从别的网域要数据。根据 XmlHttpRequest 对象受到同源策略的影响,而利用 <script>元素的这个开放策略,网页可以得到从其他来源动态产生的JSON数据,而这种使用模式就是所谓的 JSONP。用JSONP抓到的数据并不是JSON,而是任意的JavaScript,用 JavaScript解释器运行而不是用JSON解析器解析。所有,通过Chrome查看所有JSONP发送的Get请求都是js类型,而非XHR。

①原理

  我们知道,在页面上有三种资源是可以与页面本身不同源的。它们是:js脚本,css样式文件,图片,像淘宝等大型网站,肯定会将这些静态资源放入cdn中,然后在页面上连接,如下所示,所以它们是可以链接访问到不同源的资源的。

1 <script type="text/javascript" src="某某cdn地址" ></script>
2 <link type="text/css" rel="stylesheet" href="某个cdn地址" />
3 <img src="某个cdn地址" alt=""/>

  而jsonp就是利用了script标签的src属性是没有跨域的限制的,从而达到跨域访问的目的。因此它的最基本原理就是:动态添加一个<script>标签来实现。

②实现方法:

  这里是使用ajax来请求的,看起来和ajax没啥区别,其实还是有区别的。ajax的核心是通过XmlHttpRequest获取非本页内容,而jsonp的核心则是动态添加<script>标签来调用服务器提供的js脚本。

$.ajax({
        url:"http://www.baidu.com/service",
        dataType:‘jsonp‘,
        data:‘‘,
        jsonp:‘callback‘,
        success:function(data) {
            // some code
        }
    });  

  上面的代码中,callback是必须的,callback是什么值要跟后台拿。获取到的jsonp数据格式如下:

callback({
    "code": "CA1998",
    "price": 1780,
    "tickets": 5
});

 ③JSONP的不足之处:

  1. 只能使用get方法,不能使用post方法:我们知道 script,link, img 等等标签引入外部资源,都是 get 请求的,那么就决定了 jsonp 一定是 get 的。但有时候我们使用的 post 请求也成功,为啥呢?这是因为当我们指定dataType:‘jsonp‘,不论你指定:type:"post" 或者type:"get",其实质上进行的都是 get 请求!
  2. 没有关于 JSONP 调用的错误处理。如果动态脚本插入有效,就执行调用;如果无效,就静默失败。失败是没有任何提示的。例如,不能从服务器捕捉到 404 错误,也不能取消或重新开始请求。不过,等待一段时间还没有响应的话,就不用理它了。

2、跨域资源共享 CORS

?   Cross-Origin Resource Sharing(CORS)跨域资源共享是一份浏览器技术的规范,提供了 Web 服务从不同域传来沙盒脚本的方法,以避开浏览器的同源策略,确保安全的跨域数据传输。现代浏览器使用CORS在API容器如XMLHttpRequest来减少HTTP请求的风险来源。与 JSONP 不同,CORS 除了 GET 要求方法以外也支持其他的 HTTP 要求。

  浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。

①简单请求

  只要同时满足以下两大条件,就属于简单请求。

(1)请求方法是以下三种方法之一:

1 HEAD
2 GET
3 POST

(2)HTTP的头信息不超出以下几种字段:

1 Accept
2 Accept-Language
3 Content-Language
4 Last-Event-ID
5 Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

  后端

 1 // 处理成功失败返回格式的工具
 2 const {successBody} = require(‘../utli‘)
 3 class CrossDomain {
 4   static async cors (ctx) {
 5     const query = ctx.request.query
 6     // *时cookie不会在http请求中带上
 7     ctx.set(‘Access-Control-Allow-Origin‘, ‘*‘)
 8     ctx.cookies.set(‘tokenId‘, ‘2‘)
 9     ctx.body = successBody({msg: query.msg}, ‘success‘)
10   }
11 }
12 module.exports = CrossDomain

  前端什么也不用干,就是正常发请求就可以,如果需要带cookie的话,前后端都要设置一下,下面那个非简单请求例子会看到。

1 fetch(`http://localhost:9871/api/cors?msg=helloCors`).then(res => {
2   console.log(res)
3 })

②非简单请求
  非简单请求会发出一次预检测请求,返回码是204,预检测通过才会真正发出请求,这才返回200。这里通过前端发请求的时候增加一个额外的headers来触发非简单请求。
       

  后端

 1 // 处理成功失败返回格式的工具
 2 const {successBody} = require(‘../utli‘)
 3 class CrossDomain {
 4   static async cors (ctx) {
 5     const query = ctx.request.query
 6     // 如果需要http请求中带上cookie,需要前后端都设置credentials,且后端设置指定的origin
 7     ctx.set(‘Access-Control-Allow-Origin‘, ‘http://localhost:9099‘)
 8     ctx.set(‘Access-Control-Allow-Credentials‘, true)
 9     // 非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)
10     // 这种情况下除了设置origin,还需要设置Access-Control-Request-Method以及Access-Control-Request-Headers
11     ctx.set(‘Access-Control-Request-Method‘, ‘PUT,POST,GET,DELETE,OPTIONS‘)
12     ctx.set(‘Access-Control-Allow-Headers‘, ‘Origin, X-Requested-With, Content-Type, Accept, t‘)
13     ctx.cookies.set(‘tokenId‘, ‘2‘)
14
15     ctx.body = successBody({msg: query.msg}, ‘success‘)
16   }
17 }
18 module.exports = CrossDomain

  一个接口就要写这么多代码,如果想所有接口都统一处理,有什么更优雅的方式呢?见下面的代码

 1 const path = require(‘path‘)
 2 const Koa = require(‘koa‘)
 3 const koaStatic = require(‘koa-static‘)
 4 const bodyParser = require(‘koa-bodyparser‘)
 5 const router = require(‘./router‘)
 6 const cors = require(‘koa2-cors‘)
 7 const app = new Koa()
 8 const port = 9871
 9 app.use(bodyParser())
10 // 处理静态资源 这里是前端build好之后的目录
11 app.use(koaStatic(
12   path.resolve(__dirname, ‘../dist‘)
13 ))
14 // 处理cors
15 app.use(cors({
16   origin: function (ctx) {
17     return ‘http://localhost:9099‘
18   },
19   credentials: true,
20   allowMethods: [‘GET‘, ‘POST‘, ‘DELETE‘],
21   allowHeaders: [‘t‘, ‘Content-Type‘]
22 }))
23 // 路由
24 app.use(router.routes()).use(router.allowedMethods())
25 // 监听端口
26 app.listen(9871)
27 console.log(`[demo] start-quick is starting at port ${port}`)

  前端

 1 fetch(`http://localhost:9871/api/cors?msg=helloCors`, {
 2   // 需要带上cookie
 3   credentials: ‘include‘,
 4   // 这里添加额外的headers来触发非简单请求
 5   headers: {
 6     ‘t‘: ‘extra headers‘
 7   }
 8 }).then(res => {
 9   console.log(res)
10 })

3、反向代理

  想一下,如果我们请求的时候还是用前端的域名,然后有个东西帮我们把这个请求转发到真正的后端域名上,不就避免跨域了吗?这时候,Nginx出场了。
Nginx配置

 1 server{
 2     # 监听9099端口
 3     listen 9099;
 4     # 域名是localhost
 5     server_name localhost;
 6     #凡是localhost:9099/api这个样子的,都转发到真正的服务端地址http://localhost:9871
 7     location ^~ /api {
 8         proxy_pass http://localhost:9871;
 9     }
10 }

  前端就不用干什么事情了,除了写接口,也没后端什么事情了

 1 // 请求的时候直接用回前端这边的域名http://localhost:9099,这就不会跨域,然后Nginx监听到凡是localhost:9099/api这个样子的,都转发到真正的服务端地址http://localhost:9871
 2 fetch(‘http://localhost:9099/api/iframePost‘, {
 3   method: ‘POST‘,
 4   headers: {
 5     ‘Accept‘: ‘application/json‘,
 6     ‘Content-Type‘: ‘application/json‘
 7   },
 8   body: JSON.stringify({
 9     msg: ‘helloIframePost‘
10   })
11 })

  Nginx转发的方式似乎很方便!但这种使用也是看场景的,如果后端接口是一个公共的API,比如一些公共服务获取天气什么的,前端调用的时候总不能让运维去配置一下Nginx,如果兼容性没问题(IE 10或者以上),CROS才是更通用的做法吧。

原文地址:https://www.cnblogs.com/tag6254/p/10357744.html

时间: 2024-10-28 20:24:50

跨域问题详解的相关文章

跨域请求详解

同源策略 Ajax的一个限制是同源策略(same origin policy),它要求所有请求必须来自同一个域名.子域名,并且地址的端口也应当一致.主要原因是处于安全考虑:因为当一个ajax请示被发送,所有的请求都会附带主域的cookie信息一起发送.也就是说,对于远程服务来讲,请求如果是来自于登陆后的用户,若没有同源策略的限制,攻击者就有可能获取你的Gmail里的邮件.得到你的 Fackbook 状态或者你 Twitter 中的好友,这是一个非常严重的安全性问题. 但是,尽管出于安全问题的考虑

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

一.概述 AJAX 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术. AJAX = 异步 JavaScript 和 XML,是一种用于创建快速动态网页的技术.通过在后台与服务器进行少量数据交换,AJAX 可以使网页实现异步更新.这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新.传统的网页(不使用 AJAX)如果需要更新内容,必需重载整个网页面. 本博客实验环境: python:2.7.11 web框架:tonado jquery:2.1.1 二.“伪”AJAX 由于

jsonp 跨域原理详解

JavaScript是一种在Web开发中经常使用的前端动态脚本技术.在JavaScript中,有一个很重要的安全性限制,被称为“Same-Origin Policy”(同源策略).这一策略对于JavaScript代码能够访问的页面内容做了很重要的限制,即JavaScript只能访问与包含它的文档在同一域下的内容. JavaScript这个安全策略在进行多iframe或多窗口编程.以及Ajax编程时显得尤为重要.根据这个策略,在baidu.com下的页面中包含的JavaScript代码,不能访问在

你不知道的jQuery Item11 -- ajax jsonp跨域方法详解

文章从JSON和JSONP区别开始讲起,用实例来对比他们之间的不同之处,然后详细讲解了jQuery中的ajax jsonp的使用并给出了示例及详细参数说明. 1.JSON和JSONP JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,用于在浏览器和服务器之间交换信息. JSONP(JSON With Padding),就是打包在函数调用中的的JSON(或者包裹的JSON),你要跨域请求别人的东西,你肯定要包裹起来,不要污染了别人的东西,把Json数据包裹

ajax请求总是不成功?浏览器的同源策略和跨域问题详解

场景 码农小明要做一个展示业务数据的大屏给老板看,里面包含了来自自己网站的数据和来自隔壁老王的数据.那么自己网站的数据提供了 http://xiaoming.com/whoami 这样的数据接口隔壁老王提供了 http://oldwang.com/isdad 这样的数据接口单独点开都是没有问题的.但是一使用 js 的 ajax 请求就无法收到来自 oldwang.com 的数据了.点开浏览器控制台一看,红字标出(Chrome): XMLHttpRequest cannot load http:/

jQuery jsonp跨域请求详解

跨域的安全限制都是对浏览器端来说的,服务器端是不存在跨域安全限制的. 浏览器的同源策略限制从一个源加载的文档或脚本与来自另一个源的资源进行交互. 如果协议,端口和主机对于两个页面是相同的,则两个页面具有相同的源,否则就是不同源的. 如果要在js里发起跨域请求,则要进行一些特殊处理了.或者,你可以把请求发到自己的服务端,再通过后台代码发起请求,再将数据返回前端. 这里讲下使用jquery的jsonp如何发起跨域请求及其原理. 先看下准备环境:两个端口不一样,构成跨域请求的条件. 获取数据:获取数据

Kafka集群常见的跨网络访问详解

场景说明:当客户端与服务端在不同区域(跨防火墙,地址均做了映射)时,客户端访问kafka时会出现获取不到broker的问题,但是网络之间是互通的.但在跨防火墙下,client请求zookeeper的时候,zookeeper返回给client的broker IP是kafka的实际地址,而不是映射地址,因此client会访问失败. 解决方式一.1.配置文件:listeners=PLAINTEXT://主机名:90922.服务端hosts:内网地址 主机名 3.客户端hosts :外网地址 主机名 注

C#跨窗体传值详解

一.父窗体传值给子窗体      创建一个Winform窗体应用程序项目,然后添加两个窗体frmChildWindow.frmParentWindow (1)通过Form类构造方法的重载传参 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text

前端跨域知识总结

1. 什么是跨域? 跨域一词从字面意思看,就是跨域名嘛,但实际上跨域的范围绝对不止那么狭隘.具体概念如下:只要协议.域名.端口有任何一个不同,都被当作是不同的域.之所以会产生跨域这个问题呢,其实也很容易想明白,要是随便引用外部文件,不同标签下的页面引用类似的彼此的文件,浏览器很容易懵逼的,安全也得不到保障了就.什么事,都是安全第一嘛.但在安全限制的同时也给注入iframe或是ajax应用上带来了不少麻烦.所以我们要通过一些方法使本域的js能够操作其他域的页面对象或者使其他域的js能操作本域的页面