前端浏览器跨域相关,看了这篇面试时遇到跨域相关的问题如行云流水、游刃有余、泰然自若、轻松自在

1.同源策略

(1)何为同源策略 

为什么首先讲同源策略,是因为跨域产生的原因是违背了同源策略

参考MDN同源的解释

同源策略是一个重要的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。

(2)同源定义

如果两个 URL 的 protocolport (en-US) (如果有指定的话)和 host 都相同的话,则这两个 URL 是同源。这个方案也被称为“协议/主机/端口元组”,或者直接是 “元组”。(“元组” 是指一组项目构成的整体,双重/三重/四重/五重/等的通用形式)。

下表给出了与 URL http://store.company.com/dir/page.html 的源进行对比的示例:

URL结果原因
http://store.company.com/dir2/other.html同源只有路径不同
http://store.company.com/dir/inner/another.html同源只有路径不同
https://store.company.com/secure.html失败协议不同
http://store.company.com:81/dir/etc.html失败端口不同 ( http:// 默认端口是80)
http://news.company.com/dir/other.html失败主机不同

 2.如何解决跨域?

   跨域问题的出现,并不是服务端没有返回响应的数据,是因为浏览器做了限制

(1)JSONP

      jsonp原理

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

备注:

 JSONP和AJAX对比 JSONP和AJAX相同,都是客户端向服务端发送请求,从服务端获取数据的方式。但AJAX属于同源策略,JSONP属于非同源策略(跨域请求)

整个JSONP的实现过程

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

  • 声明一个回调函数,其函数名(如show)当做参数值,要传递给跨域请求数据的服务器,函数形参为要获取目标数据(服务器返回的data)
  • 创建一个 <script src=>标签 ,把那个跨域的API数据接口地址,赋值给script的src, 还要在这个地址中向服务器传递该函数名(可以通过问号传参?callback=show)。
  • 服务器接收到请求后,需要进行特殊的处理:把传递进来的函数名和它需要给你的数据拼接成一个字符串,例如:传递进去的函数名是show,它准备好的数据是 show('我喜欢乒乓球')。
  • 最后服务器把准备的数据通过HTTP协议返回给客户端,客户端再调用执行之前声明的回调函数(show),对返回的数据进行操作。

// 封装jsonp

jsonp.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=s, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script>
        function jsonp({ url, params, callback }) {
            return new Promise((resolve, reject) => {
                let script = document.createElement('script');
                params = JSON.parse(JSON.stringify(params));
                let arrs = [];
                for (let key in params) {
                    arrs.push(`${key}=${params[key]}`);
                }
                arrs.push(`callback=${callback}`);
                script.src = `${url}?${arrs.join('&')}`;
                document.body.appendChild(script);
                window[callback] = function (data) {
                    resolve(data);
                    document.body.removeChild(script);
                }
            })
        }
        // 前端调用
        jsonp({
            url: 'http://localhost:3000/hobby',
            params: {
                wd: 'I Love play pingpang'
            },
            callback: 'show'
        }).then(data => {
            console.log(data)
        })


    </script>
</body>
</html>

//后端代码 利用express起一个后端服务

server.js
// 这里用到了 express
var express = require('express');
var router = express.Router();
var app = express();
router.get('/hobby',function(req,res,next) {
 //要响应回去的数据
  let data = {
    username : 'zhangsan',
    password : 123456
  }

  let {wd , callback} = req.query;
  console.log(wd);
  console.log(callback);
  // 调用回调函数 , 并响应
  res.end(`${callback}(${JSON.stringify(data)})`);
})
app.use(router);
app.listen(3000);

利用node server 启动服务

(2)CORS(Cross-origin resource sharing)

CORS需要浏览器和后端同时支持。IE8和 IE9需要通过 XDomainRequest来实现
浏览器会自动进行 CORS通信,实现CORS通信的关键是后端。只要后端实现了CORS,实现了跨域。
服务端设置 Access-Control-Allow-Origin 就可以开启CORS。该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。
虽然设置CORS和前端没有什么关系,但是通过这种方式解决跨域问题的话,会在发送请求时出现两种情况,分别为简单请求复杂请求

    简单请求

只要同时满足以下两个条件,就属于简单请求

条件1 : 使用下列方法之一:

  • GET
  • HEAD
  • POST

