详解.NET Core中的数据保护组件

 更新时间:2021年9月22日 10:02  点击:1465

背景介绍

在 OWASP(开放式 Web 应用程序安全项目) 2013 年发布的报告中,将不安全的直接对象引用(Insecure Direct Object Reference)标记为 十大 Web 应用程序风险之一, 其表现形式是对象的引用(例如数据库主键)被各种恶意攻击利用, 所以对于Api返回的各种主键外键ID, 我们需要进行加密。

.NET Core 的数据保护组件

.NET Core 中内置了一个IDataProtectionProvider接口和一个IDataProtector接口。其中IDataProtectionProvider是创建保护组件的接口,IDataProtector是数据保护的接口。开发人员可以实现这 2 个接口,创建数据保护组件。
内置的数据保护组件

.NET Core 中默认提供了一个数据保护组件, 下面我们来尝试使用这个默认组件来保护我们的数据。

例: 当前我们有一个Movie类,代码如下, 我们期望当获取Movie对象的时候,Id字段是加密的。

public class Movie
 {
  public Movie(int id, string title)
  {
   Id = id;
   Title = title;
  }

  public int Id { get; set; }
  public string Title { get; set; }
 }

首先我们需要在Startup.cs中ConfigureService方法中配置使用默认的数据保护组件。

public void ConfigureServices(IServiceCollection services)
 {
  services.AddMvc();
  services.AddDataProtection();
 }

这段代码会启用.NET Core默认的数据保护器。

然后我们创建一个MoviesController, 并在构造函数中注入IDataProtectionProvider对象, 然后使用这个Provider对象创建一个实现IDataProtector接口的数据保护器对象

[Route("movies")]
 public class MoviesController : Controller
 {
  private readonly IDataProtector protector;
 
  public MoviesController(IDataProtectionProvider provider)
  {
   this.protector = provider.CreateProtector("protect_my_query_string");
  }
 }

TIPS: 使用Provider创建Protector的时候,我们传入了一个参数"protect_my_query_string", 这个参数标明了这个保护器的用途,你也可以把它就当成这个保护器的名字。

注意: 不同用途的保护器不能解密对方的加密字符串。, 如果使用了保护器A去解密保护器B生成的字符串,会产生以下异常CryptographicException: The payload was invalid.

然后我们在MovieController中添加2个Api, 一个是获取所有Movies对象的,一个是获取指定Movie对象的

[HttpGet]
 public IActionResult Get()
 {
  var model = GetMovies();
  
  var outputModel = model.Select(item => new
  {
   Id = this.protector.Protect(item.Id.ToString()),
   item.Title,
   item.ReleaseYear,
   item.Summary
  });

  return Ok(outputModel);
 }

 [HttpGet("{id}")]
 public IActionResult Get(string id)
 {
  var orignalId = int.Parse(this.protector.Unprotect(id));

  var model = GetMovies(); 
  
  var outputModel = model.Where(item => item.Id == orignalId);

  return Ok(outputModel);
 }

代码解释

  • 在获取Movie列表的api中,我们使用了IDataProtector接口的Protect方法对Id字段进行了加密
  • 相应的在获取单个Movie对象的api中, 我们需要使用IDataProtector接口的Unprotect方法对Id字段进行解密。

最终效果

首先我们调用/api/movies, 返回结果如下, id字段已经被正确加密了

