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

为了方便描述 , 以上代码我省略了必要的校验和异常错误处理
这样有什么好处呢?一个最明显的好处就是解耦了加解密与真正业务需求 。对真正的业务代码几乎没有侵略性 。其实我认为业务开发能做到这里其实就差不多了,还有其它需求都可以基于这个中间件进行拓展开发 。
那么在 .NET Framwork 没有中间件怎么办呢?
其实在 .NET Framwork 框架下 IHttpModule 能和中间件一样能做到这点:
public class SecurityTransportHttpModule : IHttpModule { ... public void Init(HttpApplication context) {...context.BeginRequest += ContextBeginRequest;context.PostRequestHandlerExecute += ContextPostRequestHandlerExecute; } private void ContextBeginRequest(object sender, EventArgs e) {HttpContext context = ((HttpApplication)sender).Context;var encryptedBody = context.Request.Body;...context.Request.Body = stream; } private void ContextPostRequestHandlerExecute(object sender, EventArgs e){HttpContext context = ((HttpApplication)sender).Context;...context.Response.Write(encryptedBody)}}为什么之前提到这种方案就“差不多”了呢,实际上上面这种方式在某些场景下会显得比较“累赘” 。因为无论通过中间件和还是 IHttpModule 都会导致所有请求都会经过它,相当于增加了一个过滤器 , 如果这时候我要新增一个上传文件接口,那必然也会经过这个处理程序 。说的更直接一点,如果碰到那些少数不需要加解密的接口请求那要怎么办呢?
其实上面可以进行拓展处理,比如对特定的请求进行过滤:
if(context.Request.Path.Contains("upload")) { return;}

注意上述代码只是做个 demo 展示 , 真正还是需要通过如 context.GetRouterData() 获取路由数据进行精准比对 。
当类似于这种需求开始变多以后(吐槽:谁知道业务是怎么发展的呢?)原来的中间件的“任务量”开始变得厚重了起来 。到时候也会变得难以维护和阅读 。
这个时候就是我目前较为满意的解决方案登场了,它就是模型绑定 ModelBinding 。
模型绑定回到需求的开端 , 不难发现,我们其实要是如何将前端加密后的请求体绑定到我们编写的接口方法中 。这里面的过程很复杂,需要解析前端发起的请求,解密之后还要反序列化成目标接口需要的方法参数 。而这个过程还要伴随着参数校验,如这个请求是否符合加密格式 。而这个过程的一切都是模型绑定要解决的事 。我们以 NETCore 版本为例子,讲一下大概的流程;
模型绑定的过程其实就是将请求体的各个字段于具体的 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;}}这里我做了一个方便我自己的拓展功能,就是显示打了

推荐阅读