必备-7.同源及跨域

必备-7.同源及跨域

同源与跨域

同源与跨域?

  • 客户端给服务器端发送请求

  • 同源:客户端的http协议、域名、端口号与服务器的三者完全相同

  • 跨域:只要客户端的http协议、域名、端口号其中一个与服务器的这三者不同

真实项目开发中,基本都是跨域的还是同源的?

  • 开发环境:我们在本地开发项目,我们需要调用后台的接口,此时前后端代码基本不在一起,这样的访问是跨域的
  • 生产环境:
    • 如果我们把前后端代码放在同一个服务器下,这样数据的请求是同源的【很少】
    • 一般我们都是分服务器部署[web服务器、数据服务器。。。]这样还是跨域【常用】
    • 我们还会在自己的项目中,调用第三方平台的接口获取数据,这样的还是跨域请求!!

为什么会产生跨域?

  • 服务器资源分离:WEB服务器、数据服务器、
  • 云信息共享:第三方API接口
  • 有助于分离开发:开发跨域、部署同源

解决跨域方法

如何解决跨域请求?

按常用优先级:

  • 第一种:PROXY跨域代理&nginx反向代理【目前最常用
  • 第二种:CORS跨域资源共享
  • 第三种:JSONP

其余方案:postMessage、document.domain+iframe、window.name、location.hash…【不常用了

  • JSONP:

    • ==原理:==利用ajax/fetch存在域的限制(浏览器的安全策略),但是script/link/img...不存在域的限制;
      • 而JSONP的原理就是基于<script src="接口请求地址">向服务器发送请求,绕过域的限制,实现跨域数据访问。
    • ==缺点:==JSONP只能发送GET请求,因为script请求都是get请求,不支持POST请求
    • 具体操作:
      • 第一步:客户端先创建一个"全局函数" func
      • 第二步:基于script的src发送请求,同时把全局函数func传递给服务器:src="http://api.qq.com/?callback=func"
      • 第三步:服务器接受请求,并且获取传递进来的函数,基本callback接收
      • 第四步:准备客户端需要的数据,准备好之后,给客户端返回这样格式的内容:“func(准备好的数据)”
      • 第五步:客户端获取服务器返回的信息,拿到信息之后,发现是吧第一步准备的全局函数执行,把数据作为实参传递给函数,这样我们在全局函数中就可以获取服务器准备的数据。
    • ==特点:==JSONP需要服务器端的支持:服务器端存在对应的callback函数
  • CORS:

    • ==原理:==默认情况下,因为浏览器的安全策略,服务器端不允许非同源客户端请求的;如果想跨域访问,只需要服务器允许即可,所以cors跨域资源共享,就是服务器设置允许访问的源。

    • 具体操作:服务器端设置Access-Control-Allow-Origin:

      • res.header("Access-Control-Allow-Origin","访问源");//res是响应头
        
      res.header("Access-Control-Allow-Credentials",true);//如果设置所有源访问,需要设置这个为true
      
      res.header("Access-Control-Allow-Headers","Content-Type",...);//允许客户端发送的请求头参数
      res.header("Access-Control-Allow-Methods","POST,GET,HEADR...");//允许客户端发送的方法
      
      
      • “*”:允许所有源访问,【不安全,不允许携带资源凭证】

      • “http://127.0.0.1:5500”:只允许某个源

      • 但是无法直接实现允许多个源,

    • 基于CROS给服务器发送请求,会发两次请求,第一次发一个OPTIONS方式的请求,查看是否能够请求成功,成功后再发送第二次获取数据的请求。

  • PROXY跨域代理&nginx反向代理:代理服务器

    • 原理:服务器与服务器之间不存在跨域问题,客户端启动代理服务器(webpack/node):
      • 帮我们预览客户端项目:基于代理服务器可以找到同项目下的页面服务环境,作用如同open-with-liveserver
      • 同时实现数据请求的代理
      • 此时客户端和代理服务器是同源的
      • 服务器和服务器之间不存在跨域的限制
    • 过程:
      • 接收客户端发送的同源请求,紧接着看到真正的服务器上获取真实的数据【服务器和服务器之间不存在跨域问题】,然后把从真实服务器获取的数据返回客户端。
    • ==核心:==在于设置一个代理服务器[开发环境下我们可以基于webpack-dev-server/ndoe]设置,生产环境下我们一般是基于nginx实现代理服务器的
    • 如何实现代理服务器:
      • 可以基于webpack-dev-server创建【通过http-proxy】
    • image-20211123182536173

