.NET API 接口数据传输加密最佳实践( 三 )


模型绑定的过程其实就是将请求体的各个字段于具体的 CLR 类型的字段属性进行一一匹配的过程 。.NetCore 再程序启动时会默认提供了一些内置的模型绑定器,并开放 IModelBinderProvider 接口允许用户自定义模型绑定器 。我们通过查看 MvcCoreMvcOptionsSetup 就清楚看到框架为我们添加 18 个自带的模型绑定器 。以及如何调用的方式 。
所以接下来我们很容易的可以一葫芦画瓢的照抄下来:
public class SecurityTransportModelBinder : IModelBinder {...public async Task BindModelAsync(ModelBindingContext bindingContext){if (bindingContext == null){throw new ArgumentNullException(nameof(bindingContext));}try{var request = bindingContext.HttpContext.Request;var model = await JsonSerializer.DeserializeAsync<SafeDataWrapper>(request.Body, new JsonSerializerOptions{PropertyNamingPolicy = JsonNamingPolicy.CamelCase,});var decryptContent = RsaHelper.Decrypt(model.Info, privateKey);var activateModel = JsonSerializer.Deserialize(decryptContent, bindingContext.ModelMetadata.ModelType, new JsonSerializerOptions{PropertyNamingPolicy = JsonNamingPolicy.CamelCase,});//重新包装if (activateModel == null){bindingContext.Result = ModelBindingResult.Failed();}else{bindingContext.Result = ModelBindingResult.Success(activateModel);}}catch (Exception exception){bindingContext.ModelState.TryAddModelError(bindingContext.ModelName,exception,bindingContext.ModelMetadata);}_logger.DoneAttemptingToBindModel(bindingContext);//return Task.CompletedTask;}}抄了 ModelBinder 还不行,还要抄 ModelBinderProvider:
public class SecurityTransportModelBinderProvider : IModelBinderProvider{public IModelBinder GetBinder(ModelBinderProviderContext context){if (context == null){throw new ArgumentNullException(nameof(context));}if (context.Metadata.IsComplexType && typeof(IApiEncrypt).IsAssignableFrom(context.Metadata.ModelType)){var loggerFactory = context.Services.GetRequiredService<ILoggerFactory>();var configuration = context.Services.GetRequiredService<IConfiguration>();return new SecurityTransportModelBinder(loggerFactory, configuration);}return null;}}这里我做了一个方便我自己的拓展功能,就是显示打了 IApiEncrypt 接口标签的才会正常进行解析绑定 。
剩下的就是在 ConfigureService 中添加进去即可:
services.AddControllers(options => { ... options.ModelBinderProviders.Insert(0, new SecurityTransportModelBinderProvider());})这样实现过后,我们就能像使用 FromBody 那样就能按需调用即可:
[HttpPost("security")]public async Task<ResultDto> DemoDecrypt([ModelBinder(typeof(SecurityTransportModelBinder))] OriginBusinessRequest request){//激活结果 ...return await Task.FromResult(WriteSafeData(data, publicKey));}如果是默认处理加解密也是可以的,直接在对应的请求实体类打上 IApiEncrypt 标签就会自动执行模型绑定
public class UserUpdateRequest: IApiEncrypt { public int UserId { get; set; } public string Phone { get; set; } public string Address { get; set; } ...}这种方案其实也还是有缺点的,从刚刚的使用来看就知道,模型绑定无法解决返回自动加密处理 。所以我们不得不在每个接口处写下如 WriteSafeData(data, publicKey) 这种显式加密的代码 。
优化的方式也很简单,其实我们可以通过过滤器可以解决,这也是为什么我要加 IApiEncrypt 的原因,因为有了这个就能确定知道这是一个安全传输的请求,进而进行特殊处理 。

注意,这不是 .NET Core 独有的特性,.Net Framework 也有模型绑定器
总结针对接口级传输加密这个需求,我们总共讨论了四种实现方式 。其实各有各的好处和缺点 。
硬编码方式适合只有特定需求的场景下是适合这种方案的 。但是一旦这种需求成为一种规范或普遍场景时,这个方法就不合适了

经验总结扩展阅读