ApiSign.AspNetCore 0.1.0

dotnet add package ApiSign.AspNetCore --version 0.1.0
                    
NuGet\Install-Package ApiSign.AspNetCore -Version 0.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ApiSign.AspNetCore" Version="0.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ApiSign.AspNetCore" Version="0.1.0" />
                    
Directory.Packages.props
<PackageReference Include="ApiSign.AspNetCore" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ApiSign.AspNetCore --version 0.1.0
                    
#r "nuget: ApiSign.AspNetCore, 0.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ApiSign.AspNetCore@0.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ApiSign.AspNetCore&version=0.1.0
                    
Install as a Cake Addin
#tool nuget:?package=ApiSign.AspNetCore&version=0.1.0
                    
Install as a Cake Tool

ApiSign

NuGet NuGet Downloads CI License

ApiSign.AspNetCore 是一个轻量级的 ASP.NET Core API 签名认证组件,专注请求防篡改、防重放和多算法签名验证。

功能

  • 中间件和特性两种接入方式
  • 时间戳有效期验证
  • Nonce 防重放校验
  • MD5、SHA256、HMACSHA256、HMACSHA512
  • 可替换的密钥提供者、Nonce 存储和参数提取器
  • 可自定义签名校验失败响应格式
  • 默认内存 Nonce 存储和默认参数提取实现

快速开始

builder.Services.AddSingleton<IAppSecretProvider, MyAppSecretProvider>();
builder.Services.AddApiSignAuthentication(options =>
{
    options.TimestampDisparitySeconds = 900;
    options.EnableNonce = true;
});

app.UseApiSignAuthentication(excludedPaths: new[] { "/health" });

签名字符串生成规则:

  1. appIdnoncetimestamp 与业务参数合并。
  2. 排除 sign 字段,按参数名升序排序。
  3. 对键和值做 URL Encode,拼成 key=value&key2=value2
  4. MD5/SHA256 使用 canonical&key=secret 摘要。
  5. HMACSHA256/HMACSHA512 使用 secret 作为 HMAC key。

示例项目位于 samples/ApiSign.SampleWeb

请求签名与调用示例见 samples/ApiSign.SampleWeb/REQUEST_EXAMPLES.mdsamples/ApiSign.SampleWeb/ApiSign.SampleWeb.http

StrictMode 严格模式

ApiSignOptions.StrictMode 控制签名参数的提取策略,默认启用(true)。

严格模式(默认)

签名参数必须通过 Header 传递,且 Header 中的值会覆盖 Query/Body 中的同名参数。同时,Query/Body 中的签名参数会被排除在签名字符串之外,确保签名计算仅依赖业务参数。

builder.Services.AddApiSignAuthentication(options =>
{
    options.StrictMode = true; // 默认值
});

非严格模式

关闭后签名参数(appIdnoncetimestampsign)可以从 Query String、Request Body 或 Header 中任意位置获取,优先级为 Query/Body 优先,Header 补充。适用于客户端签名参数位置不固定的场景。

builder.Services.AddApiSignAuthentication(options =>
{
    options.StrictMode = false;
});

两种模式对比

行为 严格模式(默认) 非严格模式
签名参数来源 仅 Header Query/Body/Header 均可
Header 同名参数覆盖
签名参数参与签名计算 不参与 参与
JSON 解析失败 抛出异常 静默忽略并记录警告

自定义失败响应

默认情况下,签名校验失败会返回如下 JSON:

{
  "code": "InvalidSignature",
  "message": "The request signature is invalid."
}

如果你希望改成 ProblemDetails、纯文本或自定义结构,可以实现 IApiSignFailureResponseHandler 并注册到 DI:

using ApiSign.AspNetCore.Abstractions;
using ApiSign.AspNetCore.Models;

public sealed class ProblemDetailsApiSignFailureResponseHandler : IApiSignFailureResponseHandler
{
    public async Task HandleAsync(
        HttpContext httpContext,
        SignValidationResult validationResult,
        CancellationToken cancellationToken = default)
    {
        httpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
        httpContext.Response.ContentType = "application/problem+json";

        await httpContext.Response.WriteAsJsonAsync(
            new ProblemDetails
            {
                Title = "API sign validation failed",
                Detail = validationResult.ErrorMessage,
                Status = httpContext.Response.StatusCode,
                Type = $"https://httpstatuses.com/{httpContext.Response.StatusCode}",
            },
            cancellationToken);
    }
}

