Skip to content
On this page

再学跨域(一) -- 同源策略

关于博主:前端程序员,最近专注于 webGis 开发。加微信:MasonChou123,进技术交流群。

浏览器的同源策略是一个重要的安全策略,它限制了只有来自本网站的JS 脚本才能读取自己网站里的资源:

  • cookie、LocalStorage 和 IndexDB。
  • DOM 对象;
  • 通过 fetch 、XMLHttpRequest 获取接口数据。

同源策略防止用户的数据被恶意窃取,但是也给开发带来了一些不便,比如无法通过 AJAX 跨域请求数据,给系统集成带来一些挑战。

用户登录了 http://bank.com ,然后访问了 http://evil.com ,如果 http://evil.com 可以读取 http://bank.com 的数据,那么用户的数据就会被泄露。

同源策略是浏览器专属的,其他客户端没这种限制,比如 postman。

如何判断是否同源?

两个 URL 的协议、域名、端口号都相同,就是同源,否则就是跨域。

bash
scheme://host:port # 协议、域名、端口号相同才是同源

跨域后就不能访问对方的资源了,但是可以通过一些方式来实现跨域。

网站通过 CND 引入外部资源是什么情况呢?

网站常常需要通过引入外部资源,比如 JavaScript、css 等,这是什么回事?

某些情况跨域访问资源是被允许的:

  • 跨域嵌入:script、img、link、video、iframe、@font-face、background:url 等把外部资源嵌入文档中;

  • 跨越写入:form 提交、a 链接;

  • 跨域读取被禁止:读取 cookie,XMLHttpRequest 和 Fetch 读取 http 返回的数据。

总结:跨域限制 js 读写,但是允许 html 标签嵌入外部资源。

SameSite vs SameOrigin

如何区分同源和同站?

request fromrequest toSameSiteSameOrigin
https://example.comhttps://example.comtruetrue
https://app.example.comhttps://intranet.example.comtruefalse, 域名不同
https://example.comhttps://example.com:8080truefalse, 端口不同
https://example.comhttps://example.co.ukfalse, 顶级域名不同false, 域名不同
https://example.comhttp://example.comfalse, 协议不同false, 协议不同

同站:协议相同,域名包含。同源:协议、域名、端口号相同。

不同站,一定不同源,不同源可能同站,同源要求更严格。

参考文章:Bypassing SameSite cookie restrictions

只要 domain 和 path 相同,即可读取 cookie。

两种设置 domain 的方式:

浏览器端: document.domain = 'example.com'

.a.example.com.b.example.com 相互读取对方的而 cookie。

服务器端: set-cookie: key=value;domain = .example.com;path=/

https 才能读取

服务器端: set-cookie: key=value;domain = .example.com;path=/;secure

跨域后规避上面三种限制的方式

form 是如何允许跨域写入的?

form 提交是允许跨域的,但是只能提交 GET 和 POST 请求,不能提交其他请求。

html
<!--
 * @Author      : ZhouQiJun
 * @Date        : 2024-09-29 02:30:47
 * @LastEditors : ZhouQiJun
 * @LastEditTime: 2024-09-29 20:05:04
 * @Description : 关于博主:前端程序员,最近专注于 webGis 开发
 * @加微信:MasonChou123,进技术交流群
-->
<!--
         enctype="text/plain" 用于纯文本,调试时使用
         application/x-www-form-urlencoded 默认
         multipart/form-data 上传文件
         在 http://localhost:3000 页面下提交表单到 http://localhost:5566
    -->
<form action="http://localhost:5566/form-post" method="post" accept-charset="utf-8" target="targetIfr">
  <input type="text" name="age" />
  <button>提交</button>
</form>
<form action="http://localhost:5566/form-get" method="get" target="targetIfr">
  <input type="text" name="name" />
  <button>提交</button>
</form>
<iframe name="targetIfr" style="display: none"></iframe>

iframe 用法保证提交后页面不会刷新。

enctype 的会影响提交的数据格式, application/x-www-form-urlencoded 是默认的提交方式, multipart/form-data 用于上传文件。

在浏览器中,enctype 的会表现为 Content-Type 的值。

get 请求的数据会拼接到 url 后面,post 请求的数据会放到请求体中。

后端程序:

js
/*
 * @Author      : ZhouQiJun
 * @Date        : 2024-09-29 10:34:14
 * @LastEditors : ZhouQiJun
 * @LastEditTime: 2024-09-29 19:50:32
 * @Description : 关于博主:前端程序员,最近专注于 webGis 开发
 * @加微信         : MasonChou123,进技术交流群
 */
const express = require('express')
const bodyParser = require('body-parser')
const app = express()

// NOTE 没有启用跨域,form 表单依然能提交数据
// app.use(function (req, res, next) {
//   res.header('Access-Control-Allow-Origin', 'http://localhost:3000') // 第二个参数可以换成你的域名
//   res.header('Access-Control-Allow-Credentials', true)
//   res.header('Access-Control-Allow-Methods', 'POST, PUT, DELETE, OPTIONS')
//   res.header('Access-Control-Allow-Headers', 'Content-Type')
//   next()
// })

// host 静态资源,如图片等等
app.use(express.static('./public'))
// 解析 form 表单提交的数据
app.use(bodyParser.urlencoded({
  extended: false
}))

app.post('/form-post', function(req, res, next) {
  console.log(req.query)
  console.log(req.body?.name)
  console.log(req.body?.age)
  res.json({
    code: 0,
    message: 'success'
  })
})

app.get('/form-get', function(req, res, next) {
  console.log(req.query)
  res.json({
    code: 0,
    message: 'success'
  })
})
// 测试跨域请求的时候记得使用其他的端口,比如http://localhost:8080
app.listen(5566, () => {
  console.log('server is running at http://localhost:5566')
})

为何 form 表单可以跨域提交数据?

这是历史原因,互联网早期没有 XMLHttpRequest 和 JS,只能通过 form 表单提交数据,所以浏览器允许 form 表单跨域提交数据。

另一种解释,form 表单提交数据是一种用户行为,不是脚本行为,即没有使用 JS,表单提交后拿不到接口返回数据,所以浏览器允许 form 表单跨域提交数据。

记住 form 的这个特性,后续学习 CORS 还是在理解它。

小结

  • 同源策略是浏览器的安全策略,限制了 JS 脚本读取其他网站的资源。
  • 两个 URL 的协议、域名、端口号都相同,就是同源,否则就是跨域。
  • 跨域嵌入:script、img、link、video、iframe、@font-face、background:url 等把外部资源嵌入文档中。
  • 跨域写入:form 提交、a 链接。
  • 跨域读取被禁止:读取 cookie,XMLHttpRequest 和 Fetch 读取 http 返回的数据。
  • cookie 同源比较特殊,只要 domain 和 path 相同,即可读取 cookie。
  • form 提交是允许跨域的,但是只能提交 GET 和 POST 请求,不能提交其他请求。

关于博主:前端程序员,最近专注于 webGis 开发。加微信:MasonChou123,进技术交流群

Released under the MIT License.