← 返回目录

浏览器同源策略

第 100 / 154 卷日期:

Presto 内核:Opera7 及以上 Webkit 内核:Safari、Chrome 等

浏览器同源策略

一个域下的JS脚本在未经允许的情况下,不能访问另一个域的内容,同源指的是协议域名和端口均相等的情况,为同一个域。

跨域

由于同源策略的限制,请求发送到后端,后端返回数据时被浏览器的跨域报错拦截。

问:跨域的请求在服务端会不会真正执行?

服务端就算是想拦截,也没法判断请求是否跨域,HTTP Reqeust 的所有 Header 都是可以被篡改的,它用什么去判断请求是否跨域呢?很明显服务端心有余而力不足啊!

请求一定是先发出去,在返回来的时候被浏览器拦截了,如果请求是有返回值的,会被浏览器隐藏掉。

options:预检请求有一个很重要的作用就是询问服务端是不是允许这次请求,如果当前请求是个跨域的请求,你可以理解为:询问服务端是不是允许请求在当前域下跨域发送。

当然,它还有其他的作用,比如询问服务端支持哪些 HTTP 方法。

预检请求虽然不会真正在服务端执行逻辑,但也是一个请求啊,考虑到服务端的开销,不是所有请求都会发送预检的。

一旦浏览器把请求判定为简单请求,浏览器就不会发送预检了。

所以,如果你发送的是一个简单请求,这个请求不管是不是会受到跨域的限制,只要发出去了,一定会在服务端被执行,浏览器只是隐藏了返回值而已。

对于前端开发而言,大部分的跨域问题,都是通过代理解决的。

代理使用的场景是:生产环境不发生跨域,但开发环境发生跨域。所以,只需要在开发环境使用代理解决跨域即可 ——开发代理。

解决跨域

JSONP

前后端配合。只支持GET方法且不安全。利用src发送请求,传递一个回调。

不受跨域问题限制:script,link,img,href,src,因为这些操作都不会通过响应结果进行可能出现安全问题的操作。

通过标签指向一个需要访问的地址并提供一个回调函数来接收数据。

//去创建一个script标签 var script = document.createElement("script");

//script的src属性设置接口地址并带一个callback回调函数名称

document.head.appendChild(script);

//通过定义函数名去接收后台返回数据 function jsonpCallback(data) {

//注意 jsonp返回的数据是json对象可以直接使用

//ajax 取得数据是json字符串需要转换成json对象才可以使用。

}

postMessage

(传输数据)

这种方式通常用于获取嵌入页面中的第三方页面数据。一个页面发送消息,另一个页面判断来源并接收消息

CORS

跨域资源共享 Cross Origin Resourse-Sharing

一般后端开启。是基于HTTP1.1的一种跨域解决方案。

服务端设置 Access-Control-Allow-Origin 就可以开启 CORS。该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。

一个请求可以附带很多信息,会对服务器造成不同程度的影响,又得请求只是获取一些新闻,有的请求会改动服务器的数据。

针对不同的请求,CORS规定了3种不同的交互模式

简单请求需要预检的请求

附带身份凭证的请求

简单请求

当请求同时满足以下条件时,浏览器会认为它是一个简单请求:

1.请求方法属于下面的一种:

get post

head

2.请求头仅包含安全的字段,常见的安全字段如下:

Accept Accept-Language

Content-Language Content-Type

DPR

Width

3.请求头如果包含Content-Type,仅限下面的值之一:

text/plain multipart/form-data

application/x-www-form-urlencoded

4.请求中的任意 XMLHttpRequest 对象均没有注册任何事件监听器;XMLHttpRequest 对象可以使用 XMLHttpRequest.upload 属性访问。

5.请求中没有使用 ReadableStream 对象。

如果以上条件同时满足,浏览器判定为简单请求。

当浏览器判定某个ajax 跨域请求是简单请求时,会发生以下的事情

  1. 请求头中会自动添加Origin字段

比如,在页面http://my.com/index.html中有以下代码造成了跨域

