Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

浏览器跨域问题 #30

Open
lxfriday opened this issue Oct 16, 2019 · 6 comments
Open

浏览器跨域问题 #30

lxfriday opened this issue Oct 16, 2019 · 6 comments
Labels
browser browser

Comments

@lxfriday
Copy link
Owner

No description provided.

@lxfriday
Copy link
Owner Author

lxfriday commented Oct 16, 2019

@lxfriday
Copy link
Owner Author

lxfriday commented Jan 29, 2020

JSONP

利用 <script> 标签没有跨域限制的漏洞,网页可以得到从其他来源动态产生的 JSON 数据。JSONP请求一定需要对方的服务器做支持才可以。

JSONP 优点是简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题。缺点是仅支持 get 方法具有局限性,不安全可能会遭受 XSS 攻击。

JSONP 实现流程:

  • 声明一个回调函数,其函数名(如 show )当做参数值,要传递给跨域请求数据的服务器,函数形参为要获取目标数据(服务器返回的 data );
  • 创建一个 <script> 标签,把那个跨域的 API 数据接口地址,赋值给 scriptsrc,还要在这个地址中向服务器传递该函数名(可以通过问号传参: ?callback=show);
  • 服务器接收到请求后,需要进行特殊的处理:把传递进来的函数名和它需要给你的数据拼接成一个字符串,例如:传递进去的函数名是 show,它准备好的数据是 show('我不爱你')
  • 最后服务器把准备的数据通过 HTTP 协议返回给客户端,客户端再调用执行之前声明的回调函数(show),对返回的数据进行操作;
// index.html
function jsonp({ url, params, callback }) {
  return new Promise((resolve, reject) => {
    let script = document.createElement('script')
    // 把要执行的函数名传递到服务端,服务端回传一个对该函数的调用(同时附带需要处理的参数)
    window[callback] = function(data) {
      resolve(data)
      document.body.removeChild(script)
    }
    params = { ...params, callback } // wd=b&callback=show
    let arrs = []
    for (let key in params) {
      arrs.push(`${key}=${params[key]}`)
    }
    script.src = `${url}?${arrs.join('&')}`
    document.body.appendChild(script)
  })
}
jsonp({
  url: 'http://localhost:3000/say',
  params: { wd: 'Iloveyou' },
  callback: 'show'
}).then(data => {
  console.log(data)
})
// server.js
const url = require('url')
const http = require('http')

const app = http.createServer((req, res) => {
  const urlObj = url.parse(req.url)
  console.log('urlObj', urlObj)
  const query = urlObj.query.split('&').map(v => ({ [v.split('=')[0]]: v.split('=')[1] }))
  console.log('query', query)
  // 服务端返回 show('a cat '),这样浏览器就会执行在浏览器中注册的函数
  res.end(`${query[1].callback}('a cat ')`)
})

app.listen(3333)

console.log('listening')

// urlObj Url {
//   protocol: null,
//   slashes: null,
//   auth: null,
//   host: null,
//   port: null,
//   hostname: null,
//   hash: null,
//   search: '?wd=Iloveyou&callback=show',
//   query: 'wd=Iloveyou&callback=show',
//   pathname: '/say',
//   path: '/say?wd=Iloveyou&callback=show',
//   href: '/say?wd=Iloveyou&callback=show' }
// query [ { wd: 'Iloveyou' }, { callback: 'show' } ]

@lxfriday
Copy link
Owner Author

lxfriday commented Jan 29, 2020

CORS(跨域资源共享)

简单请求

浏览器将 CORS 请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。

只要符合下面条件的都是简单请求:

  1. 请求方法是以下三种方法之一:
  • HEAD
  • GET
  • POST
  1. HTTP 的头信息不超出以下几种字段:
  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type:只限于三个值 application/x-www-form-urlencodedmultipart/form-datatext/plain

对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。

GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。

Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8
  • Access-Control-Allow-Origin:该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。
  • Access-Control-Allow-Credentials:它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。
  • Access-Control-Expose-Headers:CORS请求时,XMLHttpRequest 对象的 getResponseHeader() 方法只能拿到6个基本字段:Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers 里面指定。上面的例子指定,getResponseHeader('FooBar') 可以返回 FooBar 字段的值。

withCredentials

上面说到,CORS请求默认不发送Cookie和HTTP认证信息。如果要把Cookie发到服务器,一方面要服务器同意,指定Access-Control-Allow-Credentials字段。

Access-Control-Allow-Credentials: true

另一方面,开发者必须在AJAX请求中打开withCredentials属性。

var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

如果要发送Cookie,Access-Control-Allow-Origin 就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie也无法读取服务器域名下的Cookie。

非简单请求

非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUTDELETE,或者Content-Type字段的类型是application/json

非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。

浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。

"预检"请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源。

  • Access-Control-Allow-Methods:该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求;
  • Access-Control-Allow-Headers:它是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段;
  • Access-Control-Allow-Credentials:同简单请求;
  • Access-Control-Max-Age用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是20天(1728000秒),即允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求。

CORS与JSONP的使用目的相同,但是比JSONP更强大。

JSONP只支持GET请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。

@lxfriday
Copy link
Owner Author

lxfriday commented Jan 29, 2020

websocket

WebSocket 和HTTP都是应用层协议,都基于 TCP 协议。但是 WebSocket 是一种双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据。同时,WebSocket 在建立连接时需要借助 HTTP 协议,连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了。

服务端

// app.js
const WebSocket = require('ws')
const wss = new WebSocket.Server({ port: 3333 })
wss.on('connection', function(ws) {
  console.log('connection')

  ws.on('message', function(data) {
    console.log('message')
    console.log(data)
    ws.send('我不爱你')
    ws.send(JSON.stringify({ name: 'lxfriday' }))
  })
})
console.log('listening 3333')

页面

<script>
  let socket = new WebSocket('ws://localhost:3333')
  socket.onopen = function() {
    socket.send('我爱你') // 向服务器发送数据
  }
  socket.onmessage = function(e) {
    console.log(e)
    console.log(e.data) //接收服务器返回的数据
  }
</script>

@lxfriday
Copy link
Owner Author

lxfriday commented Jan 29, 2020

postMessage

postMessage 是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题:

postMessage() 方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递。

otherWindow.postMessage(message, targetOrigin, [transfer]);
  • message: 将要发送到其他 window 的数据;
  • targetOrigin: 通过窗口的 origin 属性来指定哪些窗口能接收到消息事件,其值可以是字符串 *(表示无限制)或者一个URI。在发送消息的时候,如果目标窗口的协议、主机地址或端口这三者的任意一项不匹配 targetOrigin 提供的值,那么消息就不会被发送;只有三者完全匹配,消息才会被发送;
  • transfer(可选):是一串和 message 同时传递的 Transferable 对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权;

http://localhost:53355/index.html 页面向 http://localhost:5000/b.html 传递“我爱你”,然后后者传回"我不爱你"。

index.html

<iframe src="http://localhost:5000/b.html" frameborder="0" id="frame" onload="load()"></iframe> //等它加载完触发一个事件
<script>
  function load() {
    let frame = document.getElementById('frame')
    frame.contentWindow.postMessage('我爱你', 'http://localhost:5000') //发送数据
    window.onmessage = function(e) {
      //接受返回数据
      console.log(e.data) // 我不爱你
    }
  }
</script>

b.html

<script>
  // b.html
  window.onmessage = function(e) {
    console.log(e)
    console.log(e.data) // 我爱你
    // e.origin => http://localhost:53355
    e.source.postMessage('我不爱你', e.origin)
  }
</script>

@lxfriday
Copy link
Owner Author

二次代理

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
browser browser
Projects
None yet
Development

No branches or pull requests

1 participant