跨域

159 阅读3分钟

同源策略(Same Origin Policy)

浏览器出于安全考虑,只允许本域下的接口交互。是浏览器的安全机制。

本域:

  • 同协议:都是http或https
  • 同域名:都是http://xxx.com/a 或者http://xxx.com/b
  • 同端口:都是80的端口

例如:xxx.com/a/b.jsxxx.com/index.html 同源

非同源情况下

  • Ajax不能发送请求
  • DOM无法获得
  • Cookie,LocalStorage无法获得

跨域的方法

JSONP

前提:在HTML标签里,一些标签比如script、img这样的获取资源的标签是没有跨域限制的。

JSONP是通过动态创建script来实现跨域。JSONP 需要对应接口的后端的配合才能实现

  • 优点:兼容性好
  • 缺点:只支持get方式请求

实例

<body>
<button class="btn">点击获取</button>
<div class="show"></div>
<script>
    var btn = document.querySelector('.btn')
    //点击创建一个script,利用script中的src不受同源策略约束来跨域获取数据
   btn.addEventListener('click',function (){
        script = document.createElement('script')
       //将src设为目标请求,插入到DOM中,发送请求到服务器,服务器接送该请求并返回数据
       //通过参数getData获取回调函数名showData,将数据作为参数调用返回
        script.src = 'http://localhost:8080/getData?callback=showData'
        document.body.appendChild(script)
    })
    function showData(data){
        var show = document.querySelector('.show')
        show.textContent = data
    }
</script>
</body>

server.js-----node server.js

var http = require('http')
var fs = require('fs')
var path = require('path')
var url = require('url')

http.createServer(function(req, res){
    var pathObj = url.parse(req.url, true)

    switch (pathObj.pathname) {
        case '/getData':
            var data = [
               'hello world'
            ]
            res.setHeader('Content-Type','text/json; charset=utf-8')
            if(pathObj.query.callback){
                res.end(pathObj.query.callback + '(' + JSON.stringify(data) + ')')
            }else{
                res.end(JSON.stringify(data))
            }

            break;

        default:
            fs.readFile(path.join(__dirname, pathObj.pathname), function(e, data){
                if(e){
                    res.writeHead(404, 'not found')
                    res.end('<h1>404 Not Found</h1>')
                }else{
                    res.end(data)
                }
            })
    }
}).listen(8080)

CORS

跨资源共享,对Ajax功能的扩展

参考:跨域资源共享 CORS 详解-阮一峰

简单请求

  • 请求方法满足
    • GET
    • POST
    • HEAD
  • 必选字段
    • Access-Control-Allow-Origin
      • 请求时允许的字段
  • 可选字段
    • Access-Control-Allow-Credentials
      • 表示是否允许发送Cookie
    • 允许发送Cookie时,Access-Control-Allow-Origin不能设置为星号
    • 在Ajax中设置xhr.withCreadentials = true

index.html

<body>
<button class="btn">show</button>
<div class="show"></div>

<script>
    var btn = document.querySelector('.btn')
    // 点击按钮会向后台发起请求获取数据,请求达到后端时会解析callback获取字符串showData
    btn.addEventListener('click',function() {
        var xhr = new XMLHttpRequest()
        //请求的url与访问的url不同域
        xhr.open('GET', 'http://127.0.0.1:8080/getData', true)
        xhr.send()
        xhr.onload = function(){
            showData(JSON.parse(xhr.responseText))
        }
    })
    // 回调函数
    function showData(data) {
        var show = document.querySelector('.show')
        show.textContent = data
    }
</script>
</body>

server.js

var http = require('http')
var fs = require('fs')
var path = require('path')
var url = require('url')

http.createServer(function(req, res){
    var pathObj = url.parse(req.url, true)

    switch (pathObj.pathname) {
        case '/getData':
            var data = [
               'hello world'
            ]
            //关键添加一个Access-Control-Allow-Origin,即允许来自http://localhost:8080的访问
            res.setHeader('Access-Control-Allow-Origin','http://localhost:8080')
            //
                res.end(JSON.stringify(data))
            break;

        default:
            fs.readFile(path.join(__dirname, pathObj.pathname), function(e, data){
                if(e){
                    res.writeHead(404, 'not found')
                    res.end('<h1>404 Not Found</h1>')
                }else{
                    res.end(data)
                }
            })
    }
}).listen(8080)

