✨ 完成API签名工具的基础结构,包含Cargo配置、README文档及核心库实现,支持多种签名算法和命令行工具功能。
This commit is contained in:
20
rust/Cargo.toml
Normal file
20
rust/Cargo.toml
Normal file
@@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "api-signer"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["Sound Force <info@example.com>"]
|
||||
description = "API签名工具,提供请求签名与验证功能"
|
||||
|
||||
[dependencies]
|
||||
md-5 = "0.10.5"
|
||||
sha1 = "0.10.5"
|
||||
sha2 = "0.10.6"
|
||||
hmac = "0.12.1"
|
||||
hex = "0.4.3"
|
||||
url = "2.3.1"
|
||||
once_cell = "1.17.1"
|
||||
chrono = "0.4.23"
|
||||
thiserror = "2.0.12"
|
||||
regex = "1.7.3"
|
||||
dotenv = "0.15.0"
|
||||
clap = { version = "4.1.11", features = ["derive"] }
|
||||
231
rust/README.md
Normal file
231
rust/README.md
Normal file
@@ -0,0 +1,231 @@
|
||||
# API签名工具 - Rust实现
|
||||
|
||||
## 项目结构
|
||||
|
||||
根据实际项目文件结构:
|
||||
|
||||
```plaintext
|
||||
rust/
|
||||
├── Cargo.toml # Cargo配置文件
|
||||
├── Cargo.lock # 依赖锁定文件
|
||||
├── src/
|
||||
│ ├── main.rs # 命令行工具入口
|
||||
│ └── lib.rs # 库入口和核心实现
|
||||
└── target/ # 构建输出目录
|
||||
```
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 构建项目
|
||||
|
||||
```bash
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
### 运行命令行工具
|
||||
|
||||
```bash
|
||||
cargo run --release -- [选项]
|
||||
```
|
||||
|
||||
或使用编译后的二进制文件:
|
||||
|
||||
```bash
|
||||
./target/release/apisign [选项]
|
||||
```
|
||||
|
||||
### 命令行选项
|
||||
|
||||
| 选项 | 描述 |
|
||||
|------|------|
|
||||
| `-a, --algorithm` | 签名算法: md5, sha1, sha256, hmac-sha256 |
|
||||
| `-u, --url` | API基础URL |
|
||||
| `-p, --param` | 请求参数,格式为key=value,可多次使用 |
|
||||
| `-k, --key` | 访问密钥ID |
|
||||
| `-s, --secret` | 密钥 |
|
||||
| `-c, --channel` | 合作渠道方ID |
|
||||
| `-h, --help` | 显示帮助信息 |
|
||||
| `-m, --mode` | 操作模式: sign_url, sign_params, verify |
|
||||
| `-t, --timestamp` | 自定义时间戳 |
|
||||
| `-n, --nonce` | 自定义随机字符串 |
|
||||
| `--version` | 显示版本信息 |
|
||||
|
||||
### 常用命令示例
|
||||
|
||||
**基本用法**
|
||||
|
||||
```bash
|
||||
./target/release/apisign
|
||||
```
|
||||
|
||||
**自定义参数**
|
||||
|
||||
```bash
|
||||
./target/release/apisign \
|
||||
-u "https://api.example.com/user/info" \
|
||||
-p "userId=12345" -p "action=getInfo" \
|
||||
-k "YOUR_ACCESS_KEY" \
|
||||
-s "YOUR_SECRET_KEY" \
|
||||
-c "3"
|
||||
```
|
||||
|
||||
**指定签名算法**
|
||||
|
||||
```bash
|
||||
./target/release/apisign -a sha256
|
||||
```
|
||||
|
||||
**生成签名参数**
|
||||
|
||||
```bash
|
||||
./target/release/apisign -m sign_params -p "userId=12345" -p "action=getData"
|
||||
```
|
||||
|
||||
**验证签名**
|
||||
|
||||
```bash
|
||||
./target/release/apisign -m verify -p "userId=12345" -p "action=getData" -p "AccessKeyId=test-key" -p "channelId=test-channel" -p "timestamp=1621234567890" -p "nonce=abc123" -p "sign=calculated-signature-here"
|
||||
```
|
||||
|
||||
**帮助信息**
|
||||
|
||||
```bash
|
||||
./target/release/apisign --help
|
||||
```
|
||||
|
||||
### API接口测试实例
|
||||
|
||||
使用真实API接口进行测试:
|
||||
|
||||
```bash
|
||||
# 未签名的API调用测试 - 返回错误
|
||||
curl "https://api-v1.sound-force.com:8443/p/album/single/media-url?channelId=3&singleId=381980"
|
||||
# 返回: {"code":400,"data":null,"msg":"Missing AccessKeyId","success":false}
|
||||
|
||||
# 生成访问https://api-v1.sound-force.com:8443/p/album/single/media-url的签名URL
|
||||
./target/release/apisign \
|
||||
-a md5 \
|
||||
-u "https://api-v1.sound-force.com:8443/p/album/single/media-url" \
|
||||
-p "singleId=381980" \
|
||||
-k "YOUR_ACCESS_KEY" \
|
||||
-s "YOUR_SECRET_KEY" \
|
||||
-c "3"
|
||||
|
||||
# 使用curl测试API接口
|
||||
signed_url=$(./target/release/apisign \
|
||||
-a md5 \
|
||||
-u "https://api-v1.sound-force.com:8443/p/album/single/media-url" \
|
||||
-p "singleId=381980" \
|
||||
-k "YOUR_ACCESS_KEY" \
|
||||
-s "YOUR_SECRET_KEY" \
|
||||
-c "3" | grep -A 1 "签名后的URL:" | tail -n 1)
|
||||
curl -v "$signed_url"
|
||||
```
|
||||
|
||||
请注意:
|
||||
|
||||
- 替换`YOUR_ACCESS_KEY`为实际的访问密钥ID
|
||||
- 替换`YOUR_SECRET_KEY`为实际的密钥
|
||||
- 示例使用的渠道ID为`3`,请根据实际情况调整
|
||||
|
||||
使用有效的密钥和签名后,API接口将返回成功响应(状态码200)并提供媒体URL数据。
|
||||
|
||||
### Rust Reqwest客户端测试示例
|
||||
|
||||
```rust
|
||||
use std::process::Command;
|
||||
use reqwest;
|
||||
use std::io::{self, Write};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// 获取签名URL
|
||||
let output = Command::new("./target/release/apisign")
|
||||
.args(&[
|
||||
"-a", "md5",
|
||||
"-u", "https://api-v1.sound-force.com:8443/p/album/single/media-url",
|
||||
"-p", "singleId=381980",
|
||||
"-k", "YOUR_ACCESS_KEY",
|
||||
"-s", "YOUR_SECRET_KEY",
|
||||
"-c", "3"
|
||||
])
|
||||
.output()?;
|
||||
|
||||
let output_str = String::from_utf8(output.stdout)?;
|
||||
let lines: Vec<&str> = output_str.lines().collect();
|
||||
|
||||
// 提取URL
|
||||
let mut signed_url = "";
|
||||
for (i, line) in lines.iter().enumerate() {
|
||||
if line.contains("签名后的URL:") && i + 1 < lines.len() {
|
||||
signed_url = lines[i + 1].trim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
println!("签名生成的URL: {}", signed_url);
|
||||
|
||||
if !signed_url.is_empty() {
|
||||
// 发送请求
|
||||
let client = reqwest::Client::new();
|
||||
let response = client.get(signed_url).send().await?;
|
||||
|
||||
println!("状态码: {}", response.status());
|
||||
let body = response.text().await?;
|
||||
println!("响应内容: {}", body);
|
||||
} else {
|
||||
println!("无法从输出中获取签名URL");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### 代码集成
|
||||
|
||||
```rust
|
||||
use apisign::{ApiSigner, SignOptions, SignatureAlgorithm};
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn main() {
|
||||
// 创建签名选项
|
||||
let options = SignOptions::new()
|
||||
.with_algorithm(SignatureAlgorithm::Md5);
|
||||
|
||||
// 创建签名工具
|
||||
let signer = ApiSigner::new(options);
|
||||
|
||||
// 准备请求参数
|
||||
let mut params = HashMap::new();
|
||||
params.insert("singleId".to_string(), "381980".to_string());
|
||||
|
||||
// 执行签名
|
||||
let signed_params = signer.sign_request(
|
||||
¶ms,
|
||||
"YOUR_ACCESS_KEY",
|
||||
"YOUR_SECRET_KEY",
|
||||
"3"
|
||||
).unwrap();
|
||||
|
||||
// 或签名URL
|
||||
let signed_url = signer.sign_url(
|
||||
"https://api-v1.sound-force.com:8443/p/album/single/media-url",
|
||||
¶ms,
|
||||
"YOUR_ACCESS_KEY",
|
||||
"YOUR_SECRET_KEY",
|
||||
"3"
|
||||
).unwrap();
|
||||
|
||||
println!("{}", signed_url);
|
||||
}
|
||||
```
|
||||
|
||||
### 环境变量
|
||||
|
||||
该工具支持从`.env`文件加载以下配置:
|
||||
|
||||
- `ACCESS_KEY_ID`: 访问密钥ID
|
||||
- `SECRET_KEY`: 密钥
|
||||
- `CHANNEL_ID`: 渠道ID
|
||||
- `SIGN_ALGORITHM`: 签名算法
|
||||
- `API_BASE_URL`: API基础URL
|
||||
373
rust/src/lib.rs
Normal file
373
rust/src/lib.rs
Normal file
@@ -0,0 +1,373 @@
|
||||
use hmac::{Hmac, Mac};
|
||||
use md5::{Digest, Md5};
|
||||
use regex::Regex;
|
||||
use sha1::Sha1;
|
||||
use sha2::Sha256;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt,
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
use thiserror::Error;
|
||||
use url::form_urlencoded;
|
||||
|
||||
type HmacSha256 = Hmac<Sha256>;
|
||||
|
||||
/// API签名错误类型
|
||||
#[derive(Error, Debug)]
|
||||
pub enum SignError {
|
||||
#[error("无效的签名算法: {0}")]
|
||||
InvalidAlgorithm(String),
|
||||
|
||||
#[error("缺少必要参数: {0}")]
|
||||
MissingParameter(String),
|
||||
|
||||
#[error("参数解析失败: {0}")]
|
||||
ParseError(String),
|
||||
|
||||
#[error("时间戳过期")]
|
||||
TimestampExpired,
|
||||
|
||||
#[error("签名验证失败")]
|
||||
SignatureVerificationFailed,
|
||||
|
||||
#[error("HMAC错误: {0}")]
|
||||
HmacError(String),
|
||||
}
|
||||
|
||||
/// 签名算法类型
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SignatureAlgorithm {
|
||||
/// MD5算法(默认、最快)
|
||||
Md5,
|
||||
/// SHA1算法
|
||||
Sha1,
|
||||
/// SHA256算法
|
||||
Sha256,
|
||||
/// HMAC-SHA256算法(最安全)
|
||||
HmacSha256,
|
||||
}
|
||||
|
||||
impl SignatureAlgorithm {
|
||||
/// 从字符串解析算法类型
|
||||
pub fn from_str(s: &str) -> Result<Self, SignError> {
|
||||
match s.to_uppercase().as_str() {
|
||||
"MD5" => Ok(SignatureAlgorithm::Md5),
|
||||
"SHA1" => Ok(SignatureAlgorithm::Sha1),
|
||||
"SHA256" => Ok(SignatureAlgorithm::Sha256),
|
||||
"HMAC_SHA256" | "HMACSHA256" | "HMAC-SHA256" => Ok(SignatureAlgorithm::HmacSha256),
|
||||
_ => Err(SignError::InvalidAlgorithm(s.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SignatureAlgorithm {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
SignatureAlgorithm::Md5 => write!(f, "MD5"),
|
||||
SignatureAlgorithm::Sha1 => write!(f, "SHA1"),
|
||||
SignatureAlgorithm::Sha256 => write!(f, "SHA256"),
|
||||
SignatureAlgorithm::HmacSha256 => write!(f, "HMAC-SHA256"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SignatureAlgorithm {
|
||||
fn default() -> Self {
|
||||
Self::Md5
|
||||
}
|
||||
}
|
||||
|
||||
/// 签名选项
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SignOptions {
|
||||
/// 签名算法
|
||||
pub algorithm: SignatureAlgorithm,
|
||||
/// AccessKeyId参数名
|
||||
pub key_name: String,
|
||||
/// 合作渠道方ID参数名
|
||||
pub channel_id_name: String,
|
||||
/// 时间戳参数名
|
||||
pub timestamp_name: String,
|
||||
/// 随机字符串参数名
|
||||
pub nonce_name: String,
|
||||
/// 签名参数名
|
||||
pub signature_name: String,
|
||||
}
|
||||
|
||||
impl Default for SignOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
algorithm: SignatureAlgorithm::default(),
|
||||
key_name: "AccessKeyId".to_string(),
|
||||
channel_id_name: "channelId".to_string(),
|
||||
timestamp_name: "timestamp".to_string(),
|
||||
nonce_name: "nonce".to_string(),
|
||||
signature_name: "sign".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// API签名工具
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ApiSigner {
|
||||
options: SignOptions,
|
||||
}
|
||||
|
||||
impl ApiSigner {
|
||||
/// 创建API签名工具
|
||||
///
|
||||
/// # 参数
|
||||
/// * `options` - 签名选项,如为None则使用默认选项
|
||||
///
|
||||
/// # 返回
|
||||
/// * 签名工具实例
|
||||
pub fn new(options: Option<SignOptions>) -> Self {
|
||||
Self {
|
||||
options: options.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 生成随机字符串
|
||||
///
|
||||
/// # 返回
|
||||
/// * 一个基于当前时间的随机字符串
|
||||
pub fn generate_nonce(&self) -> String {
|
||||
let now = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap_or_default();
|
||||
format!("{}{}", now.as_nanos(), now.as_secs() % 1000)
|
||||
}
|
||||
|
||||
/// 获取当前时间戳(毫秒)
|
||||
///
|
||||
/// # 返回
|
||||
/// * 当前的Unix时间戳(毫秒)
|
||||
pub fn get_timestamp(&self) -> i64 {
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_millis() as i64
|
||||
}
|
||||
|
||||
/// 对请求进行签名
|
||||
///
|
||||
/// # 参数
|
||||
/// * `params` - 请求参数
|
||||
/// * `access_key_id` - 访问密钥ID
|
||||
/// * `secret_key` - 密钥
|
||||
/// * `channel_id` - 渠道ID
|
||||
///
|
||||
/// # 返回
|
||||
/// * 添加了签名的完整参数
|
||||
pub fn sign_request(
|
||||
&self,
|
||||
params: &HashMap<String, String>,
|
||||
access_key_id: &str,
|
||||
secret_key: &str,
|
||||
channel_id: &str,
|
||||
) -> HashMap<String, String> {
|
||||
let mut signed_params = params.clone();
|
||||
|
||||
let timestamp = self.get_timestamp();
|
||||
signed_params.insert(self.options.key_name.clone(), access_key_id.to_string());
|
||||
signed_params.insert(self.options.channel_id_name.clone(), channel_id.to_string());
|
||||
signed_params.insert(self.options.timestamp_name.clone(), timestamp.to_string());
|
||||
signed_params.insert(self.options.nonce_name.clone(), self.generate_nonce());
|
||||
|
||||
let signature = self.calculate_signature(&signed_params, secret_key);
|
||||
|
||||
signed_params.insert(self.options.signature_name.clone(), signature);
|
||||
|
||||
signed_params
|
||||
}
|
||||
|
||||
/// 对URL进行签名
|
||||
///
|
||||
/// # 参数
|
||||
/// * `base_url` - 基础URL地址
|
||||
/// * `params` - 请求参数
|
||||
/// * `access_key_id` - 访问密钥ID
|
||||
/// * `secret_key` - 密钥
|
||||
/// * `channel_id` - 渠道ID
|
||||
///
|
||||
/// # 返回
|
||||
/// * 添加了签名的完整URL
|
||||
pub fn sign_url(
|
||||
&self,
|
||||
base_url: &str,
|
||||
params: &HashMap<String, String>,
|
||||
access_key_id: &str,
|
||||
secret_key: &str,
|
||||
channel_id: &str,
|
||||
) -> String {
|
||||
let signed_params = self.sign_request(params, access_key_id, secret_key, channel_id);
|
||||
|
||||
let query_string = self.params_to_query_string(&signed_params);
|
||||
|
||||
if base_url.contains('?') {
|
||||
format!("{}&{}", base_url, query_string)
|
||||
} else {
|
||||
format!("{}?{}", base_url, query_string)
|
||||
}
|
||||
}
|
||||
|
||||
/// 将参数转换为查询字符串
|
||||
fn params_to_query_string(&self, params: &HashMap<String, String>) -> String {
|
||||
let mut pairs = Vec::with_capacity(params.len());
|
||||
|
||||
for (key, value) in params {
|
||||
let encoded = form_urlencoded::Serializer::new(String::new())
|
||||
.append_pair(key, value)
|
||||
.finish();
|
||||
pairs.push(encoded);
|
||||
}
|
||||
|
||||
pairs.sort();
|
||||
pairs.join("&")
|
||||
}
|
||||
|
||||
/// 计算签名
|
||||
///
|
||||
/// # 参数
|
||||
/// * `params` - 请求参数
|
||||
/// * `secret_key` - 密钥
|
||||
///
|
||||
/// # 返回
|
||||
/// * 签名字符串
|
||||
pub fn calculate_signature(
|
||||
&self,
|
||||
params: &HashMap<String, String>,
|
||||
secret_key: &str,
|
||||
) -> String {
|
||||
let signing_string = self.create_signing_string(params);
|
||||
|
||||
let signing_string = format!("{}&key={}", signing_string, secret_key);
|
||||
|
||||
match self.options.algorithm {
|
||||
SignatureAlgorithm::Md5 => {
|
||||
let mut hasher = Md5::new();
|
||||
hasher.update(signing_string.as_bytes());
|
||||
hex::encode(hasher.finalize())
|
||||
}
|
||||
SignatureAlgorithm::Sha1 => {
|
||||
let mut hasher = Sha1::new();
|
||||
hasher.update(signing_string.as_bytes());
|
||||
hex::encode(hasher.finalize())
|
||||
}
|
||||
SignatureAlgorithm::Sha256 => {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(signing_string.as_bytes());
|
||||
hex::encode(hasher.finalize())
|
||||
}
|
||||
SignatureAlgorithm::HmacSha256 => {
|
||||
let mut mac =
|
||||
HmacSha256::new_from_slice(secret_key.as_bytes()).expect("HMAC初始化失败");
|
||||
mac.update(signing_string.as_bytes());
|
||||
hex::encode(mac.finalize().into_bytes())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 创建用于签名的规范化字符串
|
||||
///
|
||||
/// # 参数
|
||||
/// * `params` - 请求参数
|
||||
///
|
||||
/// # 返回
|
||||
/// * 按键名排序并拼接的字符串
|
||||
pub fn create_signing_string(&self, params: &HashMap<String, String>) -> String {
|
||||
let mut sorted_params = HashMap::new();
|
||||
for (k, v) in params {
|
||||
if k != &self.options.signature_name {
|
||||
sorted_params.insert(k.clone(), v.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let mut keys: Vec<String> = sorted_params.keys().cloned().collect();
|
||||
keys.sort();
|
||||
|
||||
let mut parts = Vec::with_capacity(keys.len());
|
||||
for key in keys {
|
||||
let value = sorted_params.get(&key).unwrap();
|
||||
let value = if self.needs_url_encode(value) {
|
||||
form_urlencoded::Serializer::new(String::new())
|
||||
.append_pair("", value)
|
||||
.finish()
|
||||
.trim_start_matches('=')
|
||||
.to_string()
|
||||
} else {
|
||||
value.clone()
|
||||
};
|
||||
parts.push(format!("{}={}", key, value));
|
||||
}
|
||||
|
||||
parts.join("&")
|
||||
}
|
||||
|
||||
/// 判断是否需要对字符串进行URL编码
|
||||
fn needs_url_encode(&self, s: &str) -> bool {
|
||||
let re = Regex::new(r"^[a-zA-Z0-9]*$").unwrap();
|
||||
!re.is_match(s)
|
||||
}
|
||||
|
||||
/// 验证签名
|
||||
///
|
||||
/// # 参数
|
||||
/// * `params` - 所有请求参数,包括签名
|
||||
/// * `secret_key` - 密钥
|
||||
/// * `max_age_ms` - 允许的最大时间差(毫秒)
|
||||
///
|
||||
/// # 返回
|
||||
/// * 验证结果,成功为Ok(()),失败为Err
|
||||
pub fn verify_signature(
|
||||
&self,
|
||||
params: &HashMap<String, String>,
|
||||
secret_key: &str,
|
||||
max_age_ms: i64,
|
||||
) -> Result<(), SignError> {
|
||||
if !params.contains_key(&self.options.key_name) {
|
||||
return Err(SignError::MissingParameter(self.options.key_name.clone()));
|
||||
}
|
||||
|
||||
let timestamp_str = params
|
||||
.get(&self.options.timestamp_name)
|
||||
.ok_or_else(|| SignError::MissingParameter(self.options.timestamp_name.clone()))?;
|
||||
|
||||
let timestamp = timestamp_str
|
||||
.parse::<i64>()
|
||||
.map_err(|e| SignError::ParseError(e.to_string()))?;
|
||||
|
||||
let now = self.get_timestamp();
|
||||
if (now - timestamp).abs() > max_age_ms {
|
||||
return Err(SignError::TimestampExpired);
|
||||
}
|
||||
|
||||
if !params.contains_key(&self.options.nonce_name) {
|
||||
return Err(SignError::MissingParameter(self.options.nonce_name.clone()));
|
||||
}
|
||||
|
||||
let provided_signature = params
|
||||
.get(&self.options.signature_name)
|
||||
.ok_or_else(|| SignError::MissingParameter(self.options.signature_name.clone()))?;
|
||||
|
||||
let expected_signature = self.calculate_signature(params, secret_key);
|
||||
if expected_signature == *provided_signature {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(SignError::SignatureVerificationFailed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 默认创建签名选项
|
||||
pub fn default_sign_options() -> SignOptions {
|
||||
SignOptions {
|
||||
algorithm: SignatureAlgorithm::Md5,
|
||||
key_name: String::from("AccessKeyId"),
|
||||
channel_id_name: String::from("channelId"),
|
||||
timestamp_name: String::from("timestamp"),
|
||||
nonce_name: String::from("nonce"),
|
||||
signature_name: String::from("sign"),
|
||||
}
|
||||
}
|
||||
167
rust/src/main.rs
Normal file
167
rust/src/main.rs
Normal file
@@ -0,0 +1,167 @@
|
||||
use api_signer::{ApiSigner, SignOptions, SignatureAlgorithm};
|
||||
use clap::Parser;
|
||||
use dotenv::dotenv;
|
||||
use std::{collections::HashMap, env, error::Error};
|
||||
|
||||
/// API签名工具CLI程序
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Args {
|
||||
/// 签名算法:MD5, SHA1, SHA256, HMAC-SHA256
|
||||
#[arg(short, long, default_value = "MD5")]
|
||||
algorithm: String,
|
||||
|
||||
/// API操作类型:sign_url, sign_params, verify
|
||||
#[arg(short = 'm', long, default_value = "sign_url")]
|
||||
action: String,
|
||||
|
||||
/// 基础URL地址(仅sign_url模式需要)
|
||||
#[arg(short, long, default_value = "https://api.example.com/v1/data")]
|
||||
url: String,
|
||||
|
||||
/// 请求参数,格式为key=value,可多次指定
|
||||
#[arg(short, long)]
|
||||
param: Vec<String>,
|
||||
|
||||
/// 访问密钥ID(默认从环境变量ACCESS_KEY_ID读取)
|
||||
#[arg(short = 'k', long)]
|
||||
access_key_id: Option<String>,
|
||||
|
||||
/// 密钥(默认从环境变量SECRET_KEY读取)
|
||||
#[arg(short = 's', long)]
|
||||
secret_key: Option<String>,
|
||||
|
||||
/// 合作渠道方ID(默认从环境变量CHANNEL_ID读取)
|
||||
#[arg(short = 'c', long)]
|
||||
channel_id: Option<String>,
|
||||
|
||||
/// 签名(仅verify模式需要)
|
||||
#[arg(short = 'g', long)]
|
||||
signature: Option<String>,
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
// 加载环境变量
|
||||
dotenv().ok();
|
||||
|
||||
// 解析命令行参数
|
||||
let args = Args::parse();
|
||||
|
||||
// 获取配置
|
||||
let access_key_id = args
|
||||
.access_key_id
|
||||
.unwrap_or_else(|| env::var("ACCESS_KEY_ID").unwrap_or("test-access-key-id".to_string()));
|
||||
|
||||
let secret_key = args
|
||||
.secret_key
|
||||
.unwrap_or_else(|| env::var("SECRET_KEY").unwrap_or("test-secret-key".to_string()));
|
||||
|
||||
let channel_id = args
|
||||
.channel_id
|
||||
.unwrap_or_else(|| env::var("CHANNEL_ID").unwrap_or("test-channel-id".to_string()));
|
||||
|
||||
// 解析签名算法
|
||||
let algorithm =
|
||||
SignatureAlgorithm::from_str(&args.algorithm).unwrap_or(SignatureAlgorithm::Md5);
|
||||
|
||||
// 创建自定义签名选项
|
||||
let mut options = SignOptions::default();
|
||||
options.algorithm = algorithm;
|
||||
|
||||
// 创建签名工具
|
||||
let signer = ApiSigner::new(Some(options.clone()));
|
||||
|
||||
// 解析参数
|
||||
let mut params = HashMap::new();
|
||||
for param in &args.param {
|
||||
if let Some((key, value)) = param.split_once('=') {
|
||||
params.insert(key.to_string(), value.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有参数,使用默认参数进行演示
|
||||
if params.is_empty() {
|
||||
params.insert("userId".to_string(), "12345".to_string());
|
||||
params.insert("action".to_string(), "getData".to_string());
|
||||
params.insert("data".to_string(), "测试数据".to_string()); // 包含非ASCII字符,测试URL编码
|
||||
}
|
||||
|
||||
println!("===================== API签名示例 =====================");
|
||||
println!("AccessKeyId: {}", access_key_id);
|
||||
println!("ChannelId: {}", channel_id);
|
||||
println!("SecretKey: {}", secret_key);
|
||||
println!("签名算法: {}", algorithm);
|
||||
println!("基础URL: {}", args.url);
|
||||
println!("请求参数:");
|
||||
for (key, value) in ¶ms {
|
||||
println!(" {}: {}", key, value);
|
||||
}
|
||||
|
||||
match args.action.as_str() {
|
||||
"sign_url" => {
|
||||
// 签名URL
|
||||
let signed_url =
|
||||
signer.sign_url(&args.url, ¶ms, &access_key_id, &secret_key, &channel_id);
|
||||
println!("\n签名后的URL:");
|
||||
println!("{}", signed_url);
|
||||
}
|
||||
"sign_params" => {
|
||||
// 获取签名后的参数
|
||||
let signed_params =
|
||||
signer.sign_request(¶ms, &access_key_id, &secret_key, &channel_id);
|
||||
println!("\n签名后的参数:");
|
||||
for (key, value) in &signed_params {
|
||||
println!(" {}: {}", key, value);
|
||||
}
|
||||
}
|
||||
"verify" => {
|
||||
// 添加签名参数(如果提供)
|
||||
if let Some(signature) = args.signature {
|
||||
params.insert(options.signature_name.clone(), signature);
|
||||
}
|
||||
|
||||
// 验证签名
|
||||
match signer.verify_signature(¶ms, &secret_key, 300000) {
|
||||
Ok(()) => println!("\n签名验证结果: 验证成功"),
|
||||
Err(err) => println!("\n签名验证结果: 验证失败 - {}", err),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(format!("未知操作类型: {}", args.action).into());
|
||||
}
|
||||
}
|
||||
|
||||
// 演示不同算法的签名结果
|
||||
demonstrate_algorithms(¶ms, &access_key_id, &secret_key);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 演示不同算法的签名结果
|
||||
fn demonstrate_algorithms(params: &HashMap<String, String>, access_key_id: &str, secret_key: &str) {
|
||||
println!("\n不同算法的签名结果:");
|
||||
|
||||
let algorithms = [
|
||||
SignatureAlgorithm::Md5,
|
||||
SignatureAlgorithm::Sha1,
|
||||
SignatureAlgorithm::Sha256,
|
||||
SignatureAlgorithm::HmacSha256,
|
||||
];
|
||||
|
||||
let default_channel_id = "test-channel-id".to_string();
|
||||
let channel_id = params.get("channelId").unwrap_or(&default_channel_id);
|
||||
|
||||
for alg in &algorithms {
|
||||
let mut options = SignOptions::default();
|
||||
options.algorithm = *alg;
|
||||
let signer = ApiSigner::new(Some(options.clone()));
|
||||
|
||||
// 添加必要的参数用于签名
|
||||
let mut sign_params = params.clone();
|
||||
sign_params.insert("AccessKeyId".to_string(), access_key_id.to_string());
|
||||
sign_params.insert("channelId".to_string(), channel_id.to_string());
|
||||
|
||||
let signature = signer.calculate_signature(&sign_params, secret_key);
|
||||
println!(" {}: {}", alg, signature);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user