[{
 "id": "CfDJ8D9KlbQBeipPoQwll5uLR6ygyO6avkgI2teCQGZQShNwsxC9ApDdsnyYd1K5IyNHjhZcRoGd6W31se3W6TWM8H9UdLEPn4fJpS5uKkqUa0PMV6a0ZZHBQSnlGoisSnj29g",
 "title": "泰坦尼克号"
}, {
 "id": "CfDJ8D9KlbQBeipPoQwll5uLR6wkMUYyzflIzy3CwoMhcaO-np2WOy4czIL3WZd2FWi7Tsy119tDeFq7yAeye4o2W-KmbffpGXnTDZzNv2QbCrAm7-AyEN35g3pkfAYHa3X7aQ",
 "title": "我是谁"
}, {
 "id": "CfDJ8D9KlbQBeipPoQwll5uLR6x2AXM6ulCwts2-uQSfzIU8UquTz-OAZIl-49D5-CYYl5H4mfZH8VihhCBJ60MMrZOlZla9qvb8EIP6GYRkEap4nhktbzGxW0Qu5r3edm6_Kg",
 "title": "蜘蛛侠"
}, {
 "id": "CfDJ8D9KlbQBeipPoQwll5uLR6zDZeLtPIVlkRLCd_V6Mr2kTzWsCkfYgmS0-cqhFAOu4dUWGtx6d402_eKnObAOFUClEDdF4mrUeDQawE71DDa805umhbAvX2712i7UgYO5MA",
 "title": "钢铁侠"
}]

然后我们继续调用api, 查询钢铁侠的电影信息

/api/movies/CfDJ8D9KlbQBeipPoQwll5uLR6zDZeLtPIVlkRLCd_V6Mr2kTzWsCkfYgmS0-cqhFAOu4dUWGtx6d402_eKnObAOFUClEDdF4mrUeDQawE71DDa805umhbAvX2712i7UgYO5MA

结果也正确的返回了。

[{"id":4,"title":"钢铁侠"}]

带过期时间的数据保护器(Limited Lifetime)

.NET Core默认还提供了一种带过期时间的数据保护器, 这种数据保护器许多使用场景,最常用的场景就是当为一个重置密码操作的Token设置失效时间, 这样一旦超时的, Token就不能解密成功, 从而我们就可以认定重置密码操作超时了。

.NET Core中, 我们可以使用IDataProtector接口的ToTimeLimitedDataProtector方法创建一个带过期时间的数据保护器。

这里我们还是使用默认还是继续以上面的例子为例, 代码修改如下

private readonly ITimeLimitedDataProtector protector;

 public MoviesController(IDataProtectionProvider provider)
 {
  this.protector = provider.CreateProtector("protect_my_query_string")
     .ToTimeLimitedDataProtector();
 }

 [HttpGet]
 public IActionResult Get()
 {
  var model = GetMovies(); // simulate call to repository
  
  var outputModel = model.Select(item => new
  {
   Id = this.protector.Protect(item.Id.ToString(), 
          TimeSpan.FromSeconds(10)),
   item.Title,
   item.ReleaseYear,
   item.Summary
  });

  return Ok(outputModel);
 }

代码解释

  • 这里我们定义了一个ITimeLimitedDataProtector接口对象protector, 并在构造函数中使用ToTimeLimitedDataProtector方法,将一个普通的数据保护器转换成了一个带过期时间的数据保护器
  • 在获取Movie列表的api中, 我们依然使用Protect方法来加密Id字段, 与之前不同的是,这里我们加入了第二个TimeSpan参数,这个参数表示了当前加密的有效时间只有10秒。

最终效果

现在我们重新运行项目,还是和之前一样先调用/api/movies方法来获取Movies列表, 结果如下

[{
 "id": "CfDJ8D9KlbQBeipPoQwll5uLR6yzbDbZ931toH32VC6Jqg8DWsrmiLrOxOFFViH4QWZne43jwSVzBjzJIfctYKZniZKNVbr50RRIZpW2fe9UtPajEzBhI-H32Effm-F0ColUaA",
 "title": "泰坦尼克号"
}, {
 "id": "CfDJ8D9KlbQBeipPoQwll5uLR6zDDVymvftZK9lKBIjEyuoNTzOEu0SC2-qfTy6quXir2S8f3A1r44f9Yz3Sd_cyLZUp-_4gfJAasMfE8_ngYLrJmdsjN9LZ0g4vox0WJLjiGA",
 "title": "我是谁"
}, {
 "id": "CfDJ8D9KlbQBeipPoQwll5uLR6zL-M2jzv2HCeTiHjevkXvI2216NERplp43TOjCXtj4S52ll68sLyQNtG2FhhWlsOmFGvYY5G4gm5SKfASMMgE1jBr20xc2b_djWdLhWLIxnA",
 "title": "蜘蛛侠"
}, {
 "id": "CfDJ8D9KlbQBeipPoQwll5uLR6wAoZKCHTG0lvgYS3If_0_eAD30a2YV8RjNagwLXUdCSKsO3kyS58hqDqAPHw_KHwNpd-hjDFl3hFPa8LOWHyk901oc6ZuSxwzxFlljaVreFA",
 "title": "钢铁侠"
}]

