使用 AWS 和 S3 存储桶对静态站点进行基本用户身份验证

Basic User Authentication for Static Site using AWS & S3 Bucket

我希望将基本用户身份验证添加到我将在 AWS 上建立的静态站点,以便只有那些拥有我将提供给这些用户的正确用户名和密码的人才能访问该站点。我找到了 s3auth,它似​​乎正是我要找的东西,但是,我想知道我是否需要以某种方式为 index.html 之外的页面设置授权。例如,我有 3 个页面 - 索引、关于和 contact.html,没有为 about.html 设置身份验证是什么阻止了个人通过 www.mywebsite.com/about 直接访问该站点。 html?我更想寻求澄清或任何人可以提供的任何资源来解释这一点!

感谢您的帮助!

这是 Lambda@Edge 的完美用法。

因为您在 S3 上托管您的静态网站,您可以使用 AWS 的内容分发网络 CloudFront 轻松且非常经济地(几分钱)向您的网站添加一些非常棒的功能,从而为您的用户提供网站服务。您可以了解如何使用 CloudFront 在 S3 上托管您的站点(包括 100% 免费 SSL)here

在部署您的 CloudFront 分配时,您将有一些时间来设置您将用于执行基本用户身份验证的 Lambda。如果这是您第一次创建 Lambda 或创建 Lambda 以供使用 @Edge,这个过程会感觉非常复杂,但如果您按照我下面的分步说明进行操作,您将进行无限的无服务器基本身份验证不到 10 分钟即可扩展。我将为此使用 us-east-1,重要的是要知道,如果您使用的是 Lambda@Edge,您应该在 us-east-1 中编写您的函数,并且当它们与您的 CloudFront 分配关联时,它们将自动在全球范围内复制。让我们开始吧...

  1. 前往 AWS 控制台中的 Lambda,然后单击“Create Function
  2. 从头开始创建您的 Lambda 并为其命名
  3. 将运行时设置为 Node.js 8.10
  4. 通过selecting“选择或创建执行角色”为您的 Lambda 授予一些权限
  5. 给角色起个名字
  6. 来自策略模板select“基本 Lambda@Edge 权限(用于 CloudFront 触发器)”
  7. 点击“创建函数”
  8. 创建 Lambda 后,将以下代码粘贴到 Function Code 部分的 index.js 文件中 - 您可以通过更改 authUser 来更新要使用的用户名和密码和 authPass 变量:
'use strict';
exports.handler = (event, context, callback) => {

    // Get request and request headers
    const request = event.Records[0].cf.request;
    const headers = request.headers;

    // Configure authentication
    const authUser = 'user';
    const authPass = 'pass';

    // Construct the Basic Auth string
    const authString = 'Basic ' + new Buffer(authUser + ':' + authPass).toString('base64');

    // Require Basic authentication
    if (typeof headers.authorization == 'undefined' || headers.authorization[0].value != authString) {
        const body = 'Unauthorized';
        const response = {
            status: '401',
            statusDescription: 'Unauthorized',
            body: body,
            headers: {
                'www-authenticate': [{key: 'WWW-Authenticate', value:'Basic'}]
            },
        };
        callback(null, response);
    }

    // Continue request processing if authentication passed
    callback(null, request);
};

  1. 点击右上角的“保存”。
  2. 现在您的 Lambda 已保存,可以附加到您的 CloudFront 分配。在上方菜单中,select 操作 -> 部署到 Lambda@Edge。
  3. 在出现的模式中 select 您之前从下拉菜单中创建的 CloudFront 分配,将缓存行为保留为 *,对于 CloudFront 事件,将其更改为“查看器请求”,最后 select/tick“包含正文”。 Select/tick 确认部署到 Lambda@Edge 并单击“部署”。

现在你等着吧。跨所有区域和边缘站点复制您的 Lambda@Edge 需要几分钟 (15-20)。转到 CloudFront 以监控函数的部署。当您的 CloudFront 分发状态显示为“已部署”时,您的 Lambda@Edge 函数就可以使用了。

部署 Lambda@edge 很难通过控制台复制。所以我创建了 CDK Stack,您只需添加自己的凭据和域名并部署即可。

