浏览器的同源策略
浏览器出于对安全的考虑,对同源请求放行,对异源请求限制,这些限制的规则统称为同源策略 ,因此因为同源策略引发的开发问题,称为跨域(异源)问题
同源,两个URL的协议 、域名 、端口号都相同,我们称为同源,当三者中任意一项或多项不同,我们就称之为异源
URL | URL | |
---|---|---|
http://a.com:80/a |
https://a.com:80/a |
❌ |
http://a.com:80/a |
http://b.com:80/a |
❌ |
http://a.com:80/a |
http://a.com:81/a |
❌ |
http://a.com:80/a |
http://a.com:80/a/b |
✅ |
同源请求
浏览器对标签发出请求进行轻微的限制,对ajax进行严厉的限制
浏览器对Ajax的限制
跨域方案
解决跨域的方式有很多,现在主流的就是CORS 、代理两种,根据具体的生产环境我们酌情选择。
生产环境存在跨域问题
第一种情况是前端打包成静态资源,然后上传到静态服务器,通过一个域名来访问(a.com),在运行过程中,我们的js可能需要通过ajax去请求一些数据资源,但是数据服务器的访问域名是另外的(b.com),这就引发了跨域问题。同时也是说明生产环境下是存在跨域问题。这种情况下,我们就不需要考虑开发环境是怎么样的,首先是要解决生产环境的开发问题,那么只有通过JSONP 或CORS ,现在主流的,同时也是官方推荐的解决方案就是CORS
使用CORS解决跨域问题
CORS 是现在跨域问题最正统,也是官方最推荐的解决方案。浏览器检验跨域的规则也称之为CORS规则,我们要解决跨域问题,就是要让它通过校验。
CORS规则
CORS是一套机制,用于浏览器校验跨域请求,他的基本理念是:
- 只要服务器明确表示允许,则校验通过
- 服务器明确拒绝或者没有表示,则校验不通过
从上所述,我们明确了要通过CORS解决跨域问题,必须要后端解决,同时要保证服务器是"自己人"。
CORS 将请求分为简单请求 和预检请求
简单请求
- 请求方式为GET 、HEAD 、POST
- 头部字段满足CORS安全规范
Content-Type
标头所指定的媒体类型的值仅限于下列三者之一:-
text/plain
-
multipart/form-data
-
application/x-www-form-urlencoded
预检请求
预检请求 在请求前先会发送一个请求方式为OPTIONS 的预检请求,询问请求头、请求源、请求方式是否允许,通过后再和简单请求相同发送
生产环境不存在跨域问题
另一种情况,前端开发打包上传到一个静态资源目录,后端程序也搭建好了一个服务器,但是在这两者之间存在一个反向代理,这意味着用户在请求的时候使用的是同一个域名,例如,a.com 访问的是前端页面资源,但是 a.com/api/xxx 就被代理服务器转发到了道理服务器,也就意味着生产环境是不存在跨域问题的,那么就不需要后端使用CORS,这种情况就需要前端在开发服务器中配置代理
使用代理解决跨域问题
Express框架
jsconst express = require('express') // const proxy = require('express-http-proxy') const app = express() //创建服务器 //接受对目标 的GET请求 app.get('目标请求', async (req,res) => { // 使用CORS解决对代理服务器的跨域 res.header('access-control-allow-origin', '*'); // 相应一段文本 res.send('hello,world') }) // 监听8080端口 app.listen(8080, () => { console.log('服务器已启动') })
HTTP模块
jslet http = require('http'); let fs = require('fs'); // 代理配置 let conifg = { '/': { // 需要拦截的本地请求路径 target: 真实代理地址, // 真实代理地址 port: 80, // 端口,默认80 } }; // 创建http服务 let app = http.createServer(function (request, response) { let url = request.url === '/' ? 'index.html' : request.url; // response.setHeader('Access-Control-Allow-Origin', '*'); // response.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE'); // If needed // response.setHeader('Access-Control-Allow-Headers', 'Authorization,Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers,RequestID,X-Content-Type-Options,X-Content-Type-Options,X-Frame-Options,X-Powered-By,X-Version,x-xss-protection,Strict-Transport-Security') // If needed // response.setHeader('Access-Control-Allow-Credentials', true); // 存在代理地址,走代理请求 if (hasProxy(url, request, response)) { return; } // 普通请求和资源加载 fs.readFile(__dirname + url, function (err, data) { if (err) { console.log('请求失败', err); } else { response.end(data); } }); }); // 判断是否存在代理地址 function hasProxy(url, request, response) { for (const key in conifg) { if (url.indexOf(key) < 0) { continue; } const { target, port } = conifg[key]; let info = target.split('//'); let opts = { // 请求参数 protocol: info[0], host: info[1], port: port || 80, method: request.method, path: url, json: true, headers: { ...request.headers, host: info[1], referer: target + url, } } proxy(opts, request, response); return true; } return false; } // 代理转发 function proxy(opts, request, response) { // 请求真实代理接口 var proxyRequest = http.request(opts, function (proxyResponse) { // 代理接口返回数据,写入本地response proxyResponse.on('data', function (chunk) { console.log('chunk', chunk, opts) response.write(chunk, 'binary'); // response.header('access-control-allow-origin', '*'); }); // 代理接口结束,通知本地response结束 proxyResponse.on('end', function () { response.end(); }); response.writeHead(proxyResponse.statusCode, proxyResponse.headers); // console.log(response,'proxy') }); // 本地接口数据传输,通知代理接口请求 request.on('data', function (chunk) { console.log(chunk, 777) proxyRequest.write(chunk, 'binary'); }); // 本地请求结束,通知代理接口请求结束 request.on('end', function (data) { proxyRequest.end(); }); } app.listen(8080); console.log('server is listen on 8080....');