我们在做 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; }
...
}