我们在做 Api 接口时,相信一定会有接触到要给传输的请求 body 的内容进行加密传输。其目的就是为了防止一些敏感的内容直接被 UI 层查看或篡改。
其实粗略一想就能想到很多种方案,但是哪些方案是目前最适合我们项目的呢?
硬编码方式
最先想到的应该就是硬编码方式,就是哪个接口需要进行传输加密,那么就针对该接口特殊处理:
public class SecurityApiController { ... public async Task UpdateUser([FromBody] SecurityRequest request) { var requestBody = RsaHelper.Decrypt(privateKey, request.Content); var user = JsonHelper.Deserialize(requestBody); await UpdateUserAsync(user); return new Result(RsaHelper.Encrypt(publicKey, new{ Success=true})); } }
// 文件 UserController.cs public partial class BaseController { ... } // 文件 AccountController.cs public partial class BaseController { } // ...
这样势必就会导致一个明显的问题,就是“代码爆炸”。这相当于将所有的业务逻辑全部灌输到一个控制器中,刚开始写的时候方便了,但是后期维护以及交接换人的时候阅读代码是非常痛苦的一个过程。因为在不同的 Controller 文件中势必会重复初始化一些模块,而我们在引用方法的时候 IDE 每次都会显示上千个方法,有时候还不得不查看哪些方法名一样或相近的具体意义。
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(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(); var configuration = context.Services.GetRequiredService(); return new SecurityTransportModelBinder(loggerFactory, configuration); } return null; } }
public class UserUpdateRequest: IApiEncrypt { public int UserId { get; set; } public string Phone { get; set; } public string Address { get; set; } ... }