在配置hao主题时,发现【文章】设置中的【动态主色】没有生效。

进入开发者模式,查看报错信息如下:

from origin 'http://***.***' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

查询相关资料,发现是浏览器的跨域访问限制造成的。所以,首先要弄明白什么是跨域限制。

CORS跨域资源共享

同源策略

要了解什么是跨域访问,先要了解什么是“同源”。同源策略(Same Origin Policy)是浏览器的一种基本安全策略,对不同源的文档、脚本的访问做了限制。同源策略判断同源的依据是:主机相同(域名相同)、端口相同、协议相同。也就是说,除了同域名不同路径的访问,都会被浏览器视作非同源。

同源策略只在浏览器端生效,可以有效地防护跨站伪造请求(CSRF)攻击。

CSRF攻击利用受害者浏览器中的认证凭据(如Cookie、Session等),向受信任的网站发送非预期的HTTP请求。由于这些请求附带了受害者的认证信息,因此,Web服务器可能会误认为这些请求是合法用户的行为,从而执行相应的操作,如转账、修改密码等。

//来自 zhouzhou的奇妙编程

跨源资源共享

W3C推荐使用跨源资源共享(Cross-origin Resource Sharing)来处理跨域资源请求。使用CORS时,异步请求会被分解为简单请求和非简单请求:

简单请求

请求方法是:HEAD、GET、POST

HTTP头信息是:Accept、Accept-Language、Content-Language

Content-type是:application/x-www-from-urlencoded、multipart/from-data、text/plain

对于简单请求,浏览器直接发出CORS请求,在头信息中添加一个Origin字段,记录请求来源(协议+域名+端口)。服务器根据来源检查是否是允许的源。若是:返回一个HTTP回应,并加上Access-Control-Allow-Origin等字段、该资源能被浏览器正确取得。若不是:返回一个普通HTTP回应,由于缺少Access-Control-Allow-Origin字段,会抛出错误。

非简单请求

请求方法是:PUT、DELETE等

Content-type是:application/json等

对于复杂请求,浏览器在发出CORS请求前,浏览器会要求服务器检查当前域名是否在许可名单内、是否被允许使用当前字段。后续操作与简单请求类似。

使用CORS

服务端(COS服务)配置

接下来着手配置CORS跨域资源共享的访问,首先要解决服务器的认证问题。打开腾讯云COS控制台,进入使用的存储桶,在其【安全管理】-【跨域访问CORS设置】中添加一条新的访问规则:

其中,需要填写的字段(带*为必填):

  • 来源 Origin:允许跨域请求的来源,添加自己所使用的域名、 IP 地址。

  • 操作 Methods: GET、PUT、POST、DELETE、HEAD中的一个或多个。

  • Allow-Headers:在发送 OPTIONS 请求时告知服务端,接下来的请求可以使用哪些自定义的 HTTP 请求头部,可以同时指定多个 Headers,每行只能填写一个。Header 容易遗漏,没有特殊需求的情况下,建议设置为 *,表示允许所有。Access-Control-Request-Headers 中指定的每个 Header,都必须在 Allowed-Header 中有对应项。

  • Expose-Headers:Expose-Header 里返回的是 COS 的常用 Header,详情请参见 公共请求头部。不允许使用通配符,大小写不敏感,支持多行且每行只能填写一个。

  • 超时 Max-Age:设置 OPTIONS 请求得到结果的有效期(秒)。

  • 返回 Vary: Origin:如果浏览器同时存在 CORS 和非 CORS 请求,请启用该选项,否则会出现跨域问题。

这里特别要注意Expose-Headers的设置,如果允许的公共请求头不对应、缺少,都会导致后续无法访问。

客户端(Halo服务器)配置

由于我们这次只解决文章页获取文章封面的问题,所以只需要修改文章页的模板即可。打开halo/themes/theme-hao/template/目录下的posts.html,找到:

<div class="coverdiv loaded" id="coverdiv">
    <img alt="cover" class="nolazyload" id="post-cover" 
        th:src="${#strings.isEmpty(post.spec.cover) ? theme.config.layout.postRandomImg : post.spec.cover}">
</div>

<img>标签内新增一个属性,crossorigin="anonymous",结果如下:

<div class="coverdiv loaded" id="coverdiv">
    <img alt="cover" class="nolazyload" id="post-cover" crossorigin="anonymous"
        th:src="${#strings.isEmpty(post.spec.cover) ? theme.config.layout.postRandomImg : post.spec.cover}">
</div>

这样,双端配置就都已经完成,别忘了重新启动halo服务来刷新:docker-compose restart

小问题

刷新页面没有生效

重启Halo后兴冲冲的刷新文章页,居然完全没有变化!这是由于我们之前的浏览器已经有了缓存记录,不会主动刷新。我们可以在开发者模式下删除当前网站的本地记录来重置,现在,【动态主色】已经能正确捕捉封面的颜色啦。

一个偷懒的解法...

既然无法获取跨域资源的图片,那不如直接用Halo原生的本地附件策略,【动态主色】也能正确捕捉封面的主色。(变成同源资源请求,当然不会被跨域策略拦截)