写点什么

crossorigin 属性:为什么它是避免 tainted canvases 的关键?

作者:水鱼兄
  • 2022 年 10 月 10 日
    日本
  • 本文字数:3011 字

    阅读完需:约 10 分钟

本文为 HTML 标准解读系列文章,其他文章详见这里


你是否曾经遇到过这样的问题?


  • 使用canvasgetImageData() 方法,却报错:The canvas has been tainted by cross-origin data

  • 使用canvastoDataURL()方法,却报错:Tainted canvases may not be exported;

  • 使用document.styleSheets[0].cssRules访问样式表的 css 规则,却报错:Failed to read the 'cssRules' property from 'CSSStyleSheet': Cannot access rules


如果你上网找解决方案,会发现他们都跟一个 HTML 属性密切关联:crossorigincrossorigin是 CORS 在 HTML 标签上的应用。 很多的元素,包括imgvideoaudiolinkscript都有这个属性:


<img src="https://example.com/test.png" crossorigin="anonymous"></img><video src="https://example.com/test.mp4" crossorigin="anonymous"></video><link href="https://example.com/test.css" rel="stylesheet" crossorigin="anonymous" /><script src="https://example.com/test.js" crossorigin="anonymous"></script>
复制代码


幸运的是,在不同的元素上,crossorigin发挥作用的机理几乎是一样的,也就是说,只要明白它在一个元素上是如何发挥作用的,其他元素上的使用也就明白了。


本文我将会基于HTML标准2.5.4 CORS settings attribute,力求给你讲清楚:


  • crossorigin属性有哪些值?分别是干什么用的?

  • crossorigin是如何解决开篇的三个问题的?

  • 如何使用 js 的fetch API,来模拟不同crossorigin的值所发起的请求?

一些必要的前置知识

同源策略 是浏览器执行的一种安全机制,这个策略的一个重要方面就是限制使用 js 脚本访问不同源的资源。而 CORS(Cross-Origin Resource Sharing/跨域资源共享)就是同源策略开的一道口子,只要客户端与服务端(通过 HTTP 头)按照一定的规则进行协商,那么客户端就能正常使用 js 访问不同源的服务端资源。


我在这里不会讲他们是如何进行协商的,因为像这样的文章满天飞。我为你准备的是一段服务端代码,这段代码使用expressJS实现了服务端资源的跨域共享,以便你在阅读本文的时候可以方便的在本地进行测试:


const express = require('express')const app = express()
// 支持跨域的关键代码app.use(function(req, res, next) { res.header("Access-Control-Allow-Origin", "*"); // 第二个参数可以换成你的域名 res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); next();});
// host静态资源,如图片等等app.use(express.static('./public'))
app.get('/', function(req, res, next) { res.send('Hello World')});
// 测试跨域请求的时候记得使用其他的端口,比如http://localhost:8080app.listen(3000)
复制代码

请求的三种状态

我们都知道,使用imgvideoaudiolinkscript 等 HTML 元素可以请求外部的资源。


根据标准,这些元素所发出的请求可以根据是否支持 CORS 划分为 3 种状态:


No CORS

Anonymous

Use Credentials


  • 状态No CORS表示不支持 CORS 的请求。 在这种情况下,不管服务端对应的资源是否也支持 CORS, 虽然浏览器依然会拿到资源,这个资源也会正常加载和显示,但使用 js 访问其资源的能力会受到限制,具体表现为:

  • 对于imgaudiovideo元素:当这些元素的资源放在canvas上使用的时候,这些资源会被标记为tainted ,于是getImageData()toDataURL()方法就会被禁用;

  • 对于script元素:使用window.onerror访问其错误信息的的能力会被限制,比如在 Chrome 上只是统一报一个Script error.的错误文本;

  • 对于link元素:资源无法被 js 访问,如样式表中的cssRule

  • 状态Anonymous表示这是一个支持 CORS 的请求:

  • 如果服务端对应的资源不支持 CORS,就会报诸如Access to XXX from origin 'YYY' has been blocked by CORS policy的错误。

  • 如果服务端对应的资源支持 CORS,那么你就能拿到这个资源完整的访问权,也就是可以通过 js 脚本操作这个资源。

  • 状态Use Credentials表示这是一个支持 CORS 的请求,并且会在请求头中附带相关的身份凭证(如Cookie信息),其他地方与状态Anonymous一样。 Anonymous 在英文中是匿名的意思,而 Use Credentials 则表明你要像服务端表明你的身份。


