同源 | 跨域 | CORS | JSONP

457 阅读5分钟

什么是同源

  • 在控制台输入window.origin或者location.origin可以得到当前源。源=协议+域名+端口号;如果两个url的协议、域名、端口号完全一致,那么这两个url是同源的。不同源的页面之间不准相互访问数据,这是浏览器为保护用户隐私设置的安全策略。
  • 注意: • www.baidu.comwww.baidu.com/s同源,二者只有路径不… • 和 www.baidu.com 不同源,因为端口不同; • 和 baidu.com 不同源,因为协议不同; • 和 news.baidu.com 不同源,因为域名不同

• 代码举例: 令index.html,qq.js和friends.js的服务器为server.json,代码依次如下,url为http://127.0.0.1:8888

  • index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>QQ空间</title>
</head>
<body>
    你的QQ空间
</body>
<script src="qq.js"></script>
</html>
  • qq.js
const request = new XMLHttpRequest()
request.open('GET','/friends.json')
request.onreadystatechange = () =>{
    if(request.readyState === 4 && request.status === 200){
        console.log(request.response)
    }
}
request.send()
  • friends.json
[    {"name":"彭于晏"},    {"name":"林俊杰"}]
  • server.js
var http = require('http')
var fs = require('fs')
var url = require('url')
var port = process.argv[2]

if(!port){
  console.log('请指定端口号好不啦?\nnode server.js 8888 这样不会吗?')
  process.exit(1)
}

var server = http.createServer(function(request, response){
  var parsedUrl = url.parse(request.url, true)
  var pathWithQuery = request.url 
  var queryString = ''
  if(pathWithQuery.indexOf('?') >= 0){ queryString = pathWithQuery.substring(pathWithQuery.indexOf('?')) }
  var path = parsedUrl.pathname
  var query = parsedUrl.query
  var method = request.method

  /******** 从这里开始看,上面不要看 ************/

  console.log('有个傻子发请求过来啦!路径(带查询参数)为:' + pathWithQuery)

  if(path === '/index.html'){
    response.statusCode = 200
    response.setHeader('Content-Type', 'text/html;charset=utf-8')
    response.write(fs.readFileSync('./public/index.html'))
    response.end()
  } else if(path === '/qq.js'){
    response.statusCode = 200
    response.setHeader('Content-Type', 'text/javascript;charset=utf-8')
    response.write(fs.readFileSync('./public/qq.js'))
    response.end()
  } else if(path === '/friends.json'){
    response.statusCode = 200
    response.setHeader('Content-Type', 'text/json;charset=utf-8')
    response.write(fs.readFileSync('./public/friends.json'))
    response.end()
  }else {
    response.statusCode = 404
    response.setHeader('Content-Type', 'text/html;charset=utf-8')
    response.write(`你输入的路径不存在对应的内容`)
    response.end()
  }

  /******** 代码结束,下面不要看 ************/
})

server.listen(port)
console.log('监听 ' + port + ' 成功\n请用在空中转体720度然后用电饭煲打开 http://localhost:' + port)

什么是跨域

  • 浏览器规定如果JS运行在源A里,那么只能获取源A的数据,不能获取源B的数据,也就是不允许跨域。所以,跨域就是允许不同源的页面之间相互访问数据。
  • 禁止跨域:假设上面举例的网站url为qq.com,有黑客网站fake.com图获取用户在网站qq.com的信息。黑客网站的服务器server.js、网站首页index.html和js文件fake.js代码依次如下
  • server.js
var http = require('http')
var fs = require('fs')
var url = require('url')
var port = process.argv[2]

if(!port){
  console.log('请指定端口号好不啦?\nnode server.js 8888 这样不会吗?')
  process.exit(1)
}

