Nova Kwok's Awesome Blog

用 Lambda@Edge + CloudFront + S3 实现静态网站上的 OAuth 认证操作记录

这其实是一篇操作指南,对于我这种完全不会写代码的人来说都能搞定,整个思路是 @handlerww 大佬建议的,想想还是蛮有意思的,遂记录一下,分享出来,希望可以给有类似需求的同学一些思路。

Google Docs

我不喜欢 Google Docs,因为它对于我而言除了有团队协作上的便利以外,似乎没有任何的好处,Google Docs 对于我而言只是一个草稿本,用来记录一些简单的设计思路和个人的想法,完成记录之后点右上角的「Share」分享给需要的人(们),经过一堆 Zoom 会议或者 Telegram 语音,在文档上出现了一堆 Comment 和修改之后,它,作为一个链接地址长相类似 https://docs.google.com/document/d/1x7Y8pN8DxxxxxxxxxxTB0c/edit 的文档来说,很快它就会成为 Drive 中的一些没法清理的「小麻烦」。

慢慢地,我们的 Google Drive 就会变成这样:

在成熟的公司里,用不着去写具体的业务代码,从事的都是脑力活动。需求越来越多,大佬们就越来越多,为了获得绩效,他们得找到可以改进的「xx设计体系方案」。方案是无限的,因为一切都可以「重构」。Google Drive 里那一堆堆发黑的 Google Docs,比墓地还要凄惨,即便到了年终总结,也不会有人去看一眼。技术就在大量的「xx设计体系方案」,「xx的建设」中渐渐消亡。相信我,在你原来的公司内的一个高可用 Jira 部署的 deployment.yml,就远远胜过我们在各种例会上的「xx 看法」。

——《不能承受的文档之轻》

引子

对于静态的网站来说,一般我们认为是没法加入验证的操作的(htpasswd 这种 pre-shared key 模式不算哈),但是为了打破上文中 Google Docs 满天飞,天天 Play with styling 的困局,我们肯定需要一套完整的,Markdown-based 的文本库作为 Wiki 的存在,一来可以沉淀一些技术上的设计思路,二来也可以为后续找文档和打开文档时省点心思和内存。

我们直接开始整个流程吧,由于是纯静态的文件(假设这里是用的 mdbook,域名为 goprivate.nova.moe),这里考虑到 CI/CD,假设我们使用了 GitHub Actions + AWS S3(桶名:goprivate.nova.moe) 的方式来持续构建和部署我们的 Wiki,并且配置了 Cloudfront 做了 CDN 加速(大陆以外的访问),接下来,我们考虑配置 Google 登录来让我们的内部同学看到对应的文章。

Google API

首先我们需要到 https://console.developers.google.com/ 创建一个类型为 Web application 的 OAuth Client,参考:

设置 Authorized redirect URIshttps://goprivate.nova.moe/_callback,并记录下 Client IDClient secret

Lambda@Edge

Clone https://github.com/Widen/cloudfront-auth 这个仓库,在确认电脑上已经安装了 node 之后在仓库内执行 ./build.sh,选择 Google 验证,并输入上文中记录下来的 Client IDClient secret,在验证部分,由于这里的需求是希望接受所有来自 @nova.moe 邮箱的登录,所以选择 Hosted Domain ,并输入 nova.moe,完成之后会在 distribution/Google 目录下发现一个 Google.zip 的文件,留着备用。

然后在 AWS 的 us-east-1 区域(这点很重要,目前 Lambda@Edge 似乎只有这个区可用)创建一个 Lambda 并选择 Use a blueprint,并搜索 cloudfront-http-redirect,如下图:

选择 Create a new role with basic Lambda permissions 并点确认。

保存并部署后,通过 Upload From 上传之前的 Google.zip 并点 Versions 创建一个 Version (名称可以随意写一个)即可,此时需要注意上面的 ARN,末尾会加入一个 :3 之类的,变为类似 arn:aws:lambda:us-east-1:31245698298:function:goprivate-nova-moe-auth:3,我们需要复制这个 ARN。

Config

最后,我们到达 Cloudfront 的设置界面点 Behaviour,并设置 Viewer Request 通过 Lambda@Edge ,后面写上上文的 ARN,类似如下,并保存:

此时,等待 10 分钟左右等 Cloudfront 生效,生效后我们就可以发现我们的静态页面在访问的时候已经会跳转到 Google Auth 进行验证了。

Website index.html

但是这样有个问题,由于所有的请求都走了 Lambda@Edge,在 S3 上设置的 Static Website Hosting 的 index page 似乎是无效的(导致访问 / 目录会直接出现一个 Access Denied),所以需要对我们用到的 Lambda@Edge 进行一个 Patch,具体来说就是在代码的 function mainProcess(event, context, callback) 内部加入:

  if (request.uri.endsWith('/')) {
    var requestUrl = request.uri;

    // Match url ending with '/' and replace with /index.html
    var redirectUrl = requestUrl.replace(/\/$/, '\/index.html');

    // Replace the received URI with the URI that includes the index page
    request.uri = redirectUrl;
  }

相关部分代码看上去如下:

...
function mainProcess(event, context, callback) {

  // Get request, request headers, and querystring dictionary
  const request = event.Records[0].cf.request;
  const headers = request.headers;
  const queryDict = qs.parse(request.querystring);
  if (event.Records[0].cf.config.hasOwnProperty('test')) {
    config.AUTH_REQUEST.redirect_uri = event.Records[0].cf.config.test + config.CALLBACK_PATH;
    config.TOKEN_REQUEST.redirect_uri = event.Records[0].cf.config.test + config.CALLBACK_PATH;
  }
  
  if (request.uri.endsWith('/')) {
    var requestUrl = request.uri;

    // Match url ending with '/' and replace with /index.html
    var redirectUrl = requestUrl.replace(/\/$/, '\/index.html');

    // Replace the received URI with the URI that includes the index page
    request.uri = redirectUrl;
  }

...

点 Deploy 并 Publish 新版本后按照上文中的 CloudFront 设置新的版本的 ARN 即可.

以上。

Reference

  1. Manual Deployment
  2. Authorization@Edge – How to Use Lambda@Edge and JSON Web Tokens to Enhance Web Application Security
  3. Serving Default index.html Files with AWS S3 and CloudFront
  4. Rewrite default urls to ./index.html #61

#Chinese