Skip to content

同源策略与跨域(携带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服务器配置反向代理

nginx
location /api/ {
     proxy_pass http://127.0.0.1:9000/;  # 匹配wx的接口代理到9000端口
}

2、CORS跨域资源共享

服务端设置响应头

nginx
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=“自定义函数名”

js
  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库】【不受同源策略限制】

js
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

  • 服务端响应头配置:

js
// 指定一个源(不能为*)
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

前端跨域代码示例

js
// 错误写法
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'
})