跨域(CORS)原因及解决方案
文章目录
- 跨域(CORS)原因及解决方案
- 什么是跨域?
- 浏览器的同源策略
- 什么是跨域?
- 如何解决跨域
- JSONP(比较老的方法,不推荐)
- CORS (服务器端设置)
- 浏览器跨域配置(开发阶段,测试使用)
- webpack/vite 等工程化工具中配置
什么是跨域?
浏览器的同源策略
浏览器有一个重要的安全机制,叫 同源策略(Same-Origin Policy)。它的意思是:浏览器只允许网页向和自己“同源”的地址发送请求,否则就拦截。
同源的定义是:
协议(http/ https)、域名(或 IP)、端口号 三者完全相同。
假设你的网页地址是:
http://localhost:3000/index.html目标地址:
| 目标地址 | 是否同源 | 原因 |
|---|---|---|
| http://localhost:3000/api/data | ✅同源 | 协议、域名、端口号都一样 |
| https://localhost:3000/api/data | ❌不同源 | 协议不同(https 🆚 http) |
| http://127.0.0.1:3000/api/data | ❌不同源 | 域名不同(localhost 🆚 127.0.0.1) |
什么是跨域?
当你访问一个不同源的接口时,浏览器会因为同源策略阻止这个请求,这就是跨域。
通常控制台会出现这样的错误:
如何解决跨域
JSONP(比较老的方法,不推荐)
解决方法:
- 浏览器生成一个script元素,访问数据接口
- 服务器响应一段js代码,调用某个函数,并把响应数据传入
前端代码:
functionjsonp(url){constscript=document.createElement("script");script.src=url;// 为了不影响页面,script加载过后,将其移除script.addEventListener("load",()=>{script.remove();)}jsonp("请求的url地址");// 服务器返回的js函数functioncallback(data){console.log(data);}服务器端代码:
router.get("/",async(req,res)=>{constresult=awaitstuServ.getStudents();res.status(200).send(getResult(result));// jsonp 实现跨域// 首先将 content-type的值设置为 “application/javascript”// 修改返回的数据,将数据放入到callback函数中res.header("content-type","application/javascript").send(`callback(${JSON.stringify(result)})`);});JSONP的问题:
- 会打乱服务器的消息格式:jsonp要求服务器响应js代码,但是在非跨域情况下,服务器又需要响应一个正常的json合适。
- 只能完成GET请求:浏览器的script标签发出的请求,只能是get请求。
CORS (服务器端设置)
CORS是基于http1.1的一种跨域解决方案,它的全称是Cross-Origin Resource Sharing,跨域资源共享。
总体思路:如果浏览器要跨域访问服务器资源,需要获得服务器的允许。
三种不同的交互模式:
简单请求
- 需要同时满足的条件:
- 请求方法属于:get,post, head 中的一种
- 请求头仅包含安全的字段,常见安全字段如下:
- Accept
- Accept-language
- Content-Type
- Content-language
- DPR
- DownLink
- Save-Data
- Viewport-Width
- Width
- 请求头如果包含 Content-Type,值只能为:
- text/plain
- multipart/form-data
- application/x-www-form-urlencoded
- 当浏览器判定某个ajax请求为简单请求时:
会在请求头中自动添加
Origin字段,告诉服务器是哪个源地址在跨域请求服务器响应头中应该包含
Access-Control-Allow-Origin,允许跨域请求
代码实现:constallowOrigins=["origin1","origin2",...];if("origin"inreq.headers&&allowOrigins.includes(req.headers.origin)){res.header("access-control-allow-origin",req.headers.origin);}
- 需要同时满足的条件:
需要预检的请求
若浏览器判定ajax请求不是一个简单的请求,就会按照下面的流程进行:浏览器发送预检请求(OPTIONS)询问服务器是否允许
服务器允许
浏览器发送真实请求
服务器完成真实响应
例如:
有一个需要预检的跨域请求:fetch("http://myRequest.com/api/test",{method:"POST",header:{"Content-Type":"application/json",a:"a",b:"b"},body:JSON.stringify({name:"abc",age:18})}).then(res=>res.json()).then(data=>console.log(data));此时浏览器会发送一个预检请求,询问服务器是否允许:
预检请求有以下特征:- 请求方法为
OPTIONS - 没有请求体
- 请求头中包含
Origin:请求的源,和简单请求的含义一致Access-Control-Request-Method:后续的真实请求将使用的请求方法Access-Control-Request-Headers:后续的真实请求会改动的请求头
若服务器允许,需要在请求头中添加:
Access-Control-Allow-Method: 允许的后续真实请求Access-Control-Allow-Headers: 允许改动的请求头Access-Control-Allow-Origin:允许的源Access-Control-Max-Age:告诉浏览器,多少秒内,对于同样的请求源、方法、头,都不需要再发送预检请求了
示例代码:
constallowOrigins=["origin1","origin2",...];if(req.method==="OPTIONS"){res.header("Access-Control-Allow-Methods",req.header("access-control-request-method"));res.header("Access-Control-Allow-Headers",req.header("access-control-request-headers"));}if("origin"inreq.headers&&allowOrigins.includes(req.headers.origin)){res.header("access-control-allow-origin",req.headers.origin);}- 请求方法为
附带身份凭证的请求
有一些场景,需要请求携带cookie,只需要在响应头中添加:Access-Control-Allow-Credentials: true即可。
对于一个附带身份凭证的请求,若服务器没有明确告知,浏览器仍然视为跨域被拒绝。
note:
对于跨域请求,nodejs中可以使用 cors库来实现,也可以自己封装跨域的中间件。
浏览器跨域配置(开发阶段,测试使用)
谷歌浏览器为例:
1. 新建目录(如C:\MyChromeDevUserData)。
2. 右键快捷方式→属性→在“目标”字段末尾添加 --disable-web-security --user-data-dir=C:\MyChromeDevUserData(注意参数前有空格)。
若原目标值带引号,参数需加在引号外。
webpack/vite 等工程化工具中配置
module.exports={// 其他配置...devServer:{// 其他devServer配置...proxy:{'/api':{target:'http://example.com',// 目标服务器地址changeOrigin:true,// 是否改变源地址pathRewrite:{'^/api':''},// 重写路径// 其他代理配置...}}}};