在Google Cloud中使用Rust生成签名URL遇阻求助
Google Cloud Storage签名URL实现问题(Rust)
我尝试在Google Cloud中实现签名URL功能,但始终无法成功,官方文档较为复杂。以下是我用Rust编写的尝试代码:
fn signed_url( method: &str, object: &str, ) -> Result<String, Box<dyn std::error::Error>> { let expiration = Utc::now() + Duration::hours(1); let expiration = expiration.timestamp(); let url_path = format!("/{}/{}", &env_vars().google_cloud_storage_bucket, object); let canonical_request = format!( "{method}\n{url_path}\n\nhost:storage.googleapis.com\nx-goog-algorithm:GOOG4-RSA-SHA256\nx-goog-credential:{}\nx-goog-date:{}\nx-goog-expires:{}\n\nhost;x-goog-algorithm;x-goog-credential;x-goog-date;x-goog-expires\nUNSIGNED-PAYLOAD", env_vars().google_cloud_client_email, Utc::now().format("%Y%m%dT%H%M%SZ"), expiration ); let rsa = Rsa::private_key_from_pem(env_vars().google_cloud_private_key.as_bytes())?; let pkey = PKey::from_rsa(rsa)?; let mut signer = Signer::new(MessageDigest::sha256(), &pkey)?; signer.update(canonical_request.as_bytes())?; let signature = signer.sign_to_vec()?; let encoded_signature = URL_SAFE.encode(signature); let signed_url = format!( "https://storage.googleapis.com{}?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential={}&X-Goog-Date={}&X-Goog-Expires={}&X-Goog-SignedHeaders=host&X-Goog-Signature={}", encode(&url_path), encode(&env_vars().google_cloud_client_email), Utc::now().format("%Y%m%dT%H%M%SZ"), expiration, encoded_signature ); Ok(signed_url) }
问题分析与修正方案
你的代码存在几个关键错误,导致签名URL验证失败:
- 凭证格式错误:
X-Goog-Credential并非直接使用客户端邮箱,而是需要遵循[客户端邮箱]/[日期(YYYYMMDD)]/[存储桶区域]/storage/goog4_request的格式,缺少区域和请求类型会导致验证不通过。 - 时间不一致:生成规范请求和最终签名URL时两次调用
Utc::now(),可能导致时间戳偏差,触发签名过期或无效的错误。 - 私钥格式问题:环境变量中存储的私钥通常会把换行符转义为
\\n,直接传入PEM解析会失败,需要先还原真实换行符。 - 区域未指定:签名URL需要匹配存储桶所在的区域,未指定会导致签名逻辑不匹配。
修正后的代码
use chrono::{Utc, Duration}; use openssl::{pkey::PKey, rsa::Rsa, sign::Signer, hash::MessageDigest}; use urlencoding::{encode, URL_SAFE}; use std::env; // 简化环境变量获取逻辑,实际项目中可替换为你的配置方式 struct EnvVars { google_cloud_storage_bucket: String, google_cloud_client_email: String, google_cloud_private_key: String, } fn env_vars() -> EnvVars { EnvVars { google_cloud_storage_bucket: env::var("GOOGLE_CLOUD_STORAGE_BUCKET").unwrap(), google_cloud_client_email: env::var("GOOGLE_CLOUD_CLIENT_EMAIL").unwrap(), google_cloud_private_key: env::var("GOOGLE_CLOUD_PRIVATE_KEY").unwrap(), } } fn signed_url( method: &str, object: &str, ) -> Result<String, Box<dyn std::error::Error>> { let vars = env_vars(); let now = Utc::now(); // 统一时间戳,避免多次调用导致的偏差 let date_full = now.format("%Y%m%dT%H%M%SZ").to_string(); let date_short = now.format("%Y%m%d").to_string(); // 替换为你的存储桶实际区域,例如us-central1、asia-east1等 let region = "us-central1"; let expiration = (now + Duration::hours(1)).timestamp(); let url_path = format!("/{}/{}", vars.google_cloud_storage_bucket, object); // 构造正确的凭证格式 let credential = format!( "{}/{}/{}/storage/goog4_request", vars.google_cloud_client_email, date_short, region ); let canonical_request = format!( "{method}\n{url_path}\n\nhost:storage.googleapis.com\nx-goog-algorithm:GOOG4-RSA-SHA256\nx-goog-credential:{}\nx-goog-date:{}\nx-goog-expires:{}\n\nhost;x-goog-algorithm;x-goog-credential;x-goog-date;x-goog-expires\nUNSIGNED-PAYLOAD", credential, date_full, expiration ); // 还原私钥中的换行符 let private_key = vars.google_cloud_private_key.replace("\\n", "\n"); let rsa = Rsa::private_key_from_pem(private_key.as_bytes())?; let pkey = PKey::from_rsa(rsa)?; let mut signer = Signer::new(MessageDigest::sha256(), &pkey)?; signer.update(canonical_request.as_bytes())?; let signature = signer.sign_to_vec()?; let encoded_signature = URL_SAFE.encode(signature); let signed_url = format!( "https://storage.googleapis.com{}?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential={}&X-Goog-Date={}&X-Goog-Expires={}&X-Goog-SignedHeaders=host&X-Goog-Signature={}", encode(&url_path), encode(&credential), date_full, expiration, encoded_signature ); Ok(signed_url) }
额外注意事项
- 确保存储桶所在区域与代码中指定的
region一致,可在Google Cloud控制台查看存储桶详情获取区域信息。 - 验证私钥是否为完整的PEM格式,开头为
-----BEGIN PRIVATE KEY-----,结尾为-----END PRIVATE KEY-----。 - 测试时可缩短过期时间(比如5分钟),方便快速验证签名是否有效。
内容的提问来源于stack exchange,提问作者Oliver Dixon
相关产品推荐
相关产品推荐

