.NET Core 7 API端到端加密:请求解密最佳实践咨询
.NET Core 7 API端到端加密:两种实现方案的最佳实践
针对你提出的两种端到端加密实现方式,以下是具体的最佳实践分析和代码示例:
方案1:自定义过滤器统一处理请求解密
可行性与优势
完全可行,这种方式遵循关注点分离原则,将加解密逻辑与控制器业务代码解耦,适合全局或批量接口的加解密需求,能大幅减少重复代码,便于后续维护。
实现步骤与代码示例
封装加密解密服务
先实现通用的加密解密逻辑,这里以AES算法为例:public interface IEncryptionService { string Decrypt(string encryptedText, string key = null); string Encrypt(string plainText, string key = null); } public class AesEncryptionService : IEncryptionService { private readonly string _defaultKey; public AesEncryptionService(IConfiguration config) { _defaultKey = config["Encryption:DefaultKey"]; // 生产环境建议使用密钥管理服务(如Azure Key Vault)存储密钥,避免硬编码 } public string Decrypt(string encryptedText, string key = null) { var actualKey = key ?? _defaultKey; using var aes = Aes.Create(); aes.Key = Encoding.UTF8.GetBytes(actualKey.PadRight(32).Substring(0, 32)); aes.IV = Encoding.UTF8.GetBytes(actualKey.PadRight(16).Substring(0, 16)); var decryptor = aes.CreateDecryptor(aes.Key, aes.IV); var encryptedBytes = Convert.FromBase64String(encryptedText); using var ms = new MemoryStream(encryptedBytes); using var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read); using var sr = new StreamReader(cs); return sr.ReadToEnd(); } public string Encrypt(string plainText, string key = null) { var actualKey = key ?? _defaultKey; using var aes = Aes.Create(); aes.Key = Encoding.UTF8.GetBytes(actualKey.PadRight(32).Substring(0, 32)); aes.IV = Encoding.UTF8.GetBytes(actualKey.PadRight(16).Substring(0, 16)); var encryptor = aes.CreateEncryptor(aes.Key, aes.IV); using var ms = new MemoryStream(); using var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write); using var sw = new StreamWriter(cs); sw.Write(plainText); sw.Flush(); cs.FlushFinalBlock(); return Convert.ToBase64String(ms.ToArray()); } }实现自定义请求解密过滤器
重写ActionFilterAttribute的OnActionExecutingAsync方法,读取并解密请求体,替换控制器的参数:public class RequestDecryptionFilter : ActionFilterAttribute { private readonly IEncryptionService _encryptionService; private readonly ILogger<RequestDecryptionFilter> _logger; public RequestDecryptionFilter(IEncryptionService encryptionService, ILogger<RequestDecryptionFilter> logger) { _encryptionService = encryptionService; _logger = logger; } public override async Task OnActionExecutingAsync(ActionExecutingContext context) { // 仅处理带请求体的HTTP方法 if (!new[] { HttpMethods.Post, HttpMethods.Put, HttpMethods.Patch }.Contains(context.HttpContext.Request.Method)) { await base.OnActionExecutingAsync(context); return; } try { // 启用请求体缓冲,允许多次读取 context.HttpContext.Request.EnableBuffering(); using var reader = new StreamReader(context.HttpContext.Request.Body, leaveOpen: true); var encryptedBody = await reader.ReadToEndAsync(); context.HttpContext.Request.Body.Position = 0; // 重置流位置,避免后续组件读取失败 // 解密并反序列化为目标模型 var decryptedBody = _encryptionService.Decrypt(encryptedBody); var targetParam = context.ActionArguments.Values.FirstOrDefault(); if (targetParam != null) { var modelType = targetParam.GetType(); var deserializedModel = JsonSerializer.Deserialize(decryptedBody, modelType); // 替换ActionArguments中的参数,控制器将直接拿到解密后的模型 var paramKey = context.ActionArguments.Keys.First(k => context.ActionArguments[k] == targetParam); context.ActionArguments[paramKey] = deserializedModel; } } catch (Exception ex) { _logger.LogError(ex, "请求解密失败"); context.Result = new BadRequestObjectResult("请求数据无效或解密失败"); } await base.OnActionExecutingAsync(context); } }注册服务与过滤器
在Program.cs中注册加密服务和过滤器:builder.Services.AddScoped<IEncryptionService, AesEncryptionService>(); // 全局注册(所有接口生效) builder.Services.AddControllers(options => { options.Filters.Add<RequestDecryptionFilter>(); }); // 或局部注册:在控制器/Action上添加[ServiceFilter(typeof(RequestDecryptionFilter))]
关键注意事项
- 必须启用请求体缓冲,否则
Request.Body只能读取一次,后续组件会读取失败 - 完善异常处理,解密失败直接返回
BadRequest,避免泄露敏感信息 - 若不同接口使用不同密钥,可从请求头中获取密钥,传入解密方法
方案2:控制器层直接调用加解密层
实现方式
在控制器的Action中手动读取加密请求体,调用加密服务解密后反序列化为模型;返回响应时,先序列化模型再加密返回。
代码示例
[ApiController] [Route("api/data")] public class DataController : ControllerBase { private readonly IEncryptionService _encryptionService; private readonly ILogger<DataController> _logger; public DataController(IEncryptionService encryptionService, ILogger<DataController> logger) { _encryptionService = encryptionService; _logger = logger; } [HttpPost] public async Task<IActionResult> ProcessData() { try { // 读取并解密请求体 using var reader = new StreamReader(Request.Body); var encryptedBody = await reader.ReadToEndAsync(); var decryptedBody = _encryptionService.Decrypt(encryptedBody); var requestModel = JsonSerializer.Deserialize<DataRequest>(decryptedBody); // 执行业务逻辑 var responseModel = new DataResponse { Status = "Success", Data = requestModel }; // 加密响应并返回 var serializedResponse = JsonSerializer.Serialize(responseModel); var encryptedResponse = _encryptionService.Encrypt(serializedResponse); return Ok(encryptedResponse); } catch (Exception ex) { _logger.LogError(ex, "请求处理失败"); return BadRequest("请求数据无效"); } } } // 模型定义 public class DataRequest { public string Id { get; set; } public string Content { get; set; } } public class DataResponse { public string Status { get; set; } public DataRequest Data { get; set; } }
优缺点
- 优点:灵活性极高,可针对单个接口定制加解密逻辑,无需全局配置
- 缺点:代码重复严重,多个接口需要加解密时会产生大量冗余代码,不符合DRY原则
方案选择建议
- 若大部分接口都需要加解密,优先选择方案1(自定义过滤器),统一管理逻辑,降低维护成本
- 若仅少数接口需要加解密,或不同接口的加解密逻辑差异较大,选择方案2更合适
额外优化建议
- 响应加密也可通过过滤器实现(重写
OnActionExecutedAsync方法),统一处理所有响应的加密 - 推荐使用RSA交换AES密钥的模式:Android端与API端先通过RSA交换临时AES密钥,再用AES加密实际业务数据,兼顾安全性与性能
- 密钥务必通过安全方式管理,禁止硬编码到代码中
内容的提问来源于stack exchange,提问作者abid Hussain
相关产品推荐
相关产品推荐

