CORS详解

  CORS(Cross-Origin Resource Sharing, 跨源资源共享)是W3C出的一个标准,其思想是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功,仍是应该失败。所以,要想实现CORS进行跨域,须要服务器进行一些设置,同时前端也须要作一些配置和分析。本文简单的对服务端的配置和前端的一些设置进行分析。前端

服务端的配置

本文服务端的代码采用的是node,使用koa,koa-router和koa2-cors。node

配置的主要代码以下:git

复制代码
app.use(cors({
    origin: function(ctx) {
        const regexp = new RegExp('/CORS');
        const regexpWith = new RegExp('/CORSWith');
        if (regexpWith.test(ctx.url)) {
            return `http://${packageData.url}:7000`;
        } else if(regexp.test(ctx.url)) {
            return '*'
        } else if(~String(ctx.url).indexOf('/imgs/')) {
            return `http://${packageData.url}:7000`;
        }
        return false;
    },
    exposeHeaders: ['WWW-Authenticate', 'Server-Authorization', 'Date'],
    maxAge: 100,
    credentials: true,
    allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
    allowHeaders: ['Content-Type', 'Authorization', 'Accept', 'X-Custom-Header', 'anonymous'],
}));
复制代码

主要是使用了koa2-cors进行了配置,下面对配置项进行简单介绍:github

origin:

设置Access-Control-Allow-Origin,源码以下:canvas

  Access-Control-Allow-Origin表示容许跨域的域名,能够设置为*也能够设置为具体的域,其中,*表示所有,即全部的域名下的请求都容许,但设置为*后,全部的请求都不会携带附带身份凭证(好比cookie);设置为具体的域则表示只有该域下的请求容许,别的域下的请求不被容许,设置为具体的域是请求中携带身份凭证的基础。跨域

exposeHeaders:

设置Access-Control-Expose-Headers,源码以下:浏览器

  Access-Control-Expose-Headers表示容许脚本访问的返回头,请求成功后,脚本能够在XMLHttpRequest中访问这些头的信息,如在上面配置的代码中设置了Date,在浏览器端就能够经过js拿到服务器的时间了,下面是经过XMLHttpRequest实例的getResponseHeader()获取服务器时间的代码:安全

复制代码
let xhr = new XMLHttpRequest();
    xhr.open('GET', `http://${url}/CORS/userInfo/12`, true);
    xhr.onload = function() {
        if(xhr.readyState == 4) {
            try {
                // 获取服务器时间
                console.log(xhr.getResponseHeader('Date'));
            } catch(ex) {
                new Error(ex);
            }
        }
    };
    xhr.send();
复制代码

maxAge:

设置Access-Control-Max-Age,源码以下:服务器

Access-Control-Max-Age用来指定本次预检请求的有效期,单位为秒,预检请求将在下面具体介绍,如今先过。

credentials:

设置Access-Control-Allow-Credentials,源码以下:

  Access-Control-Allow-Credentials为服务端标识浏览器请求CORS时是否能够附带身份凭证,对于附带身份凭证的请求,服务器不得设置 Access-Control-Allow-Origin 的值为“*”。

allowMethods:

设置Access-Control-Allow-Methods,源码以下:

Access-Control-Allow-Methods用来设置检查网络请求的方式,如GET、POST等。

allowHeaders:

设置Access-Control-Request-Headers,源码以下:

Access-Control-Request-Headers用来将实际请求所携带的首部字段告诉服务器,在这里能够自定义头部信息,用来对浏览器的非简单请求进行预检判断。

前端的配置

前端的配置主要经过简单请求和非简单请求,携带身份凭证,canvas中画图使用的跨域图片三部分进行讲解

简单请求和非简单请求

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

简单请求:

简单请求知足如下条件:

1. 使用下列方法之一:

  • GET
  • HEAD
  • POST

2.HTTP的头信息不超出如下几种字段:

  • Accept
  • Accept-Language
  • Content-Language
  • Content-Type:值属于下列之一:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

简单请求如图所示:

浏览器与服务器之间请求只进行了一次。

非简单请求:

不知足简单请求条件的请求则要先进行预检请求,即便用OPTIONS方法发起一个预检请求到服务器,已获知服务器是否容许该实际请求。

非简单请求以下所示:

