再学跨域(一) -- 同源策略
关于博主:前端程序员,最近专注于 webGis 开发。加微信:MasonChou123,进技术交流群。
浏览器的同源策略是一个重要的安全策略,它限制了只有来自本网站的JS 脚本才能读取自己网站里的资源:
- cookie、LocalStorage 和 IndexDB。
- DOM 对象;
- 通过 fetch 、XMLHttpRequest 获取接口数据。
同源策略防止用户的数据被恶意窃取,但是也给开发带来了一些不便,比如无法通过 AJAX 跨域请求数据,给系统集成带来一些挑战。
用户登录了 http://bank.com
,然后访问了 http://evil.com
,如果 http://evil.com
可以读取 http://bank.com
的数据,那么用户的数据就会被泄露。
同源策略是浏览器专属的,其他客户端没这种限制,比如 postman。
如何判断是否同源?
两个 URL 的协议、域名、端口号都相同,就是同源,否则就是跨域。
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 from | request to | SameSite | SameOrigin |
---|---|---|---|
https://example.com | https://example.com | true | true |
https://app.example.com | https://intranet.example.com | true | false, 域名不同 |
https://example.com | https://example.com:8080 | true | false, 端口不同 |
https://example.com | https://example.co.uk | false, 顶级域名不同 | false, 域名不同 |
https://example.com | http://example.com | false, 协议不同 | false, 协议不同 |
同站:协议相同,域名包含。同源:协议、域名、端口号相同。
不同站,一定不同源,不同源可能同站,同源要求更严格。
参考文章:Bypassing SameSite cookie restrictions
cookie 只能限制是否为同站,和同源有点区别
只要 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 请求,不能提交其他请求。
<!--
* @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 请求的数据会放到请求体中。
后端程序:
/*
* @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,进技术交流群