// 简单请求

fetch('http://crossdomain.com/api/news');

请求发出后,请求头会是下面的格式:

GET /api/news/ HTTP/1.1

Host: crossdomain.com

Connection: keep-alive

Referer: http://my.com/index.html

Origin: http://my.com

最后一行,Origin字段会告诉服务器,是哪个源地址在跨域请求

  1. 服务器响应头中应包含Access-Control-Allow-Origin

当服务器收到请求后,如果允许该请求跨域访问,需要在响应头中添加Access-Control-Allow-Origin字段

该字段的值可以是:

*:表示我很开放,什么人我都允许访问具体的源:比如http://my.com,表示我就允许你访问

实际上,这两个值对于客户端http://my.com而言,都一样,因为客户端才不会管其他源服务器允不允许,就关心自己是否被允许

当然,服务器也可以维护一个可被允许的源列表,如果请求的Origin命中该列表,才响应*或具体的源

Date: Tue, 21 Apr 2020 08:03:35 GMT

Access-Control-Allow-Origin: http://my.com

消息体中的数据

当浏览器看到服务器允许自己访问后,高兴的像一个两百斤的孩子,于是,它就把响应顺利的交给 js,以完成后续的操作

需要预检请求

但是,如果浏览器不认为这是一种简单请求,就会按照下面的流程进行:

  1. 浏览器发送预检请求,询问服务器是否允许
  2. 服务器允许
  3. 浏览器发送真实请求
  4. 服务器完成真实的响应

比如,在页面http://my.com/index.html中有以下代码造成了跨域

// 需要预检的请求 fetch('http://crossdomain.com/api/user', {

method: 'POST', // post 请求

headers: {

// 设置请求头 a: 1,

b: 2,

'content-type': 'application/json', },

body: JSON.stringify({ name: '袁小进',

age: 18 }), // 设置请求体

});

浏览器发现它不是一个简单请求,则会按照下面的流程与服务器交互

1.浏览器发送预检请求,询问服务器是否允许

OPTIONS /api/user HTTP/1.1

Access-Control-Request-Method: POST

Access-Control-Request-Headers: a, b, content-type

可以看出,这并非我们想要发出的真实请求,请求中不包含我们的请求头,也没有消息体。

这是一个预检请求,它的目的是询问服务器,是否允许后续的真实请求。

预检请求没有请求体,它包含了后续真实请求要做的事情

预检请求有以下特征:

请求方法为OPTIONS

没有请求体

请求头中包含

Origin:请求的源,和简单请求的含义一致 Access-Control-Request-Method:后续的真实请求将使用的请求方法

Access-Control-Request-Headers:后续的真实请求会改动的请求头

2.服务器允许

服务器收到预检请求后,可以检查预检请求中包含的信息,如果允许这样的请求,需要响应下面的消息格式

HTTP/1.1 200 OK

Date: Tue, 21 Apr 2020 08:03:35 GMT

Access-Control-Allow-Origin: http://my.com

Access-Control-Allow-Methods: POST

Access-Control-Allow-Headers: a, b, content-type

Access-Control-Max-Age: 86400

对于预检请求,不需要响应任何的消息体,只需要在响应头中添加:

Access-Control-Allow-Origin:和简单请求一样,表示允许的源 Access-Control-Allow-Methods:表示允许的后续真实的请求方法

Access-Control-Allow-Headers:表示允许改动的请求头 Access-Control-Max-Age:告诉浏览器,多少秒内,对于同样的请求源、方法、头,都不需要再发送预检请

求了

  1. 浏览器发送真实请求

预检被服务器允许后,浏览器就会发送真实请求了,上面的代码会发生下面的请求数据

POST /api/user HTTP/1.1

Referer: http://my.com/index.html

Origin: http://my.com

{"name": "xiaoming", "age": 18 }

4.服务器响应真实请求

HTTP/1.1 200 OK

Date: Tue, 21 Apr 2020 08:03:35 GMT

Access-Control-Allow-Origin: http://my.com

添加用户成功