等待10秒钟后,我们继续调用api, 查询钢铁侠的电影信息

/api/movies/CfDJ8D9KlbQBeipPoQwll5uLR6wAoZKCHTG0lvgYS3If_0_eAD30a2YV8RjNagwLXUdCSKsO3kyS58hqDqAPHw_KHwNpd-hjDFl3hFPa8LOWHyk901oc6ZuSxwzxFlljaVreFA

返回了错误信息CryptographicException: The payload expired at 9/29/2018 11:25:05 AM +00:00. 这说明当前加密的有效期已过, 不能正确解密了。

Tips: 使用Action Filter解密参数

在之前的代码中,我们在获取单个Movie的方法中,我们手动调用了Unprotected方法来解密id属性

[HttpGet("{id}")]
 public IActionResult Get(string id)
 {
  var orignalId = int.Parse(this.protector.Unprotect(id));

  var model = GetMovies(); // simulate call to repository
  
  var outputModel = model.Where(item => item.Id == orignalId);

  return Ok(outputModel);
 }

下面我们改用Action Filter来改进这部分代码。

首先我们创建一个DecryptReferenceFilter, 代码如下:

public class DecryptReferenceFilter : IActionFilter
 {
  private readonly IDataProtector protector;

  public DecryptReferenceFilter(IDataProtectionProvider provider)
  {
   this.protector = provider.CreateProtector("protect_my_query_string");
  }

  public void OnActionExecuting(ActionExecutingContext context)
  {
   object param = context.RouteData.Values["id"].ToString();
   var id = int.Parse(this.protector.Unprotect(param.ToString()));
   context.ActionArguments["id"] = id;
  }

  public void OnActionExecuted(ActionExecutedContext context)
  {

  }
 }

 public class DecryptReferenceAttribute : TypeFilterAttribute
 {
  public DecryptReferenceAttribute() :
   base(typeof(DecryptReferenceFilter))
  { }
 }

代码解释

  • 这里DecryptReferenceFilter实现了IActionFilter接口, 并实现了OnActionExecuting和OnActionExecuted方法
  • 在DecryptReferenceFilter类中,我们注入了默认的数据保护器提供器,并在构造函数中初始化了一个数据保护器
  • 在OnActionExecuting中我们从RouteData中获取到未解密的id字段, 然后将其解密之后,替换了之前未解密的id字段,这样ModelBinder就会使用解密后的字符串来绑定模型。

最终修改

最后我们修改一下获取单个Movie的api, 代码如下:

[HttpGet("{id}")]
 [DecryptReference]
 public IActionResult Get(int id)
 {
  var model = GetMovies();

  var outputModel = model.Where(item => item.Id == id);

  return Ok(outputModel);
 }

我们在获取单个Movie的方法上添加了DecryptReference特性。

运行代码之后,代码和之前的效果一样。

源码地址:http://xiazai.jb51.net/201809/yuanma/id_protector_jb51.rar

[!--infotagslink--]

