我在搭建这个博客系统的时候,选择了Gitalk作为评论系统,看中其利用GitHub Issues来管理评论的机制,且需要使用GitHub账号登录,有一定门槛,方便管理。
搭建过程需要在GitHub创建一个公共仓库,以及一个OAuth Apps,参考相关资料按步骤操作即可,这里不做赘述。要注意的是Authorization callback URL
这项要填写博客首页地址,我是把http强制定向到了https,这里完整地址也必须填写https,否则无法正常使用。
按步骤搭建完成之后,每篇博文下面会出现一个评论框,当使用管理员账号首次登录后会自动创建和文章标题对应的Issues主题。
问题描述
到目前为止似乎都没什么问题,然而记住我的结论,只要你使用了Gitalk且未做其它修改,迟早会在登录或者评论时遇到403 Forbidden的报错。如果你运气不好,在首次授权登录之后,就会出现该提示。
来看一看事故现场发生了什么:
分析
当看到cors-anywhere.herokuapp.com
这个域名时,我已经猜到问题背后的原因了,剩下的就是找资料去验证和解决问题。
看到「cors」这个词首先想到的便是「跨域」,cors-anywhere这个取名更是表达了通过「反向代理」无视浏览器的「同源策略」。关于跨域,我后面打算写一篇专题深入讲解。这里先简单说明一下,浏览器出于安全考虑,会采用「同源策略」,即页面内部访问与本站来源不同的地址时有一定的限制。https://github.com
与我的站点域名https://liuyueyang.top
显然不同源。绕开该限制一般有三种方法:
- 使用JSONP
- 使用CORS协议
- 使用反向代理
这里不打算解释每一种方法,但希望能够对解决跨域问题有个基础的认识——想要做到跨域,就是须要浏览器和服务器一起约定,允许浏览器打破默认的同源策略,回到Gitalk使用的方法,通过将请求发送到cors-anywhere.herokuapp.com
这个站点中转,再添加CORS协议的相关报头,实际上是结合了第二种和第三种方法。
分析至此,这个403报错也就不难理解了,所有使用Gitalk插件默认配置的用户,都在使用这个域名去做代理,那GitHub检测到同一IP在一段时间内产生大量请求,cors-anywhere.herokuapp.com
这个中转服务自然就被「限流」了,GitHub背后的机制也不难猜测,比如使用的是「令牌桶」的方式,一段时间请求量超过设定阈值,就禁止访问多久,这也是Gitalk间歇性抽风的根本原因。
有了以上这些分析,接下来就很好办了,先验证然后解决方案自然就有了——自己搭建反向代理。
验证
在Gitalk项目下,找到了一条配置项proxy
的说明,正好是我们需要的:
我们将porxy配置改为自己搭建的服务,就不用再担心受到GitHub的限流措施了。
值得一提的是文档里特别给了个说明链接「为什么要这样?」,这个链接里描述了跨域的问题:在GitHub的v3版API里,除了https://github.com/login/oauth/access_token
这个接口以外的其它接口都有加上CORS允许跨域访问。
评论作者提出这是一个Bug,他认为这个接口禁止跨域访问并不能增加安全性。给出的理由是可以通过一个第三方反向代理绕开这个限制。
我对这个观点存疑,这并不是一个Bug,通过第三方反向代理绕开这个限制是要付出代价的,我们已经体会到了,即被「访问控制」间歇性403。允许跨域本来就是浏览器和服务器之间的约定,而我理解的OAuth2授权流程是要有服务器参与的。接过微信登录或者其它第三方登录SDK的同学,应该对OAuth2有印象,其中Secret Key是不应该放在客户端的。所以Gitalk插件完全依赖前端走通整个登录授权,本身是否破坏了安全性还有待进一步考量。
解决
问题验证了之后,解决方案也很清楚了,只要自己搭建一个反向代理服务,再把插件配置中的proxy改为自己的代理服务地址即可完美解决。这里记录一下关键步骤,有一些细节是须要注意的。
搭建反向代理服务
反向代理可以做到同源,用Nginx简单配置一下即可,但这里考虑到插件js里可能有特定的格式,为了和原来默认配置保持一致:
https://cors-anywhere.herokuapp.com/https://github.com/login/oauth/access_token
那么这个反向代理服务,并不是直接同源,而是结合方法二和方法三,将CORS相关报头加到代理的响应中即可。Nginx应该也能做到,不过得查一下配置的细节。我这里没有使用Nginx,直接找到了默认配置中用到的那个小服务源码,按说明部署。
这里是源码地址。
操作步骤:
1 | git clone https://github.com/Rob--W/cors-anywhere.git |
这里注意最好设置一下白名单HOST,非白名单的域名访问会直接拒绝,没有这个配置,这个服务就是个公共服务了。另外我这里的端口不是443也不是80,而是自定义的8090,前面Nginx做了个代理,这里是有原因的,https站点页面是不能请求http协议的,所以这个代理服务也得使用https协议。
修改proxy配置
代理服务部署好了之后,最后修改一下Gitalk的proxy配置,我的Hexo使用的是hexo-theme-melody
这个主题,原本是没有proxy这个配置字段的,得自己加。找到主题目录下的文件node_modules/hexo-theme-melody/layout/includes/comments/gitalk.pug
添加一行proxy: '!{theme.gitalk.proxy}',
再找到_config.melody.yml
下的gitalk配置,添加自己的proxy路径即可。
如果使用的是其它主题,没有支持proxy配置,需要自己去对应的模版文件中找到并添加。
其它的问题
至此我的Gitalk评论已经能够正常投入使用,再也没有遇到过403的问题。使用过程中,还发现Gitalk的一个Bug,我的标题中有一个中文逗号「,」,在重定向后被改成了英文逗号「,」,当然就报404 Not Found了,试了手动把地址栏上的英文逗号替换成中文,可以走通后续的登录流程。
我还没细究是什么样的代码导致这个有趣的Bug,用Nginx做了一个临时的代理解决了这个问题。
心得
- 遇到页面问题第一时间打开Chrom浏览器Commond+Option+J查看报错信息
- 关于Issue中提到GitHub接口设计Bug的评论,正如我所料并非如此,我在官网找到了答案:The「implicit grant type」is not supported
- 对OAuth 2.0的认证流程还可以有进一步思考,出于什么目的要这样设计
- 平时的技术积淀在关键时候对问题的快速定位有大作用,在我实际工作中遇到过几次了
- 遇到紧急、难定位的Bug可以先换个思路取巧绕开,比如我的页面链接改成了短链,已经不会再出现中文逗号了