同源策略与跨域(携带cookie)的解决方案
同源
指协议+域名+端口三者相同,【两个不同的域名指向同一个ip地址,也非同源】
为什么要有同源策略?
保护用户信息安全。
从两个层面看:数据与DOM层面
、网络层面
1、无法读取非同源的本地存储信息(Cookie、localStorage、IndexedDB)及DOM 【例如在钓鱼网站使用iframe嵌入银行首页,就可以轻松获取DOM信息】
2、无法响应发送非同源的ajax/fetch请求 【例如在A网站登录后,
在B网站请求A网站api并携带cookie了
,会造成CSFR攻击。但同源策略阻止了非同源请求并默认不携带cookie】
- 总结:如果缺少了同源策略,cookie会被共享,则毫无安全性可言。浏览器也很容易受到 XSS、CSFR 等攻击。
跨域
向非同源的服务器发请求就会造成跨域,返回的内容将会被浏览器的同源策略拦截
请求跨域解决方案
1、web服务器配置反向代理
location /api/ {
proxy_pass http://127.0.0.1:9000/; # 匹配wx的接口代理到9000端口
}
2、CORS跨域资源共享
服务端设置响应头
location / {
add_header Access-Control-Allow-Origin *; # 允许任何源跨域访问
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS'; # 允许调用的方法
# 允许自定义的请求头字段
add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
# 预检请求响应204及头部
if ($request_method = 'OPTIONS') {
return 204;
}
}
3、jsonp 跨域
注意:业界统一规范jsonp跨域后必须携带参数:callback=“自定义函数名”
var script = document.createElement('script')
script.type = 'text/javascript'
// 传参一个回调函数名给后端,方便后端返回时执行这个在前端定义的回调函数
script.src = 'http://www.xxx.com/login?user=admin&callback=handleCallback'
document.head.appendChild(script)
// 回调执行函数
function handleCallback(res) {
alert(JSON.stringify(res))
}
// 后端返回格式
res.send(params.callback + '(' + ')') // 返回一个函数直接调用
4、WebSocket 协议跨域【Socket.io库】【不受同源策略限制】
var socket = io('http://www.domain2.com:8080');
// 连接成功处理
socket.on('connect', function() {
// 监听服务端消息
socket.on('message', function(msg) {
console.log('data from server: ---> ' + msg);
});
// 监听服务端关闭
socket.on('disconnect', function() {
console.log('Server socket has closed.');
});
});
document.getElementsByTagName('input')[0].onblur = function() {
socket.send(this.value);
};
5、postMessage 跨域【h5新的api,用于解决内嵌第三方页面的通信】
document.domain + iframe 跨域
window.name + iframe 跨域
location.hash + iframe 跨域
withCredentials 属性
跨域请求携带cookie方案
前提:目标跨源站点的cookie的samesite属性不能Lax或者strict,否则以下方案也无法携带cookie
注意1:跨域请求携带cookie是携带请求自身host的cookie,例如在a.com跨域请求b.com,携带的是b.com的cookie
注意2:cookie的作用域:cookie的domain字段设置可以访问该Cookie的域名,如果设置为“.google.com”,则所有以“google.com”结尾的域名都可以访问该Cookie(以 . 开头)【默认跨域不携带cookie, 需设置头部withCredentials及满足其他条件(见下)】
请求头与响应头配置:
请求的时候前端配置请求头
withCredentials:true
服务端响应头配置:
// 指定一个源(不能为*)
Access-Control-Allow-Origin: https://xxx.com
// 允许发送cookie到服务端
Access-Control-Allow-Credentials: true
// 允许的http请求方法
Access-Control-Allow-Methods: GET, POST, PUT
// 允许请求头部: withCredentials:true
Access-Control-Allow-Headers: withCredentials
"预检"请求(preflight)
- 浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错
当满足以下条件之一, 浏览器会先发送一个预检请求option:
PUT或DELETE、Content-Type字段的类型是application/json、其他自定义请求头(例如: token: xxx)
浏览器发送预检请求会自动带上两个特殊的请求头, 用于询问服务器是否同意此次跨源请求【此时服务端应正确响应是否允许该自定义头部,否则浏览器报错】
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: token
前端跨域代码示例
// 错误写法
axios.post('http://sso.xxx.com/v1/api/sso/checkLogin', {}, {
headers: {
withCredentials: true,
}
})
// 方案一axios
axios('http://sso.xxx.com/v1/api/sso/checkLogin', {
withCredentials: true,
method: 'POST'
})
// 或
const service = axios.create({
withCredentials: true // 允许携带cookie
})
// 或
axios.interceptors.request.use((config) => {
config.headers.withCredentials = true
return config
})
// 方案二 fetch【IE全军覆没】 https://www.caniuse.com/?search=fetch
fetch('http://sso.xxx.com/v1/api/sso/checkLogin', {
credentials: 'include', // include, same-origin, *omit
method: 'POST'
})