builder.Services.AddSingleton<IApiSignFailureResponseHandler, ProblemDetailsApiSignFailureResponseHandler>();
builder.Services.AddApiSignAuthentication();

注册自定义实现后,中间件模式和 [SaCheckSign] 特性模式都会复用同一个失败响应处理器。

Redis Nonce 示例

核心类库默认使用内存 Nonce 存储,不直接依赖 Redis。若需要 Redis 防重放存储,可以像示例项目一样自行实现 INonceStore

samples/ApiSign.SampleWeb 已提供:

  • RedisNonceStore
  • RedisNonceOptions
  • Microsoft.Extensions.Caching.StackExchangeRedis 接入示例

启用方式:

{
  "RedisNonce": {
    "Enabled": true,
    "Configuration": "localhost:6379",
    "InstanceName": "apisign:"
  }
}

示例注册代码:

var redisNonceOptions = builder.Configuration
    .GetSection(RedisNonceOptions.SectionName)
    .Get<RedisNonceOptions>() ?? new RedisNonceOptions();

if (redisNonceOptions.Enabled)
{
    builder.Services.AddStackExchangeRedisCache(options =>
    {
        options.Configuration = redisNonceOptions.Configuration;
        options.InstanceName = redisNonceOptions.InstanceName;
    });
    builder.Services.AddSingleton<INonceStore, RedisNonceStore>();
}

客户端签名(回调第三方接口)

当你需要主动调用第三方 API 并对出站请求自动签名时,使用 ApiSignHttpMessageHandler。它通过 DelegatingHandler 模式嵌入 HttpClient 管道,在发起请求前自动附加签名 Header。

注册方式

注册时不建议绑定固定的 BaseAddress,因为回调地址通常是运行时动态决定的:

// ✅ 推荐:不绑死 BaseAddress,请求时传入完整 URI
builder.Services.AddHttpClient("callback-client")
    .AddApiSignMessageHandler("my-app-id", options =>
    {
        options.Algorithm = SignAlgorithm.HMACSHA256;
        options.StrictMode = true;
    });

使用示例

动态回调 URL(推荐)

回调 URL 通常存储在数据库或配置中,请求时动态传入:

[ApiController]
[Route("api/payment")]
public class PaymentController : ControllerBase
{
    private readonly IHttpClientFactory _httpClientFactory;

    public PaymentController(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
    }

    [HttpPost("notify")]
    public async Task<IActionResult> NotifyThirdParty(
        [FromBody] NotifyRequest request)
    {
        var client = _httpClientFactory.CreateClient("callback-client");

        var payload = new { request.OrderId, Status = "paid", request.Amount };
        var response = await client.PostAsJsonAsync(request.CallbackUrl, payload);

        return Ok(await response.Content.ReadFromJsonAsync<object>());
    }
}

public sealed record NotifyRequest(
    string OrderId,
    decimal Amount,
    string CallbackUrl);
从配置读取
// appsettings.json
{
  "Callback": {
    "TransferUrl": "https://partner.example.com/api/transfer"
  }
}

// 使用方
var callbackUrl = _configuration["Callback:TransferUrl"];
var response = await client.PostAsJsonAsync(callbackUrl, payload);

签名流程

  1. IAppSecretProvider 获取应用密钥
  2. 收集请求参数(Query String、Form、JSON Body)
  3. 严格模式下,剔除 appId/nonce/timestamp/sign 等签名参数名
  4. 构建规范字符串并计算签名
  5. appIdnoncetimestampsign 写入请求 Header

签名算法覆盖

默认使用 IAppSecretProvider 返回的算法。你也可以在注册时通过 options.Algorithm 覆盖:

.AddApiSignMessageHandler("my-app-id", options =>
{
    options.Algorithm = SignAlgorithm.SHA256;
})

自定义 Header 名称

.AddApiSignMessageHandler("my-app-id", options =>
{
    options.AppIdHeaderName = "X-App-Id";
    options.NonceHeaderName = "X-Nonce";
    options.TimestampHeaderName = "X-Timestamp";
    options.SignHeaderName = "X-Sign";
})
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.0 90 7/2/2026