什么是跨域
当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域
当前页面url | 被请求页面url | 是否跨域 | 原因 |
---|---|---|---|
www.test.com/ | www.test.com/index.html | 否 | 同源(协议、域名、端口号相同) |
www.test.com/ | www.test.com/index.html | 跨域 | 协议不同(http/https) |
www.test.com/ | www.baidu.com/ | 跨域 | 主域名不同(test/baidu) |
www.test.com/ | blog.test.com/ | 跨域 | 子域名不同(www/blog) |
www.test.com:8080/ | www.test.com:7001/ | 跨域 | 端口号不同(8080/7001) |
跨域问题
跨域问题的具体体现在浏览器上开发页面时,跨域的请求会被浏览器拦截。
出于浏览器的同源策略限制。同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互。所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)
请求被拦截是浏览器对跨域的处理,并非服务端对跨域的处理。某些用于开发的浏览器可以不遵守同源策略,从而无跨域问题。
跨域解决方法
Webpack代理服务器
如果是开发时导致的跨域,最好的解决方法是设置代理服务器。现代开发基于Webpack
我们先简单提及一下webpack-dev-server
这个插件的原理,本地启动了一个使用express的Http服务器
并监听一个端口,这个本地服务器与客户端采用websocket通信协议,当原始文件发生改变,webpack-dev-server会实时编译。上线是请求实际的服务器,开发时是请求本地的服务器。
服务器不仅可以提供热更新的功能,还可以充当代理服务器
配置:
module.exports = {
//...
devServer: {
proxy: {
'/api': {
target: 'http://www.baidu.com/',
changeOrigin: true, // target是域名的话,需要这个参数,
pathRewrite: {'^/api' : ''}
},
'/api2': {
.....
}
}
}
};
参数说明
-
'/api'和pathRewrite
'/api'
是捕获API的标志,如果API中有这个字符串,那么就开始匹配代理,比如API请求/api/users
, 会被代理到请求 www.baidu.com/api/users 。pathRewrite
作用是把路径重写,也就是说会修改最终请求的API路径。比如访问的刚刚未设置pathRewrite
的API路径:http://www.baidu.com/api/users
,设置pathRewrite: {'^/api' : ''},
后,最终代理访问的路径:http://www.baidu.com/users
,这个参数的目的是给代理命名后,在访问时把命名删除掉。 -
target和changeOrigin
代理的API地址,就是需要跨域的API地址。地址可以是域名,如:
http://www.baidu.com
,也可以是IP地址:http://127.0.0.1:3000
。如果是域名需要额外添加一个参数changeOrigin: true
,否则会代理失败。
当我们配置proxy之后,发送请求应该往本地端口发,比如原根地址是 www.baidu.com ,现在根地址就应该变为 /api
。因为发送的请求和url同源,自然不会有跨域问题了,本质是本地服务器代理请求以达到欺骗浏览器的目的。
代理服务器Cookie失效的问题
然而在实际开发中,我们有时会发现设置上述的代理后,请求可以发出去了但浏览器无法通过返回头的Set-Cookie字段设置Cookie。
简单来了解一下Set-Cookie字段
属性 | 说明 |
---|---|
NAME=VALUE | 赋予 Cookie 的名称和其值(必需项) |
expires=DATE | Cookie 的有效期(若不明确指定则默认为浏 览器关闭前为止) |
path=PATH | 将服务器上的文件目录作为Cookie的适用对象(若不指定则默认为文档所在的文件目录) |
domin=域名 | 作为 Cookie 适用对象的域名 (若不指定则 默认为创建 Cookie 的服务器的域名) |
Secure | 仅在 HTTPS 安全通信时才会发送 Cookie |
HttpOnly | 加以限制,使 Cookie 不能被 JavaScript 脚 本访问 |
非同源情况我们已经排除了,那问题只可能出现在Cookie不适用于path,domin和Secure这三项。
例如 后台考虑Cookie的适用范围时,就可能会加上path条件。
比如我的项目上线存放html的地址是 https://xxx/content,我的后台就设置了 path=/content
。而开发请求的地址默认是根目录,即 http://localhost:xxxx ,不在Cookie的适用范围。解决方法有多种:
-
后台判断开发环境进行设置
-
更改根地址,加上
content
-
暴力处理Set-Cookie字段
上面的proxy设置中有
option.cookiePathRewrite
和option.cookieDomainRewrite
的选项,作用是重写Set-Cookie中的path和domain条件,还有一种更为暴力的方法,proxy
可以直接对返回头进行处理
proxy的onProxyRes函数
module.exports = {
// ...
devServer: {
proxy: {
'/api': {
// ...
onProxyRes: function(proxyRes, req, res) {
const cookies = proxyRes.headers['set-cookie']
const cookiePathRegex = /(p|P)ath=\/\w*;/
let newCookie
// 修改cookie Path
if (cookies) {
newCookie = cookies.map(cookie => {
if (cookieRegex.test(cookie)) {
// 替换
return cookie.replace(cookiePathRegex, 'path=/;')
}
return cookie
})
// 替换set-cookie
delete proxyRes.headers['set-cookie']
proxyRes.headers['set-cookie'] = newCookie
// console.log(proxyRes.headers['set-cookie'])
}
}
},
}
}
};
onProxyRes
中我把path改为'path=/',就不存在限制了。因为可以把set-cookie完全重写,理论上可以处理任何cookie不适用的问题。具体处理看返回头,有的设置Cookie是set-cookie字段而不是是Set-Cookie字段,有的是因为domin限制而不是path限制。