本文记录阿猪在使用WordPress REST API为跨站应用做身份认证时遇到的一例CORS问题。经过一番折腾,最后发现问题出在使用的REST API Authentication插件在处理HTTP请求时与WordPress原生的REST API有重复。

  在WordPress的原生REST API中,支持Cookie Authentication和Basic Authentication两种认证方式。然而目前比较主流的认证方式是Json Web Token(简称JWT)和OAuth2.0,需要借助第三方插件实现。以JWT为例,基本逻辑是使用POST方式向生成token的endpoint发送用户名和密码,如果用户名、密码正确,则返回的Headers中会包含token信息,后续使用REST API时,只要每次在Header中包含Authorization: Bearer <json-web-token>就可以了。

  阿猪最先使用的是miniOrange出品的WordPress REST API Authentication插件。在WordPress中和在本地测试请求生成token都正常,但是在跨站应用中测试时,浏览器的控制台却报错:Access to fetch at 'https://yfzhu.cn/wp-json/api/v1/token-validate' from origin 'https://xxx.yfzhu.cn' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

  CORS policy是什么鬼?出于安全考虑,浏览器默认会执行同源策略(Same-origin policy),只有同源(URI、主机名、端口相同)的两个页面才被允许相互访问资源。但是在WEB应用中,跨站访问资源是很常见的需求,这就涉及到“跨源资源分享”(Cross-Origin Resource Sharing)的问题。

  经过百度和ChatGPT的一通搜索,大部分的回答是说Nginx的配置不正确,没有开启Access-Control-Allow-Origin这个Header。按照ChatGPT的指导在Nginx的配置文件中添加了相应的Header信息的配置后,可以正常生成token了。以下代码供参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
http {
...
server {
...
location / {
...
add_header 'Access-Control-Allow-Origin' 'https://xxx.yfzhu.cn';//将url替换为*表示允许所有访问源
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
...
}
...
}
...
}

  这里顺便说一下,如果你和阿猪一样使用的是OneinStack的LNMP+WordPress全家桶,那么Nginx的配置文件位置是在/usr/local/nginx/conf/vhost/wordpress.conf,而不是在/usr/local/nginx/conf/nginx.conf

  但是还没高兴多久,很快又产生了新的问题。阿猪使用生成的jwt通过GET方式向WordPress REST API请求登录用户信息的时候,浏览器的控制台出现如下错误信息:
Access to fetch at 'https://yfzhu.cn/wp-json/wp/v2/users/me' from origin 'https://xxx.yfzhu.cn' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains multiple values 'https://xxx.yfzhu.cn, https://xxx.yfzhu.cn', but only one is allowed. Have the server send the header with a valid value, or, if an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

  再次经过百度和ChatGPT的一通搜索,大部分的回答是说Nginx的配置不正确,有重复的`Access-Control-Allow-Origin`配置。但是阿猪翻遍了Nginx目录下的所有conf文件,也没有找到重复的`Access-Control-Allow-Origin`配置。如果把仅有的`Access-Control-Allow-Origin`配置删除,则又会回到最初的` No 'Access-Control-Allow-Origin' header is present on the requested resource`问题。阿猪又尝试了各种搜索到的办法,但是仍然没有解决。

  后来阿猪想,会不会是WordPress的程序中也有响应Headers信息的语句,从而导致了这个问题。毕竟先前验证token的时候使用的也是GET方法,响应是正常的,没有道理同样是GET请求,服务器区别对待。再仔细对比两个endpoint的路径,一个是第三方插件生成的/wp-json/api/v1/token-validate,一个是WordPress原生的/wp-json/wp/v2/users/me。可能是第三方插件和WordPress都在同一个HTTP请求中输出了headers信息,从而导致了重复。搜索了一下WordPress的代码,果然有几个文件中包含Access-Control-Allow-Origin

  阿猪首先尝试更换JWT插件,毕竟如果换个插件能解决,就可以省去改代码的麻烦了。这次阿猪运气好,换了Enrique Chavez的JWT Authentication for WP-API插件后,问题解决了。如果你也正在被这个问题困扰,希望这篇文章能帮助到你。