可以看出,当完成预检之后,后续的处理与简单请求相同

附带身份凭证请求

默认情况下,ajax 的跨域请求并不会附带 cookie,这样一来,某些需要权限的操作就无法进行

不过可以通过简单的配置就可以实现附带 cookie

// xhr var xhr = new XMLHttpRequest();

xhr.withCredentials = true;

// fetch api fetch(url, {

credentials: 'include', });

当一个请求需要附带 cookie 时,无论它是简单请求,还是预检请求,都会在请求头中添加cookie字段

而服务器响应时,需要明确告知客户端:服务器允许这样的凭据

告知的方式也非常的简单,只需要在响应头中添加:Access-Control-Allow-Credentials: true即可

对于一个附带身份凭证的请求,若服务器没有明确告知,浏览器仍然视为跨域被拒绝。

另外要特别注意的是:对于附带身份凭证的请求,服务器不得设置 Access-Control-Allow-Origin 的值为*。这就是不推荐使用*的原因

额外补充

在跨域访问时,JS 只能拿到一些最基本的响应头,如:Cache-Control、Content-Language、Content-Type、 Expires、Last-Modified、Pragma,如果要访问其他头,则需要服务器设置本响应头。

代理

适用场景:生产环境不发生跨域,但开发环境发生跨域。

开发代理:只需要在开发环境使用代理解决跨域。

