Rust 客户端安全上传下载微软 Azure Blob:rusty-cat SAS 预签名实战

适合读者:想让客户端上传/下载 Azure Blob 文件,但不想把 Storage Account Key 暴露到客户端的 Rust 开发者。

1. SAS 是什么?为什么比直连更适合客户端?

Azure SAS 的全称是 Shared Access Signature,可以理解为一段带权限范围和过期时间的临时访问授权。

直连模式是:

text 复制代码
客户端或服务进程持有 Account Key
    -> 本地给 Azure 请求签名

SAS 模式是:

text 复制代码
后端持有 Account Key 或其他云端授权能力
    -> 后端生成短期 SAS URL
        -> 客户端只拿 SAS URL 上传/下载

SAS 更适合客户端,因为:

  • 客户端不需要知道 Storage Account Key。
  • SAS 可以限制到某个 container/blob。
  • SAS 可以限制权限,例如只读、只写、只创建。
  • SAS 可以设置过期时间。
  • 泄露后的影响范围比长期 Account Key 小得多。

本文不会展示真实 SAS token。所有 URL 中的 sigsv 等参数都用 REDACTED 表示。

2. rusty-cat 在 Azure SAS 模式里做什么?

Azure SAS 模式中,rusty-cat 不负责生成 SAS,也不接触 Account Key。它负责:

  • 根据后端给出的 SAS URL 上传 block。
  • 生成或使用 block id。
  • 通过 Put Block 上传分片。
  • 通过 Put Block List 提交最终 Blob。
  • 使用 SAS URL 做 Range 下载。
  • 根据 total_size 跳过或执行 HEAD
  • 分发进度回调。
  • 处理分片重试、暂停、恢复、取消。

核心类型包括:

  • PresignedMultipartUpload
  • PresignedMultipartUploadPlan
  • PresignedRangeDownload
  • PresignedRangeDownloadPlan
  • rusty_cat::azure_blob_sas helper 函数

3. 依赖与 feature

