跨域

1. 什么是跨域

不同源之间的数据访问称之为跨域
当跨域访问数据时, 如果没有特地做过处理, 则无法获取不同源的数据(通过标签引用 js, img, css 是可以的, 因为虽然我们通过标签获取到了这些资源, 但是我们仍然不知道对应文件中的内容是什么)

1.1 什么是源

所谓的源是指 协议+域名+端口号
如果两个 url 之间的 协议, 域名, 端口号 中有任意一个不同, 则称这两个 url 不同源(即使是两个不同的域名指向同一个 IP 地址也一样是不同源的)

1.2 为什么会有同源策略

1995年, 同源策略由 Netscape 公司引入浏览器. 最初, 它的含义是指: A网页设置的 Cookie, B网页不能打开, 除非这两个网页同源

如果没有同源策略的话, 那么在你访问B网站的同时, B网站也可以获取A网站的 Cookie

  • 而如果此时的 Cookie 包含了好友信息, 账户余额等隐私, 就容易导致隐私的泄露.
  • 而且Cookie 经常用来保存用户的登录状态, 如果B网站在用户登录A网站的同时获取了 Cookie, 那么B网站也能够登录A网站从而进行更多的操作

由此可见, 同源策略的存在是必要的. 否则 Cookie 能够共享, 那么上网将毫无安全性可言

1.3 同源策略的具体限制

随着时代的发展, 同源策略的限制也越来越严格

  • Cookie, LocalStorageIndexDB 无法获取
  • DOM 无法获得
  • AJAX 请求的数据无法获取

虽然同源策略是必要的, 但很多时候还是会带来很多不便. 尤其是现在 web 服务器和资源服务器基本都是不同源的情况下. 所以出现了一些即使不同源, 也能获取数据的方法

2. 如何跨域

2.1 使用代理服务器进行跨域

只有浏览器上应用了同源策略, 所以即使浏览器与服务器之间的不同源访问会有限制, 但是服务器与服务器之间的不同源访问就不会有限制.

故而, 我们令代理服务器与浏览器访问的页面同源, 从而使得浏览器可以访问代理服务器, 而代理服务器又能够访问目的服务器, 最终使得浏览器能够获取目的服务器的资源. 大致流程如下图所示:

这是相应的代理服务器的 node.js 代码

// server.js 代码
const express = require('express')
const request = require('request')
const app = express()

app.listen(2333, () => {
  console.log('listening 2333 port')
})


app.get('/resource', (req, res) => { 
  // 当收到来自浏览器的 /resourse 请求时, 将会请求指定url的资源, 并将其返还给浏览器
  let url = `https://www.bilibili.com`
  req.pipe(request(url)).pipe(res)
})

/*
- src
  - index.html
  - main.js
  - style.css
- server.js 
*/

app.use(express.static('./src/')) // 托管静态文件
// http://localhost:2333/index.html 相当于直接访问 index.html 页面

2.2 JSONP

回顾一下, 我们是可以通过 script 标签获取到对应的文件的, 主要的问题是我们无法通过这个方法获取到数据. 但如果我们获取的这个 js 文件可以返回数据呢

  • 在客户端设置: 可以在 script 标签中的 src 属性上添加一定的参数(比如说 callback=fn, 在这之前, 需要存在一个全局函数 fn)

  • 在服务端设置: 服务端可以根据相应的路径和参数, 获取对应的数据, 并返回 fn(returnData) 形式的数据. 而返回给浏览器的数据会被当做 js 代码执行, 从而达到跨域的目的(当然也可以返回其他形式的数据, 只要前后端协调好就行了)

  • 前端页面代码

<script>
  const getData = (data) => {
    // ... 处理数据
  }
</script>
<script src="xxx/resource?callback=getData" />
  • 后端处理代码
app.get('/resource', (req, res) => {
  let { callback } = req.query

  fs.readFile('./resource.js', (err, data) => { // 这里请求的文件的类型可以是 js, 也可以是 css, img 等等, 并不局限于某一类
    res.send(`${callback}(${data.toString()})`)
  })
})

优点:

  • 支持 IE
  • 可以跨域

缺点:

  • 只有 GET 方式, 并不支持 POST

2.3 CORS