手撕JSONP

  • 我们最终需要拼装出来一个<script src="http://api.qq.com/?callback=func></script>"这样的子串
  • jsonp内部实现要点:
    • 第一步:获取客户传入的url和options:
      • url必须是字符串类型:否则报错:if (typeof url !== "string") throw new TypeError('url is not a string~');
      • 如果options没传:则默认为空对象if (!isPlainObject(options)) options = {};
    • 第二步:将默认值options对象与用户传入的对象合并:Object.assign({params:null,jsonpName: 'callback'},params)
    • 第三步:返回一个Promise实例:Promise实例内部有如下操作
      • 1、创建一个随机的全局函数window[name]=function(){}
        • 全局函数是我们从服务器端获取到数据后会自动执行的函数:func***("服务器返回的数据")
        • 所以能执行到这个函数体内则表示数据获取成功,在函数体内返回正确结果就可以resolve("服务器返回的数据")
        • 也可以对性能做一些优化:删除用完的script标签,删除window[name]生成的随机私有属性name
      • 2、拼接URL字符串:
        • 我们最终需要拼装出来一个<script src="http://api.qq.com/?callback=func></script>"这样的子串
        • options中有参数params并且参数是个对象时,将params拼接到url后面:url += ${url.includes('?') ? '&' : '?'}${params};
        • 最后需要拼上我们请求的jsonpName:url += ${url.includes('?') ? '&' : '?'}${jsonpName}=${name};
      • 3.基于script发送请求:
        • 创建script标签:script = document.createElement('script');
        • 将url添加到src中:script.src = url;
        • 使script编程异步的:script.async = true;
        • 将节点放到最后:document.body.appendChild(script);
        • 如果报错就执行reject:script.onerror = () => reject();
    • 第四步:暴露API
      • 如果是客户端,暴露给window:if (typeof window !== 'undefined') window.jsonp = jsonp;
      • 如果是服务器端,暴露给module.export:if (typeof module === 'object' && typeof module.exports === 'object') module.exports = jsonp;
/* 
  封装一个jsonp方法「基于promise管理」,执行这个方法可以发送jsonp请求 
    jsonp([url],[options])
      options配置项
      + params:null/对象 问号参数信息
      + jsonpName:'callback' 基于哪个字段把全局函数名传递给服务器
      + ...
*/
        (function () {
            //判断是不是普通标准对象
            const isPlainObject = function isPlainObject(obj) {
                let proto, Ctor;
                if (!obj || Object.prototype.toString.call(obj) !== "[object Object]") return false;
                proto = Object.getPrototypeOf(obj);
                if (!proto) return true;
                Ctor = proto.hasOwnProperty('constructor') && proto.constructor;
                return typeof Ctor === "function" && Ctor === Object;
            };
            //拼接字符串方法
            function toencodedstring(obj){
                let keys=Reflect.ownKeys(obj);
                let str="";
                keys.forEach(item=>{
                    str+=`&${item}=${obj[item]}`;
                    
                })
                nstr=str.slice(1);
                return nstr;
            }

            // jsonP方法
            const jsonp=function jsonp(url,options){
                // 写一个默认的options
                let defaultOptions={
                    params:null,
                    jsonName:"callback"
                }
                // 第一步:验证传入的两个参数的格式是否符合规范
                if(typeof url!=="string") throw new TypeError("url must be a string");
                if(!isPlainObject(options)) options={};
                // 第二步:将默认参数对象与用户传入的参数对象合并
               let {params,jsonName}= Object.assign(defaultOptions,options);
                // 第三步:返回一个Promise对象
                return new Promise((resolve,reject)=>{
                    // 1、创建一个随机数,可以加时间戳或者随机数
                    let name=`jsonp${+new Date()}`,script;
                    // 2、将随机数作为window的一个私有函数
                    window[name]=function(da){
                        // 执行到这一步就说明已经从服务器拿回了数据,直接输出数据即可
                        resolve(da);
                        // 优化:
                        delete window[name];
                       if(script) document.body.removeChild(script);
                    }
                    // 拼接字符串
                    if(params&&isPlainObject(params)){
                        url+=`${url.includes('?')?"&":"?"}${toencodedstring(params)}`;
                    }
                    url+=`${url.includes('?')?"&":"?"}${jsonName}=${name}`;
                    // 将字符串传给script标签
                    script=document.createElement("script");
                    script.src=url;
                    script.onerror=()=>reject();
                    script.async=true;
                    document.body.appendChild(script);

                })      
            }
            //暴露API          
            if(typeof window!=='undefined') window["jsonp"]=jsonp;
            if(typeof module=='object'&& typeof module.exports=='object') module.exports=jsonp;
        })()
        jsonp("https://www.baidu.com/sugrec",{
            params:{
                prod:"pc",
                wd:"史佳豪"
            },
            jsonName:"cb"
        }).then(resolved=>{
            console.log(resolved);
        })