https://github.com/apoorvmote/cdk-examples/tree/master/password-protect-s3-static-site

我用Node12.x

测试了以下功能
exports.handler = async (event, context, callback) => {

const request = event.Records[0].cf.request

const headers = request.headers

const user = 'my-username'

const password = 'my-password'

const authString = 'Basic ' + Buffer.from(user + ':' + password).toString('base64')

if (typeof headers.authorization === 'undefined' || headers.authorization[0].value !== authString) {

    const response = {
        status: '401',
        statusDescription: 'Unauthorized',
        body: 'Unauthorized',
        headers: {
            'www-authenticate': [{key: 'WWW-Authenticate', value:'Basic'}]
        }
    }

    callback(null, response)
}

callback(null, request)
}

到目前为止,我更喜欢 CloudFront 函数也可以做到这一点,因为它进一步降低了复杂性(Lambda 已经不太复杂)。这是我写的关于我刚刚所做的事情...

基本上需要完成 3 件事:

  1. 创建 CloudFront 函数以将基本身份验证添加到请求中。
  2. 在几个地方正确配置 CloudFront 分发的来源。
  3. 激活 CloudFront 功能。 就是这样,没有特别的花里胡哨。这是我所做的:

首先,转到 CloudFront,然后单击左侧的 Functions,使用您选择的名称创建一个新函数(不需要区域等),然后添加以下内容作为函数的代码:

function handler(event) {

    var user = "myuser";
    var pass = "mypassword";

    function encodeToBase64(str) {
        var chars =
        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
        for (
            // initialize result and counter
            var block, charCode, idx = 0, map = chars, output = "";
            // if the next str index does not exist:
            //   change the mapping table to "="
            //   check if d has no fractional digits
            str.charAt(idx | 0) || ((map = "="), idx % 1);
            // "8 - idx % 1 * 8" generates the sequence 2, 4, 6, 8
            output += map.charAt(63 & (block >> (8 - (idx % 1) * 8)))
        ) {
        charCode = str.charCodeAt((idx += 3 / 4));
        if (charCode > 0xff) {
            throw new InvalidCharacterError("'btoa' failed: The string to be encoded contains characters outside of the Latin1 range."
        );
        }
        block = (block << 8) | charCode;
      }
      return output;
    }


    var requiredBasicAuth = "Basic " + encodeToBase64(`${user}:${pass}`);
    var match = false;
    if (event.request.headers.authorization) {
        if (event.request.headers.authorization.value === requiredBasicAuth) {
            match = true;
        }
    }

    if (!match) {
      return {
        statusCode: 401,
        statusDescription: "Unauthorized",
        headers: {
          "www-authenticate": { value: "Basic" },
        },
      };
    } 

    return event.request;
}

然后您可以直接在 UI 上进行测试,假设它可以正常工作,假设您已经自定义了用户名和密码,请发布函数。

请注意,我在 Internet 上找到了上述函数的各个部分,因此这不是我自己的代码(除了将它们拼凑在一起)。我希望我仍然能找到资料来源,这样我就可以在这里引用它们,但我再也找不到了。不过要归功于创作者! :-)

接下来,打开您的 CloudFront 分配并执行以下操作:

  1. 确保源中的 S3 存储桶被配置为 REST 端点而不是网站端点,即它必须以 .s3.amazonaws.com 结尾并且没有单词 website在主机名中。

  2. 同样在 Origin 设置中,在“S3 存储桶访问”下,select“是使用 OAI(存储桶只能访问 CloudFront)”。在下面的设置中单击“创建 OAI”以创建一个新的 OAI(除非您已有一个并且知道自己在做什么)。 select“是的,更新存储桶策略”以允许 AWS 向您的 OAI 添加必要的权限。

  3. 最后,打开您的 CloudFront 分发行为并滚动到底部。在“函数关联”下,对于“查看者请求”select“CloudFront 函数”和select 您新创建的 CloudFront 函数。保存您的更改。

应该就是这样。运气好的话只需要几分钟(我知道实际上更多),尤其是在所有设置完成后不会增加额外的复杂性。