var server = http.createServer(function(request, response){
  var parsedUrl = url.parse(request.url, true)
  var pathWithQuery = request.url 
  var queryString = ''
  if(pathWithQuery.indexOf('?') >= 0){ queryString = pathWithQuery.substring(pathWithQuery.indexOf('?')) }
  var path = parsedUrl.pathname
  var query = parsedUrl.query
  var method = request.method

  /******** 从这里开始看,上面不要看 ************/

  console.log('有个傻子发请求过来啦!路径(带查询参数)为:' + pathWithQuery)

  if(path === '/index.html'){
    response.statusCode = 200
    response.setHeader('Content-Type', 'text/html;charset=utf-8')
    response.write(fs.readFileSync('./public/index.html'))
    response.end()
  } else if(path === '/fake.js'){
    response.statusCode = 200
    response.setHeader('Content-Type', 'text/javascript;charset=utf-8')
    response.write(fs.readFileSync('./public/fake.js'))
    response.end()
  } else if(path === '/'){
    response.statusCode = 200
    response.setHeader('Content-Type', 'text/javascript;charset=utf-8')
    response.write('黑客网站首页')
    response.end()
  }else {
    response.statusCode = 404
    response.setHeader('Content-Type', 'text/html;charset=utf-8')
    response.write(`你输入的路径不存在对应的内容`)
    response.end()
  }

  /******** 代码结束,下面不要看 ************/
})

server.listen(port)
console.log('监听 ' + port + ' 成功\n请用在空中转体720度然后用电饭煲打开 http://localhost:' + port)
  • index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>黑客网站</title>
</head>
<body>
   我是黑客网站 
</body>
<script src="fake.js"></script>
</html>
  • fake.js
const request = new XMLHttpRequest()
request.open('GET','http://qq.com:8888/friends.json')
request.onreadystatechange = () =>{
    if(request.readyState === 4 && request.status === 200){
        console.log(request.response)
    }
}
request.send()
  • 此时登入黑客网站fake.com:9999/index.html时,浏览器会读取fake.js的GET请求,然后试图读取url为qq.com:8888/friends.jso…
  • 此时浏览器控制台会打印出“从黑客网站发出的的XMLHttpRequest被阻拦;No Access-Control-Allow-Origin header”字样。

CORS跨域

  • CORS的全称是跨域资源共享标准(Cross-Origin Sharing Standard),它允许在下列场景中使用跨域HTTP请求:
  1. 由XMLHttpRequest 或 Fetch发起的跨域HTTP请求
  2. Web字体(CSS中通过@font-face使用跨域字体资源),网站可以发布TrueType字体资源并只允许已授权网站进行跨域调用
  3. 使用drawImage将Images/video画面绘制到canvas 课程里所讲、和代码中的举例均只涉及第一种情况。 注意:IE 6 7 8 9都不支持CORS
else if(path === '/friends.json'){
    response.statusCode = 200
    response.setHeader('Content-Type', 'text/json;charset=utf-8')
    response.setHeader('Access-Control-Allow-Origin','http://fake.com:9999')
    response.write(fs.readFileSync('./public/friends.json'))
    response.end()
  }

JSONP跨域

  • Wiki 定义:JSONP,或JSON-P(JSON with Padding),是一种历史悠久的JavaScript技术,通过加载
  • 在跨域时,由于当前浏览器或起其他因素导致不支持CORS的情况下跨域的操作。JSONP在请求一个JS文件的时候,这个JS文件会执行一个回调,这个回调有需要跨域的数据。回调的名字可以随机生成,可以是一个随机数,随机数以callback的形式传给后台以后,后台会把这个函数再次返回并执行。
  • JSONP的优点有:
  1. 兼容IE
  2. 可以跨域,即便请求的不是当前IP还是可以跨域
  • JSONP的缺点有:
  1. 由于它是script标签,所以读不到AJAX精确的状态(无状态码,无响应头,只知道是成功还是失败)
  2. 由于它是script标签,所以它只能发送GET请求,不支持发送POST请求。
  • 代码示例:试图通过JSONP实现黑客网站访问qq.com中friends.json的操作,首先在qq.com中新建一个js文件,名为friends.js
window.xxx = {{data}}

然后修改qq.com的服务器server.js

else if(path === '/friends.js'){
    response.statusCode = 200
    response.setHeader('Content-Type', 'text/javascript;charset=utf-8')
    const string = fs.readFileSync('./public/friends.js').toString()
    const data = fs.readFileSync('./public/friends.json').toString()
    const string2 = string.replace('{{data}}', data)
    response.write(string2)
    response.end()
  }

然后修改黑客网站的fake.js文件

const script = document.createElement('script')  //  创建一个script标签
script.src = 'http://qq.com:8888/friends.js'     //  访问qq.com新建的friends.js文件
script.onload = ()=>{                            //  监听onload事件
    console.log(window.xxx)
}
document.body.appendChild(script)                //  把script标签放到页面里

之后再次访问黑客网站fake.com:9999/index.html时…