非简单请求

对服务器有特殊要求的请求

  • PUT/DELETE
  • Content-type字段类型application/json

非简单请求会在正式通信前增加一次预检请求

  • 浏览器先询问服务器是否许可,得到肯定答复再发送XMLHttp请求,否则就报错
  • 预检请求方法是OPTIONS,表示这个请求是用来询问的
  • 必选字段
    • Access-Control-Request-Method
      • GET, POST, PUT
    • Access-Control-Request-Headers
  • 预检请求回应必选字段
    • Access-Control-Allow-Method
    • Access-Control-Allow-Headers

优点:支持所有类型的Http请求 index.html

<body>
<button class="btn">show</button>
<div class="show"></div>

<script>
    var btn = document.querySelector('.btn')
    btn.addEventListener('click',function() {
        var xhr = new XMLHttpRequest()
        //请求方式为PUT
        xhr.open('PUT', 'http://127.0.0.1:8080/getData', true)
        xhr.setRequestHeader('X-Custom-Header', 'value')
        //
        xhr.send()
        xhr.onload = function(){
            showData(JSON.parse(xhr.responseText))
        }
    })
    // 回调函数
    function showData(data) {
        var show = document.querySelector('.show')
        show.textContent = data
    }
</script>
</body>

server.js

var http = require('http')
var fs = require('fs')
var path = require('path')
var url = require('url')

http.createServer(function(req, res){
    var pathObj = url.parse(req.url, true)

    switch (pathObj.pathname) {
        case '/getData':
            var data = [
               'hello world'
            ]
            //关键添加一个Access-Control-Allow-Origin,即允许来自http://localhost:8080的访问
            res.setHeader('Access-Control-Allow-Origin','http://localhost:8080')
            res.setHeader('Access-Control-Allow-Methods','GET, POST, PUT')
            res.setHeader('Access-Control-Allow-Headers',' X-Custom-Header')
                res.end(JSON.stringify(data))
            break;

        default:
            fs.readFile(path.join(__dirname, pathObj.pathname), function(e, data){
                if(e){
                    res.writeHead(404, 'not found')
                    res.end('<h1>404 Not Found</h1>')
                }else{
                    res.end(data)
                }
            })
    }
}).listen(8080)

降域

当两个子域不同,主域相同,适合利用降域来跨域。

实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。

如 a.juejin.com:8080、b.juejin.com:8080。

  • 在两个文件中加入
document.domain = 'juejin.com'
  • 通过a.html文件创建一个iframe,去控制iframe的window进行交互
  • 在hosts添加127.0.0.1 a.juejin.com、127.0.0.1 b.juejin.com
  • 用http-server监听,输入a.juejin.com:8080/a.html 或者 b.juejin.com/b.html 实现跨域
<!--a.html-->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        .ct {
            width: 910px;
            margin: auto;
        }

        .main {
            float: left;
            width: 450px;
            height: 300px;
            border: 1px solid #ccc;
        }

        .main input {
            margin: 20px;
            width: 200px;
        }

        .iframe {
            float: right;
        }

        iframe {
            width: 450px;
            height: 300px;
            border: 1px dashed #ccc;
        }
    </style>
</head>

<body>
    <div class="ct">
        <h1>使用降域实现跨域</h1>
        <div class="main">
            <input type="text" placeholder="跨域成功">
        </div>
        <iframe src="http://b.juejin.com:8080/b.html" frameborder="0"></iframe>
    </div>
    <script>
        document.querySelector('.main input').addEventListener('input', function () {
            console.log(this.value);
            window.frames[0].document.querySelector('input').value = this.value;
        })
        document.domain = 'juejin.com'
    </script>
</body>

</html>
<!--b.html-->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        html,
        body {
            margin: 0;
        }

        input {
            margin: 20px;
            width: 200px;
        }
    </style>
</head>

<body>
    <input id="input" type="text" placeholder="跨域成功">
    <script>
        document.querySelector('#input').addEventListener('input', function () {
            window.parent.document.querySelector('input').value = this.value
        })
        document.domain = "juejin.com"
    </script>
</body>

</html>

postMessage

没有主域限制

postMessage(data,origin)