You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何配置AWS S3+CloudFront实现JWT授权的私有文件访问(C#)

解决方案:CloudFront + 私有S3桶的JWT授权访问方案

一、核心架构思路

用CloudFront作为图片访问的唯一入口,S3保持完全私有。通过CloudFront的CloudFront Functions(轻量低成本)或Lambda@Edge(复杂逻辑场景)在请求阶段验证JWT令牌:

  • 有效令牌:允许请求穿透到S3返回图片
  • 无效/无令牌:返回自定义未授权XML错误(匹配你提供的示例格式)

二、AWS服务配置步骤

1. S3私有桶配置(保持原有私有设置)

  • 确认存储桶已启用「阻止所有公共访问」,无需修改
  • 创建Origin Access Control (OAC):
    1. 进入CloudFront控制台,新建OAC,类型选「S3」,勾选「签名请求」
    2. 关联到你的S3桶,更新桶策略,仅允许该OAC对应的CloudFront分发访问:
      {
        "Version": "2008-10-17",
        "Id": "PolicyForCloudFrontPrivateContent",
        "Statement": [
          {
            "Sid": "AllowCloudFrontAccess",
            "Effect": "Allow",
            "Principal": {
              "Service": "cloudfront.amazonaws.com"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::YOUR_BUCKET_NAME/*",
            "Condition": {
              "StringEquals": {
                "AWS:SourceArn": "arn:aws:cloudfront::YOUR_AWS_ACCOUNT_ID:distribution/YOUR_CLOUDFRONT_DIST_ID"
              }
            }
          }
        ]
      }
      
  • 上传图片到S3,保持对象默认私有权限

2. CloudFront分发配置

  • 新建分发,源选择你的S3桶,关联上述OAC
  • 行为设置:
    • 路径模式设为/*
    • 缓存策略选「Managed-CachingDisabled」(避免缓存未授权请求)
    • 绑定JWT验证逻辑到「Viewer Request」阶段

3. JWT验证逻辑配置

场景1:轻量验证用CloudFront Functions

创建CloudFront Function,代码如下(直接匹配你要的错误格式):

function handler(event) {
    const request = event.request;
    const authHeader = request.headers.authorization;

    // 从Authorization头提取Bearer令牌(localStorage的令牌需前端通过该头携带)
    if (!authHeader || !authHeader.value.startsWith('Bearer ')) {
        return generateUnauthorizedResponse();
    }

    const token = authHeader.value.split(' ')[1];
    try {
        // 解析JWT payload(生产环境需验证签名,这里示例仅检查过期时间)
        const payload = JSON.parse(atob(token.split('.')[1]));
        if (payload.exp * 1000 < Date.now()) {
            return generateUnauthorizedResponse();
        }
        // 可额外验证受众、发行方等字段
        if (payload.aud !== "YOUR_APP_AUDIENCE") {
            return generateUnauthorizedResponse();
        }
    } catch (e) {
        return generateUnauthorizedResponse();
    }

    return request;
}

function generateUnauthorizedResponse() {
    const requestId = Math.random().toString(36).substring(2, 18);
    return {
        statusCode: 403,
        statusDescription: 'Forbidden',
        headers: {
            'content-type': { value: 'application/xml' }
        },
        body: {
            encoding: 'text',
            data: `<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>ResourceNotFound</Code>
<Message>The specified resource does not exist. RequestId:${requestId} Time:${new Date().toISOString()}</Message>
</Error>`
        }
    };
}

将该函数关联到CloudFront分发的「Viewer Request」阶段。

场景2:复杂验证用Lambda@Edge

如果需要调用外部授权服务(如Cognito、Auth0)验证JWT,用Lambda@Edge(Node.js示例):

const jwt = require('jsonwebtoken');
const PUBLIC_KEY = 'YOUR_JWT_PUBLIC_KEY';

exports.handler = async (event) => {
    const request = event.Records[0].cf.request;
    const authHeader = request.headers.authorization;

    if (!authHeader || !authHeader[0].value.startsWith('Bearer ')) {
        return generateUnauthorizedResponse();
    }

    const token = authHeader[0].value.split(' ')[1];
    try {
        jwt.verify(token, PUBLIC_KEY, { audience: "YOUR_APP_AUDIENCE" });
    } catch (err) {
        return generateUnauthorizedResponse();
    }

    return request;
};

function generateUnauthorizedResponse() {
    const requestId = Math.random().toString(36).substring(2, 18);
    return {
        status: '403',
        statusDescription: 'Forbidden',
        headers: {
            'content-type': [{ key: 'Content-Type', value: 'application/xml' }]
        },
        body: `<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>ResourceNotFound</Code>
<Message>The specified resource does not exist. RequestId:${requestId} Time:${new Date().toISOString()}</Message>
</Error>`
    };
}

4. 自定义错误页面(可选)

在CloudFront分发的「错误页面」设置中,添加403错误规则:

  • HTTP错误代码:403
  • 自定义响应代码:403
  • 直接用上述函数返回的XML内容,无需额外指定页面路径

三、C#实现代码

1. 生成JWT令牌(后端)

使用System.IdentityModel.Tokens.Jwt库生成合规令牌:

using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.IdentityModel.Tokens;

public string GenerateValidJwt(string userId)
{
    var secretKey = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes("YOUR_JWT_SECRET_KEY"));
    var signingCreds = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256);

    var claims = new[]
    {
        new Claim(ClaimTypes.NameIdentifier, userId),
        new Claim(JwtRegisteredClaimNames.Aud, "YOUR_APP_AUDIENCE"),
        new Claim(JwtRegisteredClaimNames.Exp, DateTimeOffset.UtcNow.AddHours(1).ToUnixTimeSeconds().ToString())
    };

    var token = new JwtSecurityToken(
        issuer: "YOUR_APP_ISSUER",
        audience: "YOUR_APP_AUDIENCE",
        claims: claims,
        expires: DateTime.UtcNow.AddHours(1),
        signingCredentials: signingCreds);

    return new JwtSecurityTokenHandler().WriteToken(token);
}

2. 上传图片到S3(后端)

使用AWS SDK for .NET上传私有对象:

using Amazon.S3;
using Amazon.S3.Transfer;

public async Task UploadImageToPrivateBucket(string bucketName, string localFilePath, string s3ObjectKey)
{
    // 优先用IAM角色或环境变量配置凭证,避免硬编码
    var s3Client = new AmazonS3Client();
    var transferUtility = new TransferUtility(s3Client);
    
    await transferUtility.UploadAsync(localFilePath, bucketName, s3ObjectKey);
}

3. 前端携带令牌请求图片

前端从localStorage/Cookie取令牌,通过Authorization头发送请求:

// 配合C#后端生成的令牌
const jwtToken = localStorage.getItem("userJwt");
const imageUrl = "https://YOUR_CLOUDFRONT_DOMAIN/images/photo.jpg";

fetch(imageUrl, {
    headers: {
        "Authorization": `Bearer ${jwtToken}`
    }
})
.then(res => res.blob())
.then(blob => {
    const img = document.createElement("img");
    img.src = URL.createObjectURL(blob);
    document.body.appendChild(img);
})
.catch(err => console.log("未授权访问:", err));

四、测试验证

  1. 用有效JWT请求CloudFront图片URL,应正常返回图片
  2. 不携带令牌/用过期令牌请求,应返回你指定格式的XML错误

内容的提问来源于stack exchange,提问作者Ming Hieu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 07:20:58