toml 复制代码
[dependencies]
rusty-cat = { version = "0.2.2", features = ["azure-blob-sas"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

版本提示:正式接入前,请以 crates.iodocs.rs 的当前版本为准:

azure-blob-sas 会启用 provider-neutral presigned 能力和 Azure SAS helper。

4. 后端需要返回什么?

上传时,后端至少要返回:

字段 说明
blob_sas_url 目标 Blob 的 SAS URL。需要允许写入 block 和提交 block list。
total_size 文件总大小。
chunk_size 分片大小。
expires_at SAS 过期时间。
business_upload_id 可选,业务上传 ID,方便恢复和审计。

下载时,后端至少要返回:

字段 说明
download_sas_url 允许读取目标 Blob 的 SAS URL。
total_size 推荐返回。返回后 SDK 可以跳过 HEAD
expires_at SAS 过期时间。
headers 可选,业务需要的额外 header。

SAS 权限建议:

操作 建议权限
上传新 Blob write (w) 和 create (c)。
覆盖或提交 block list 根据后端策略确保可以 Put Block / Put Block List。
下载 read (r)。
获取大小 如果不提供 total_size,URL 或额外授权需要支持 HEAD

5. Azure SAS 上传完整示例

下面示例演示如何用一个 Blob SAS URL 生成每个 block 的上传 URL,并在所有 block 完成后提交 block list。

rust 复制代码
use std::sync::Arc;

use rusty_cat::api::{
    MeowClient, MeowConfig, PresignedMultipartUpload,
    PresignedMultipartUploadPlan, UploadPounceBuilder,
};
use rusty_cat::azure_blob_sas as azure;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    // 真实项目中,这个 URL 必须由后端返回。
    // 这里的 REDACTED 代表签名参数已脱敏,不能直接运行。
    let blob_sas_url =
        "https://account.blob.core.windows.net/container/blob.bin?sv=REDACTED&sig=REDACTED";

    let chunk_size = 1024 * 1024;
    let total_size = 5 * chunk_size;
    let part_count = 5;

    let parts = (0..part_count)
        .map(|i| {
            azure::put_block_from_blob_url(
                blob_sas_url,
                i,
                i as u64 * chunk_size,
                chunk_size,
            )
        })
        .collect::<Result<Vec<_>, _>>()?;

    let block_ids = (0..part_count)
        .map(azure::block_id_by_index)
        .collect::<Vec<_>>();
    let block_id_refs = block_ids
        .iter()
        .map(String::as_str)
        .collect::<Vec<_>>();

    let complete_request = azure::put_block_list_request(
        blob_sas_url,
        block_id_refs,
    )?;

    let upload_protocol = PresignedMultipartUpload::new(
        PresignedMultipartUploadPlan::new(total_size, chunk_size, parts)
            .with_complete_request(complete_request),
    );

    let client = MeowClient::new(MeowConfig::default());
    let task = UploadPounceBuilder::new(
        "azure-sas.bin",
        "./azure-sas.bin",
        chunk_size,
    )
    .with_url(blob_sas_url)
    .with_breakpoint_upload(Arc::new(upload_protocol))
    .build()?;

    let outcome = client
        .enqueue_and_wait(task, |record| {
            println!(
                "Azure SAS 上传进度:{:.2}% status={:?}",
                record.progress() * 100.0,
                record.status(),
            );
        })
        .await?;

    println!("上传完成:task_id={} payload={:?}", outcome.task_id, outcome.payload);
    client.close().await?;
    Ok(())
}

运行前必须替换:

  • blob_sas_url:后端返回的真实 SAS URL。
  • total_size:真实文件大小。
  • chunk_size:与后端规划一致的分片大小。
  • part_count:按文件大小和分片大小计算,不要写死。
  • ./azure-sas.bin:真实本地文件路径。

6. 上传参数逐个解释

参数或方法 说明
azure::put_block_from_blob_url(blob_sas_url, index, offset, size) 根据 Blob SAS URL 生成某个 block 的 Put Block 请求描述。
index block 序号,用来生成 block id 和 URL 参数。
offset 该 block 对应本地文件起始字节位置。
size 该 block 大小,最后一个 block 可能小于 chunk_size
azure::block_id_by_index(index) 根据序号生成稳定 block id。
azure::put_block_list_request(blob_sas_url, block_id_refs) 创建提交 block list 的完成请求。
with_complete_request(complete_request) 告诉 SDK 上传完所有 block 后提交 block list。
with_breakpoint_upload(...) 把 SAS 上传协议注入上传任务。

最关键的点是:**上传 block 的 block id 列表必须和最终 Put Block List 使用的 block id 完全一致。**否则 Azure 可能找不到 block,或者提交出错误内容。

7. Azure SAS 下载完整示例

如果后端知道 Blob 大小,推荐直接返回 total_size

rust 复制代码
use std::sync::Arc;

use rusty_cat::api::{
    DownloadPounceBuilder, MeowClient, MeowConfig,
    PresignedRangeDownload, PresignedRangeDownloadPlan,
};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    // 真实项目中,这个 URL 来自后端。不要把完整 SAS 写入日志。
    let download_sas_url =
        "https://account.blob.core.windows.net/container/blob.bin?sv=REDACTED&sig=REDACTED";
    let total_size = 5 * 1024 * 1024;

    let download_protocol = PresignedRangeDownload::new(
        PresignedRangeDownloadPlan::new(download_sas_url)
            .with_total_size(total_size),
    );

    let client = MeowClient::new(MeowConfig::default());
    let task = DownloadPounceBuilder::new(
        "azure-sas.bin",
        "./downloads/azure-sas.bin",
        1024 * 1024,
        download_sas_url,
    )
    .with_breakpoint_download(Arc::new(download_protocol))
    .build();

    let outcome = client
        .enqueue_and_wait(task, |record| {
            println!(
                "Azure SAS 下载进度:{:.2}% status={:?}",
                record.progress() * 100.0,
                record.status(),
            );
        })
        .await?;

    println!("下载完成:task_id={}", outcome.task_id);
    client.close().await?;
    Ok(())
}

如果没有 with_total_size(total_size),SDK 可能需要通过 HEAD 获取大小。这要求 SAS URL 或后端提供的 head URL 支持 HEAD。对小白来说,最稳妥的方式是让后端直接返回文件大小。

8. SAS URL 过期怎么处理?

SAS URL 是短期授权,过期后常见表现是 403