下面是PUT请求第一次返回的结果:

  经过PUT请求结果能够看出,当检测到PUT请求为非简单请求时,浏览器便会发送一个预检请求,目的是询问,自定义头部X-Custom-Header的PUT请求是否被容许,浏览器返回了全部能够请求的方法和自定义的头部(把全部能够的返回是为了不屡次预检请求),这时候预检请求成功了,便会发送真正的PUT请求。

关于预检请求,须要注意一下两点:

  1. 预检请求对js来讲是透明的,js获取不到预检请求的任何信息。
  2. 预检请求并非每次请求都发生,服务端设置的Access-Control-Max-Age头部指定了预检请求的有效期,在有效期内的非简单请求不须要再次发生预检请求。

携带身份凭证

大部分的请求是须要用户携带着用户信息的,好比在一个登陆的系统中,用户会携带着相应的cookie或token,但CORS跨域默认是不带身份凭证的。

若是须要附带身份凭证,在发送请求时,经过将withCredentials属性设置为true,能够指定某个请求能够发送凭据。

下面提供针对XMLHttpRequest附带身份凭证的兼容性写法:

复制代码
function createCORSRequest(method, url) {
    var xhr = new XMLHttpRequest();
    xhr.onload = function() {
        if(xhr.readyState == 4) {
            try {
                if((xhr.status >= 200 && xhr.status < 300) || xhr == 304) {
                    console.log(xhr.response);
                } else {
                    console.log('Request was unsuccessful: ' + xhr.status);
                }
            } catch(ex) {
                new Error(ex);
            }
        }
    };
    if('withCredentials' in xhr) {
        xhr.open(method,url, true);
    } else if(typeof XDomainRequest != 'undefined') {
        xhr = new XDomainRequest();
        xhr.open(method, url);
    } else {
        xhr = null;
    }
    return xhr;
}
复制代码

附带身份凭证对服务端有两个要求:

  1. 服务端的Access-Control-Allow-Origin头部不能设置为*
  2. 服务端的Access-Control-Allow-Credentials头部设置为true

canvas中画图使用的跨域图片

  尽管不经过 CORS 就能够在画布中使用图片,可是这会污染画布。一旦画布被污染,你就没法读取其数据。例如,你不能再使用画布的 toBlob(), toDataURL() 或 getImageData() 方法,调用它们会抛出安全错误。

这种机制能够避免未经许可拉取远程网站信息而致使的用户隐私泄露。

对跨域图片进行修改的话,img须要添加crossOrigin属性,代码以下:

复制代码
function drawCanvas(id, drawId, url) {
        let canvas = document.getElementById(id);
        let ctx = canvas.getContext('2d');

        var img = document.createElement('img');
        img.src = url;
        img.crossOrigin = 'anonymous';
        // 必须等到图片彻底加载后才能对其进行操做。浏览器一般会在页面脚本执行的同时异步加载图片。若是试图在图片未彻底加载以前就将其呈现到canvas上,那么canvas将不会显示任何图片
        if (img.complete) {
            canvas.width = img.width;
            canvas.height = img.height;
            ctx.drawImage(img, 0, 0 );
        } else {
            img.onload = function () {
                canvas.width = img.width;
                canvas.height = img.height;
                ctx.drawImage(img, 0, 0 );
                const src = canvas.toDataURL(" image/jpeg", 0.3);
                $(drawId).attr('src', src);
            };
        }
    }
复制代码

服务器针对跨域修改的图片也要作两点限制:

  1. 服务端的Access-Control-Allow-Origin头部不能设置为*
  2. 服务端的Access-Control-Request-Headers头部添加一个自定义头部,其值为img的crossOrigin的值

常见的图片跨域修改的错误有两种:

  • Uncaught SecurityError: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.
  • Image from origin 'http://127.0.0.1:4000' has been blocked from loading by Cross-Origin Resource Sharing policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://127.0.0.1:7000' is therefore not allowed access.

总结

至此,关于CORS的一些知识点已经分享完了,总结以下:

CORS是浏览器和服务器配合完成的跨域请求,我的认为,主要是服务端配置好后,浏览器根据服务端配置的自定义头部和提供的能够进行的CORS的方法来进行跨域操做。

基于以上总结,提供测试Demo:

  https://github.com/weiruifeng/fetchTest

参考资料:

  HTTP访问控制(CORS): https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS

 

若有问题,欢迎你们指正。

相关文章
相关标签/搜索