当浏览器向不同源发送数据请求时, 浏览器会自动给请求头添加 Origin 字段, 其值为当前源. 如果返回的相应头中没有包含 Access-Control-Allow-Origin 字段, 或者该字段的值并不包含当前源, 则浏览器会阻止前端获取该数据

通过在后端设置响应头的一些属性, 从而使得跨域能够成功, 如在响应头设置

  • Access-Control-Allow-Origin: *: 该字段是必须的. * 表示允许来自任何源的资源请求. 同理, 只要相应的值设置为所允许访问的源, 则可实现跨域资源共享
  • Access-Control-Allow-Credentials: true: 该字段可选, 表示是否允许发送 cookie 给服务器, 如果不允许直接不添加该字段即可
  • Access-Control-Expose-Headers: 该字段可选. CORS请求时, xhr 对象的 getResponseHeader 方法只能拿到6个基本字段:Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers 里面指定 对应的属性即为浏览器额外能够获取的字段

代码示例:

  • 前端 js 请求代码
// 前端设置了一个 div 块级元素, 当点击该元素, 会向后端发送请求
const div = document.querySelector('.container')

div.addEventListener('click', (e) => {
  const service = axios.create({
    baseURL: 'http://127.0.0.1:2333',
    timeout: 12000,
    headers: {
      'Content-Type': 'application/json'
    },
    withCredentials: true // 发送的请求中携带 cookie
  })
  service.get('/cookie_test').then(result => {
    console.log(result)
  }).catch(e => {
    console.log(e)
  })
})
  • 后端 nodejs 相应代码
const express = require('express')
const request = require('request')
const fs = require('fs')
const app = express()

app.listen(2333, () => {
  console.log('listening 2333 port')
})

app.get('/resource', (req, res) => { // 通过 JSONP 获取数据
  let {callback} = req.query // 获取参数
  
  fs.readFile('./data.json', (err, data) => {
    res.setHeader('Set-Cookie', ['name=saber; HttpOnly', 'gender=female; HttpOnly', 'age=20; HttpOnly']) // 为浏览器设置 cookie 缓存(不允许前端访问)
    res.send(`${callback}(${data})`)
  })
})

app.get('/cookie_test', (req, res) => { // 前端的 AJAX 请求
  res.setHeader('Access-Control-Allow-Origin', 'http://127.0.0.1:5500') // 设置允许相应源的访问
  res.setHeader('Access-Control-Allow-Credentials', 'true') // 允许请求头中携带 cookie
  console.log(req.headers['cookie'])
  res.send('response')
})

  • access-control-allow-methods: GET, POST, OPTIONS: 表示请求方法只允许 GET , POSTOPTIONS 三种

CORS 跨域请求中, 浏览器发送请求时会在请求头中增加一个 origin 字段, 对应的值记录的是本次请求所来自的源

2.4 Nginx 反向代理

给出一个专门进行反向代理的服务器的 BASE_URL

  • 当用户需要获取前端页面时, 直接通过 BASE_URL/path/querystring 访问
  • 当前端页面需要与后端进行交互时, 可以通过 BASE_URL/specialTag/path/querystring 获取

Nginx 反向代理配置(/etc/nginx/nginx.conf)

worker_processes 2; // 指定工作线程的数量

http {

  server {
    listen 8081; // nginx 的监听端口号
    server_name localhost; // 服务器的域名或IP地址
    
    location / {
      // 如果访问的 URL 以 localhost/ 开头, 则访问该 location 块下的 proxy_pass 所对应的地址
      proxy_pass http://localhost:8080;
    }

    location /api {
      // 如果访问的 URL 以 localhost/api 开头, 则会访问该 location 块下的 proxy_pass 所指定的地址
      proxy_pass http://localhost:8000;

      // 将客户端请求中的 Host 头信息原封不动地传递给后端服务器
      proxy_set_header Host $host; 
    }
  }
}

关于 Nginx 的一些基本指令:

  • nginx: 启动 Nginx
  • nginx -s reload: 重启 Nginx
  • nginx -s stop: 停止 Nginx
  • nginx -t: 检查 Nginx 的配置文件是否正确
posted @ 2022-10-08 14:53  小阁下  阅读(73)  评论(0)    收藏  举报