module.exports = {

devServer: { // 配置开发服务器

proxy: { // 配置代理

"/api": { // 若请求路径以 /api 开头

target: "http://dev.taobao.com", // 将其转发到 http://dev.taobao.com },

}, },

}

nginx 代理

反向代理功能是nginx的三大主要功能之一(静态web服务器、反向代理、负载均衡)。

反向代理:帮服务器拿到数据,然后选择合适的服务器。

和CORS原理同,需要配置请求响应头Access-Control-Allow-Origin等字段。

怎么做反向代理与负载均衡

Nginx作为反向代理服务器,就是把http请求转发到另一个或者一些服务器上。通过把本地一个url前缀映射到要跨域访问的web服务器上,就可以实现跨域访问。

对于浏览器来说,访问的就是同源服务器上的一个url。

而Nginx通过检测url前缀,把http请求转发到后面真实的物理服务器。

并通过rewrite命令把前缀再去掉。这样真实的服务器就可以正确处理请求,并且并不知道这个请求是来自代理服务器的。

正向代理就是冒充客户端,反向代理就是冒充服务端。

WebSocket 协议(与HTTP同级)

因为WebSocket请求头信息中有origin字段,表示请求源自哪个域,服务器可以根据这个字段判断是否允许本次通信。

document.domain + iframe

原理:相同主域名不同子域名下的页面,

全站分卷索引 · 154 卷
  1. 1代码随想录知识星球精华(最强八股文)第五版(前端篇))
  2. 2html语义化
  3. 3src 和 href 的区别
  4. 4link 与 @import 的区别
  5. 5iframe
  6. 6CSS
  7. 7伪元素和伪类
  8. 8block,inline,inline-block
  9. 9重排、重绘和合成
  10. 10盒子模型
  11. 11margin 合并
  12. 12margin和padding 的值为百分比时
  13. 13浮动
  14. 14定位
  15. 15CSS选择器
  16. 16CSS选择器优先级
  17. 17选择器权重Specificity
  18. 18CSS属性继承
  19. 19不可/可继承属性
  20. 20display: none与 visibility: hidden的区别
  21. 21background-size
  22. 22client、offset&scroll
  23. 23CSS3新特性
  24. 24flex布局
  25. 25常见布局
  26. 26三栏布局
  27. 27垂直居中
  28. 28水平垂直居中
  29. 29CSS画三角形
  30. 30使chrome支持12px以下文字
  31. 31响应式布局
  32. 32媒体查询
  33. 33过渡与动画
  34. 34JavaScript 动画 和 CSS动画
  35. 35SCSS
  36. 36CSS性能优化
  37. 37有哪些数据类型
  38. 38判断数据类型
  39. 39类型转换
  40. 40parseInt&parseFloat
  41. 41浮点数求和步骤
  42. 42十进制小数转二进制
  43. 430.1+0.2 为什么不等于 0.3
  44. 44undefined 和 undeclared 的区别
  45. 45JavaScript NaN 属性
  46. 46isFinite()&isNaN()
  47. 47数组
  48. 48对原数组有影响
  49. 49对原数组无影响
  50. 50数组去重
  51. 51数组拍平
  52. 52ES6新增的数组方法
  53. 53判断是否存在某个值
  54. 54arguments为啥不是数组
  55. 55对象
  56. 56遍历对象的方法
  57. 57判断对象是否具有属性
  58. 58for in 和 for of 的区别
  59. 59继承方式
  60. 60instanceof
  61. 61深浅拷贝
  62. 62freeze属性
  63. 63函数
  64. 64函数柯里化
  65. 65函数的 length
  66. 66箭头函数和普通函数区别
  67. 67构造函数
  68. 68执行上下文
  69. 69作用域
  70. 70作用域和作用域链
  71. 71闭包和内存泄漏
  72. 72this
  73. 73关于 this 的指向问题
  74. 74改变this指向的方法(bind / call / apply)
  75. 75字符串
  76. 76DOM和事件
  77. 77哪些事件支持冒泡
  78. 78阻止冒泡
  79. 79HTMLCollection、NodeList
  80. 80dom.onclick 和 dom.addEventListener的区别
  81. 81e.target和e.currentTarget
  82. 82页面生命周期
  83. 83异步
  84. 84内存管理GC
  85. 85其它
  86. 86JavaScript 中日期时间格式转换
  87. 87input和object实现双向绑定
  88. 88JSON.stringify()
  89. 89escape、encodeURI&encodeURIComponent
  90. 90移动端滚动穿透和溢出
  91. 91ES6
  92. 92Map字典
  93. 93... 与rest
  94. 94proxy
  95. 95模块化
  96. 96Ajax、Fetch和Axios
  97. 97Webpack
  98. 98浏览器:事件循环
  99. 99浏览器内核
  100. 100浏览器同源策略
  101. 101输入URL回车后发生了什么
  102. 102HTTP
  103. 103HTTP 2.0
  104. 104浏览器存储
  105. 105鉴权
  106. 106Webstorage
  107. 107浏览器执行机制
  108. 108AST 抽象语法树
  109. 109性能优化
  110. 110CSS
  111. 111网络相关
  112. 112使用HTTP2.0
  113. 113图片
  114. 114CDN图片
  115. 115其他文件优化
  116. 116降低APP首页开屏渲染时间
  117. 117页面白屏
  118. 118HTTPS中间人攻击
  119. 119Web Worker
  120. 120Service Worker
  121. 121微前端
  122. 122Vue
  123. 123v-show 和 v-if 的区别
  124. 124双向数据绑定的原理
  125. 125Vue中的插槽使用
  126. 126Vue中的key
  127. 127Vue过滤器
  128. 128Vue中Scoped原理
  129. 129Vue中$nextTick的使用
  130. 130Vue中实现组件通信的方式
  131. 131Vue-router
  132. 132Vue路由传参
  133. 133前端路由
  134. 134Vuex
  135. 135虚拟DOM
  136. 136DIFF 算法
  137. 137Vue3和Vue2有哪些区别
  138. 138说一说你对React的理解
  139. 139React事件机制
  140. 140说一说你对react refs的理解
  141. 141react中key的作用
  142. 142组件开发
  143. 143有状态组件和无状态组件
  144. 144函数式组件和类组件的区别
  145. 145PureComponent&Component
  146. 146常见 Hooks
  147. 147状态管理
  148. 148redux toolkit
  149. 149React18新特性
  150. 150client中的createRoot取代以前的ReactDOM.render
  151. 151Suspense
  152. 152新的Hooks
  153. 153VDom一定更快吗
  154. 154Fiber