条件2 :Content-Type 的值仅限于下列三者之一 :

  • text/plain
  • multipart/form-data
  • application/x-www-form-urlencoded 请求中的任意 XMLHttpRequestUpload 对象均没有注册任何事件监听器;

   复杂请求

不符合以上条件的请求就是复杂请求,复杂请求的CORS请求,在正式通信之前,会增加一次HTTP查询,称为"预检"请求,该请求是option方法 , 通过该请求来知道服务端是否允许跨域请求。
当用 PUT 向后台请求时, 属于复杂请求,后台需如下配置:

// 允许哪个方法访问我
res.setHeader('Access-Control-Allow-Methods', 'PUT')
// 预检的存活时间
res.setHeader('Access-Control-Max-Age', 6)
// OPTIONS请求不做任何处理
if (req.method === 'OPTIONS') {
  res.end() 
}
// 定义后台返回的内容
app.put('/getData', function(req, res) {
  console.log(req.headers)
  res.end('我喜欢打乒乓球')
})

// 前端代码

cors.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script>
        let xhr = new XMLHttpRequest();
        document.cookie = 'name=hw';
        xhr.withCredentials = true; //前端设置是否带 cookie
        xhr.open('PUT','http://localhost:4000/getData',true);
        xhr.setRequestHeader('name','hw');
        xhr.onreadystatechange = function() {
            if(xhr.readyState === 4) {
                if(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
                    console.log(JSON.parse(xhr.response));
                    console.log(xhr.getResponseHeader('name'))
                }
            }
        }
        xhr.send();
    </script>
</body>
</html>

 // 给前端起一个端口服务

let express = require('express');
let app = express();
app.use(express.static(__dirname))
app.listen(3000)

// 后端代码

cors.js
// 后端代码
let express = require('express')
let app = express()
let whitList = ['http://127.0.0.1:3000'] //设置白名单
app.use(function(req, res, next) {
  let origin = req.headers.origin
  if (whitList.includes(origin)) {
    // 设置哪个源可以访问我
    res.setHeader('Access-Control-Allow-Origin', origin)
    // 允许携带哪个头访问我
    res.setHeader('Access-Control-Allow-Headers', 'name')
    // 允许哪个方法访问我
    res.setHeader('Access-Control-Allow-Methods', 'PUT')
    // 允许携带cookie
    res.setHeader('Access-Control-Allow-Credentials', true)
    // 预检的存活时间
    res.setHeader('Access-Control-Max-Age', 6)
    // 允许返回的头
    res.setHeader('Access-Control-Expose-Headers', 'name')
    if (req.method === 'OPTIONS') {
      res.end() // OPTIONS请求不做任何处理
    }
  }
  next()
})
app.put('/getData', function(req, res) {
  let data = {
      username : 'zhangsan',
      password : 123456
  }
  console.log(req.headers)
  res.setHeader('name', 'jw') //返回一个响应头,后台需设置
  res.end(JSON.stringify(data))
})
app.get('/getData', function(req, res) {
  console.log(req.headers)
  res.end('hhhhaha')
})
app.listen(4000)

//这个时候在浏览球器地址栏中,可以输入127.0.0.1:3000/cors.html

 

可以看到有两个getData请求

   PUT请求

response Headers部分

Request Headers部分

 如此就实现了一个从3000端口到4000端口的跨域请求访问

    OPTIONS请求

(3) nginx解决跨域

在众多的解决跨域方式中,都不可避免的都需要服务端进行支持,使用Nginx可以纯前端解决请求跨域问题

server
{
    listen 3002;
    server_name localhost;
    location /ok {
        proxy_pass http://localhost:3000;

        #   指定允许跨域的方法,*代表所有
        add_header Access-Control-Allow-Methods *;

        #   预检命令的缓存,如果不缓存每次会发送两次请求
        add_header Access-Control-Max-Age 3600;
        #   带cookie请求需要加上这个字段,并设置为true
        add_header Access-Control-Allow-Credentials true;

        #   表示允许这个域跨域调用(客户端发送请求的域名和端口) 
        #   $http_origin动态获取请求客户端请求的域   不用*的原因是带cookie的请求不支持*号
        add_header Access-Control-Allow-Origin $http_origin;

        #   表示请求头的字段 动态获取
        add_header Access-Control-Allow-Headers 
        $http_access_control_request_headers;

        #   OPTIONS预检命令,预检命令通过时才发送请求
        #   检查请求的类型是不是预检命令
        if ($request_method = OPTIONS){
            return 200;
        }
    }
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值