注意:以上说的都是针对不同源的请求而言的。如果是同源的请求,资源不会受到同源策略的限制,默认就是有完整的访问权限。

使用crossorigin改变请求的状态

大部分情况下,No CORS 是 HTML 元素发起请求的默认状态。这也就是为什么在不做任何配置的情况下,在canvas使用上不同源的img会报tainted canvases的错误,访问不同源的cssRule会报错。只有一种例外情况,当linkscriptmodule一起使用的时候,默认请求状态会设置为Anonymous


而你可以通过crossorigin属性改变默认的请求状态


  • 如果crossorigin="anonymous",就会创建一个状态为Anonymous的 CORS 请求。

  • 如果crossorigin="use-credentials",就会创建一个状态为Use Credentials的 CORS 请求。

  • 其他任何属性值,包括空字符串"",都会创建一个状态为Anonymous的 CORS 请求。


于是,对于文章开头的几个问题,解法已经很清晰了:你必须使用crossorigin属性修改请求的状态,使其支持跨域。这样如果服务端也做了跨域支持,那么你就可以拿到资源完整的访问权。


也就是说,在服务端已经支持跨域的情况下,为了避免tainted canvases,你需要给img赋予一个crossorigin属性,如:


<img src="https://example.com/test.png" crossorigin="anonymous"></img>
复制代码


为了访问cssRule,你需要给link赋予一个crossorigin属性,如:


<link href="https://example.com/test.css" rel="stylesheet" crossorigin="anonymous" />
复制代码


值得一提的是,虽然objectembediframe等元素虽然也可以发起资源请求,但是他们都不支持crossorigin属性,所以他们的请求状态都是No CORS

使用 fetch api 模拟三种请求状态

fetch(resource,options)options参数有两个属性可以用来模拟不同状态:



于是,你可以在 JS 中使用fetch模拟 HTML 元素请求三种状态:


// No CORS requestfetch('https://example.com/test', {mode: 'no-cors'})// Anonymous requestfetch('https://example.com/test', {mode: 'cors'})// Use Credentials requestfetch('https://example.com/test', {mode: 'cors', credentials: 'include'})
复制代码


与 HTML 元素不同的是,一般情况下mode的默认值是cors,也就是说一般情况下 fetch 请求默认支持 CORS。


当你发送的是一个状态为No CORS 的跨域请求,你会发现返回的response里的body为 null,也就是说,虽然请求成功,但你仍然无法访问返回的资源。


当你发送的是一个状态为Anonymous 的跨域请求,你可以从开发者工具看到你的请求头并没有Cookies 这一项。


当你发送的是一个状态为Use Credentials的跨域请求,你可以从开发者工具看到你的请求头附上了Cookies

总结

我在HTML常见微语句提到过,有一种 HTML 属性叫「可枚举属性」。这种属性有有限的状态,并且定义了一个关键词/状态映射的集合,一个关键词对应一个状态。两个特殊的状态是missing value default 以及 invalid value default,分别表示属性没有出现时使用的状态以及属性值非法(不匹配任一关键词)时的使用的状态。与「可枚举属性」相反的是像class这种属性,有无限的状态。


crossorigin就是一种可枚举属性。下面我用一张表为你总结了这个属性:



发布于: 刚刚阅读数: 5
用户头像

水鱼兄

关注

前端开发 2019.01.15 加入

还未添加个人简介

评论

发布
暂无评论
crossorigin属性:为什么它是避免tainted canvases的关键?_水鱼兄_InfoQ写作社区