跨域身份验证方案

跨域身份验证的三种方案?

  • 方式一:登录态校验

    • 第一步:客户端发送POST请求做登录态校验,服务器端检验用户名密码是否准确,如果正确则返回成功
    • 第二步:客户端收到成功响应后,在本地手动设置cookie,存储登录成功,例如:isLogin=true
    • 第三步:之后再访问页面,检测本地是否有isLogin的cookie信息,有表示已经登陆过了,无需再验证
    • 缺陷:
      • 不准确,不安全,cookie可以随便修改
  • 方式二:客户端设置cookie:会话存储

    • 第一步:客户端发送POST请求登录,
    • 第二步:服务器做账号密码验证,如果正确在响应头中设置session信息:Set-Cookie:connect.sid
    • 第三步:客户端收到响应头中有Set-Cookie:connect.sid,会在缓存中存储一份
    • 第四步:之后再访问页面,客户端会携带connect.sid请求校验,服务器会查找自己的session来看是否匹配成功
    • 缺陷:
      • session中有信息则是登录,否则没登录
      • session容易丢失【过去时间、服务器重启,session信息就没有了】
      • 和cookie相关,它也容易丢失
      • 不利于服务器的分布式
  • 方式三:使用Token[常用]

    • 第一步:客户端向服务器发送登录请求,服务器进行校验:
      • 账号密码如果正确:基于JWT算法,生成一个Token信息【含有登录者、过期日期等待信息】,“服务器端不需要存储这个Token”
    • 第二步:服务器手动把Token给客户端
    • 第三步:客户端获取Token后,存储到本地:可以用localStorage、sessionStroage、vuex/redux...
    • 第四步:再次访问首页时,需要手动将缓存的Token发给服务器端==【基于axios的请拦截器】==
    • 第五步:服务器拿到Token后,再基于JWT反反解析,验证它的有效性
    • 优点:实时校验,要稳定以及有利于服务器分布
    • 缺点:性能略低[忽略],需要自己写代码。

image-20211124182736317

后台开发权限管理

  • 权限信息:
    • 获取权限信息后,我们一般存储在数据库中,这样以后各个模块都可以获取权限信息
      • 如果不存储:以后需要权限信息,重新发送请求即可
      • 如果存储:不建议使用本地存储(缓存:cookie、localeStorage、sessionStroage),因为不安全,别人可能会修改权限标识[vuex/redux]
  • 权限校验三类方法:
    • 1、可以让用户看到,但是操作的时候要进行权限校验【无权则提示
    • 2、没有权限就渲染【为了安全保证,直接不渲染结构,而不是控制display:none
    • 3、同样的页面,根据用户权限不同,获取的信息不同,例如:进入客户列表,有的人看到的是全公司的用户,有的人只能看到自己的用户
  • 结论:不管如何,只要是客户端处理权限,就不一定100%安全,所以需要服务器进行权限的二次校验【尤其是重点信息

image-20211127163305801

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值