crossorigin 属性:为什么它是避免 tainted canvases 的关键?
本文为 HTML 标准解读系列文章,其他文章详见这里。
你是否曾经遇到过这样的问题?
使用
canvas的getImageData()方法,却报错:The canvas has been tainted by cross-origin data;使用
canvas的toDataURL()方法,却报错:Tainted canvases may not be exported;使用
document.styleSheets[0].cssRules访问样式表的 css 规则,却报错:Failed to read the 'cssRules' property from 'CSSStyleSheet': Cannot access rules。
如果你上网找解决方案,会发现他们都跟一个 HTML 属性密切关联:crossorigin。crossorigin是 CORS 在 HTML 标签上的应用。 很多的元素,包括img、video、audio、link、script都有这个属性:
幸运的是,在不同的元素上,crossorigin发挥作用的机理几乎是一样的,也就是说,只要明白它在一个元素上是如何发挥作用的,其他元素上的使用也就明白了。
本文我将会基于HTML标准2.5.4 CORS settings attribute,力求给你讲清楚:
crossorigin属性有哪些值?分别是干什么用的?crossorigin是如何解决开篇的三个问题的?如何使用 js 的
fetch API,来模拟不同crossorigin的值所发起的请求?
一些必要的前置知识
同源策略 是浏览器执行的一种安全机制,这个策略的一个重要方面就是限制使用 js 脚本访问不同源的资源。而 CORS(Cross-Origin Resource Sharing/跨域资源共享)就是同源策略开的一道口子,只要客户端与服务端(通过 HTTP 头)按照一定的规则进行协商,那么客户端就能正常使用 js 访问不同源的服务端资源。
我在这里不会讲他们是如何进行协商的,因为像这样的文章满天飞。我为你准备的是一段服务端代码,这段代码使用expressJS实现了服务端资源的跨域共享,以便你在阅读本文的时候可以方便的在本地进行测试:
请求的三种状态
我们都知道,使用img、video、audio、link、script 等 HTML 元素可以请求外部的资源。
根据标准,这些元素所发出的请求可以根据是否支持 CORS 划分为 3 种状态:
No CORS
Anonymous
Use Credentials
状态
No CORS表示不支持 CORS 的请求。 在这种情况下,不管服务端对应的资源是否也支持 CORS, 虽然浏览器依然会拿到资源,这个资源也会正常加载和显示,但使用 js 访问其资源的能力会受到限制,具体表现为:对于
img、audio、video元素:当这些元素的资源放在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会报错。只有一种例外情况,当link和script与module一起使用的时候,默认请求状态会设置为Anonymous。
而你可以通过crossorigin属性改变默认的请求状态:
如果
crossorigin="anonymous",就会创建一个状态为Anonymous的 CORS 请求。如果
crossorigin="use-credentials",就会创建一个状态为Use Credentials的 CORS 请求。其他任何属性值,包括空字符串
"",都会创建一个状态为Anonymous的 CORS 请求。
于是,对于文章开头的几个问题,解法已经很清晰了:你必须使用crossorigin属性修改请求的状态,使其支持跨域。这样如果服务端也做了跨域支持,那么你就可以拿到资源完整的访问权。
也就是说,在服务端已经支持跨域的情况下,为了避免tainted canvases,你需要给img赋予一个crossorigin属性,如:
为了访问cssRule,你需要给link赋予一个crossorigin属性,如:
值得一提的是,虽然object、embed、iframe等元素虽然也可以发起资源请求,但是他们都不支持crossorigin属性,所以他们的请求状态都是No CORS。
使用 fetch api 模拟三种请求状态
fetch(resource,options)的options参数有两个属性可以用来模拟不同状态:
mode属性,可以用来设置是否支持 CORS,具体的值在这里列出了。credentials属性,可以控制是否发送身份认证信息,具体的值在这里列出了。
于是,你可以在 JS 中使用fetch模拟 HTML 元素请求三种状态:
与 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就是一种可枚举属性。下面我用一张表为你总结了这个属性:
版权声明: 本文为 InfoQ 作者【水鱼兄】的原创文章。
原文链接:【http://xie.infoq.cn/article/730a345c35c051aa8363bf73a】。文章转载请联系作者。










评论