相关文章

  • ASP.NET购物车实现过程详解

    这篇文章主要为大家详细介绍了ASP.NET购物车的实现过程,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-09-22
  • .NET Core下使用Kafka的方法步骤

    这篇文章主要介绍了.NET Core下使用Kafka的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-09-22
  • 在ASP.NET 2.0中操作数据之七十二:调试存储过程

    在开发过程中,使用Visual Studio的断点调试功能可以很方便帮我们调试发现程序存在的错误,同样Visual Studio也支持对SQL Server里面的存储过程进行调试,下面就让我们看看具体的调试方法。...2021-09-22
  • Win10 IIS 安装.net 4.5的方法

    这篇文章主要介绍了Win10 IIS 安装及.net 4.5及Win10安装IIS并配置ASP.NET 4.0的方法,本文给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下...2021-09-22
  • 详解.NET Core 3.0 里新的JSON API

    这篇文章主要介绍了详解.NET Core 3.0 里新的JSON API,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-09-22
  • .net数据库操作框架SqlSugar的简单入门

    这篇文章主要介绍了.net数据库操作框架SqlSugar的简单入门,帮助大家更好的理解和学习使用.net技术,感兴趣的朋友可以了解下...2021-09-22
  • ASP.NET Core根据环境变量支持多个 appsettings.json配置文件

    这篇文章主要介绍了ASP.NET Core根据环境变量支持多个 appsettings.json配置文件,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-09-22
  • 记一次EFCore类型转换错误及解决方案

    这篇文章主要介绍了记一次EFCore类型转换错误及解决方案,帮助大家更好的理解和学习使用asp.net core,感兴趣的朋友可以了解下...2021-09-22
  • .NET C#利用ZXing生成、识别二维码/条形码

    ZXing是一个开放源码的,用Java实现的多种格式的1D/2D条码图像处理库,它包含了联系到其他语言的端口。这篇文章主要给大家介绍了.NET C#利用ZXing生成、识别二维码/条形码的方法,文中给出了详细的示例代码,有需要的朋友们可以参考借鉴。...2020-06-25
  • 详解ASP.NET Core 中基于工厂的中间件激活的实现方法

    这篇文章主要介绍了ASP.NET Core 中基于工厂的中间件激活的实现方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-09-22
  • C#使用Ado.Net更新和添加数据到Excel表格的方法

    这篇文章主要介绍了C#使用Ado.Net更新和添加数据到Excel表格的方法,较为详细的分析了OLEDB的原理与使用技巧,可实现较为方便的操作Excel数据,需要的朋友可以参考下...2020-06-25
  • asp.net通过消息队列处理高并发请求(以抢小米手机为例)

    这篇文章主要介绍了asp.net通过消息队列处理高并发请求(以抢小米手机为例),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-09-22
  • Underscore源码分析

    Underscore 是一个 JavaScript 工具库,它提供了一整套函数式编程的实用功能,但是没有扩展任何 JavaScript 内置对象。这篇文章主要介绍了underscore源码分析相关知识,感兴趣的朋友一起学习吧...2016-01-02
  • ASP.NET单选按钮控件RadioButton常用属性和方法介绍

    RadioButton又称单选按钮,其在工具箱中的图标为 ,单选按钮通常成组出现,用于提供两个或多个互斥选项,即在一组单选钮中只能选择一个...2021-09-22
  • ASP.NET 2.0中的数据操作:使用两个DropDownList过滤的主/从报表

    在前面的指南中我们研究了如何显示一个简单的主/从报表, 该报表使用DropDownList和GridView控件, DropDownList填充类别,GridView显示选定类别的产品. 这类报表用于显示具有...2016-05-19
  • 详解.NET Core 使用HttpClient SSL请求出错的解决办法

    这篇文章主要介绍了.NET Core 使用HttpClient SSL请求出错的解决办法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2021-09-22
  • Python调用.NET库的方法步骤

    这篇文章主要介绍了Python调用.NET库的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-05-09
  • ASP.NET中iframe框架点击左边页面链接 右边显示链接页面内容

    这篇文章主要介绍了ASP.NET中iframe框架点击左边页面链接,右边显示链接页面内容的实现代码,感兴趣的小伙伴们可以参考一下...2021-09-22
  • 创建一个完整的ASP.NET Web API项目

    ASP.NET Web API具有与ASP.NET MVC类似的编程方式,ASP.NET Web API不仅仅具有一个完全独立的消息处理管道,而且这个管道比为ASP.NET MVC设计的管道更为复杂,功能也更为强大。下面创建一个简单的Web API项目,需要的朋友可以参考下...2021-09-22
  • ASP.NET连接MySql数据库的2个方法及示例

    这篇文章主要介绍了ASP.NET连接MySql数据库的2个方法及示例,使用的是MySQL官方组件和ODBC.NET,需要的朋友可以参考下...2021-09-22