跨域方式实现原理
跨域方式实现原理
同源策略限制内容有:
- Cookie、LocalStorage等存储性内容
- AJAX 请求被浏览器拦截
但是有三个标签是允许跨域加载资源:
- <img src=xxx>
- <link href=xxx>
- <script src=xxx>
当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域。不同域之间相互请求资源,就算作“跨域”
协议 和 端口号 造成的跨域是前端无法解决的
跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了
跨域解决方案
1) JSONP
原理:利用 <script>
标签没有跨域限制的漏洞,网页可以得到从其他来源动态产生的 JSON 数据。JSONP请求一定需要对方的服务器做支持才可以。
优点:JSONP优点是简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题
缺点: 仅支持get方法,参数需要拼接起来,不够安全
H5
function jsonp({ url, params, callback }) { return new Promise((resolve, reject) => { let script = document.createElement('script') window[callback] = data => { resolve(data); } params = { ...params, callback } Object.keys(params).forEach(function (value, index) { script.src += index ? `&${value}=${params[value]}` : `${url}?${value}=${params[value]}` }) document.body.appendChild(script) }) } jsonp({ url: 'http://localhost:3000/wel', params: { id: 3, name: 'Kobe' }, callback: 'show' }).then((data) => { console.log(data) })
node服务端
var express = require('express'); var app = express(); // 接口 app.get('/', (req, res) => res.send('hello word!')); app.get('/wel', (req, res) => { let { id, name, callback } = req.query let obj = { name: 'chen', height: 30, code: -1 } if (id && name) { obj = { name: 'li', height: 50, code: 1 } } return res.send(`${callback}(${JSON.stringify(obj)})`) }); app.listen(3000, () => console.log('listening on port 3000'));
2) CORS(参考文章:HTTP访问控制(CORS))
跨域资源共享( CORS )机制允许 Web 应用服务器进行跨域访问控制,从而使跨域数据传输得以安全进行。现代浏览器支持在 API 容器中(例如 XMLHttpRequest
或 Fetch )使用 CORS,以降低跨域 HTTP 请求所带来的风险。(MDN描述)
功能描述 :跨域资源共享标准新增了一组 HTTP 首部字段,允许服务器声明哪些源站通过浏览器有权限访问哪些资源。另外,规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET
以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST
请求),浏览器必须首先使用 OPTIONS
方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。服务器确认允许之后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括 Cookies 和 HTTP 认证相关数据)。
node服务端
let whitList = ['http://localhost:8080','http://localhost:8081'] //设置白名单 app.use(function(req, res, next) { let origin = req.headers.origin console.log(origin) if (whitList.includes(origin)) { // 设置哪个源可以访问我 res.setHeader('Access-Control-Allow-Origin', origin) // 允许携带哪个头访问我 res.setHeader('Access-Control-Allow-Headers', 'x-requested-with') // 允许哪个方法访问我 res.setHeader('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE') // 允许携带cookie res.setHeader('Access-Control-Allow-Credentials', true) // 预检的存活时间 res.setHeader('Access-Control-Max-Age', 36) // 允许返回的头 res.setHeader('Access-Control-Expose-Headers', 'x-requested-with') if (req.method === 'OPTIONS') { res.end() // OPTIONS请求不做任何处理 } } next() }) app.put('/index', function(req, res) { res.setHeader('x-requested-with', 'origin') //返回一个响应头,后台需设置 const obj={ id:1, name:'Kobe', code:1, } return res.send(`${JSON.stringify(obj)}`) })
H5
let xhr = new XMLHttpRequest(); xhr.withCredentials = true; // 前端设置是否带cookie xhr.open("PUT", "http://localhost:3000/index", true); xhr.setRequestHeader("x-requested-with", "origin"); xhr.onreadystatechange = function() { if (xhr.readyState === 4) { if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) { console.log(xhr.response); //得到响应头,后台需设置Access-Control-Expose-Headers console.log(xhr.getResponseHeader("x-requested-with")); } } }; xhr.send();