建议:

  • 后端返回 expires_at
  • 客户端记录任务开始时间和预计剩余时间。
  • 大文件上传时,在过期前请求新 SAS。
  • 暂停很久后恢复时,不要继续用旧 SAS。
  • 下载失败如果是过期导致,重新向后端申请下载 SAS,再重建任务。

日志里不要打印完整 SAS URL。可以这样记录:

text 复制代码
file_id=业务文件ID provider=azure-sas status=expired

不要这样记录:

text 复制代码
download_url=https://account.blob.core.windows.net/...?...&sig=REDACTED_REAL_SIGNATURE

9. 后端接口设计建议

上传授权接口:

text 复制代码
POST /api/azure/blob/sas-upload

请求:
{
  "file_name": "demo.bin",
  "total_size": 5242880,
  "chunk_size": 1048576
}

响应:
{
  "blob_sas_url": "已脱敏的 SAS URL",
  "total_size": 5242880,
  "chunk_size": 1048576,
  "expires_at": "2026-05-17T12:30:00Z",
  "business_upload_id": "upload-001"
}

下载授权接口:

text 复制代码
POST /api/azure/blob/sas-download

响应:
{
  "download_sas_url": "已脱敏的 SAS URL",
  "total_size": 5242880,
  "expires_at": "2026-05-17T12:30:00Z"
}

后端必须做:

  • 用户鉴权。
  • Blob 路径权限校验。
  • SAS 权限最小化。
  • SAS 过期时间控制。
  • 审计记录,但不要记录完整 sig
  • 必要时提供刷新 SAS 的接口。

10. 与 direct 模式对比

维度 direct SAS
客户端是否持有 Account Key
是否适合公开客户端 不适合 更适合
后端复杂度 中等,需要签发 URL
权限范围 取决于 Account Key 权限 可限制到某个 Blob 和某些操作
过期机制 Account Key 通常长期有效 SAS 可短期有效
泄露风险 相对低,但仍需保护 URL

一句话建议:

后端服务可以 direct,用户侧客户端优先 SAS。

11. 常见问题排查

现象 常见原因 处理建议
403 SAS 过期、权限不足、URL 被错误修改。 重新签发 SAS,检查权限和过期时间。
block 上传成功但 Blob 不存在 没有提交 Put Block List。 确认 with_complete_request(...) 已设置。
提交 block list 失败 block id 列表不一致或顺序错误。 确保 block_id_by_index 和 completion 使用同一批 block id。
下载 prepare 失败 SAS 不允许 HEAD,且没有提供 total_size 后端返回 total_size
Range 下载失败 SAS 没有读权限或 Blob 不存在。 检查 SAS 权限和 Blob 路径。
日志泄露签名 打印了完整 SAS URL。 做 URL 脱敏,只记录业务 ID。

12. 本篇小结

Azure Blob SAS 模式的核心价值是安全边界清晰:Account Key 留在后端,客户端只拿短期、有限权限的 URL。

rusty-cat 负责执行上传下载任务,包括分片、重试、进度、完成请求和 Range 下载;你的后端负责用户鉴权、SAS 签发、刷新和审计。

如果你要做面向用户的 Rust 桌面端、CLI 或客户端文件传输,SAS 模式通常比 direct 模式更安全、更容易长期维护。

相关推荐
祁白_1 小时前
[HCTF 2018]WarmUp1
安全·渗透·测试·ctf·writeup
黎阳之光1 小时前
智慧公安视频孪生平台:构建全域治安防控可视化体系
大数据·人工智能·算法·安全·数字孪生
JiaWen技术圈2 小时前
滑块验证码自行编码实现流程
前端·安全
Magic-Yuan2 小时前
致命的耳语 - 提示词注入
人工智能·安全
无风听海2 小时前
OAuth 中的state参数:被低估的安全基石
安全·oauth
Arman_2 小时前
Rust 接入微软 Azure Blob 文件上传下载:rusty-cat 直连模式实战
microsoft·rust·azure·断点续传
黎阳之光2 小时前
城市基础设施安全监测|黎阳之光赋能燃气、供水、热力管网智慧监管
安全
冴羽yayujs2 小时前
前端周报:Rolldown 1.0 正式发布、TanStack 遭遇史诗级供应链攻击、Bun 全面迁移至 Rust
前端·rust·前端开发·前端周报
techdashen2 小时前
Rust 能帮你捕获什么,又不能捕获什么
开发语言·后端·rust