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

背景介绍

在 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

(0)

相关推荐

  • Asp.Net Core中基于Session的身份验证的实现

    在Asp.Net框架中提供了几种身份验证方式:Windows身份验证.Forms身份验证.passport身份验证(单点登录验证). 每种验证方式都有适合它的场景: 1.Windowss身份验证通常用于企业内部环境,Windows Active Directory就是基于windows平台的身份验证实现: 2.Forms身份验证是Asp.Net框架中提出的另一种验证方式: 3.passport身份验证是微软提供的基于自己的lives账号实现的单点认证服务. Asp.net Core验证码登录遇到

  • .NET Core中Object Pool的多种用法详解

    前言 复用,是一个重要的话题,也是我们日常开发中经常遇到的,不可避免的问题. 举个最为简单,大家最为熟悉的例子,数据库连接池,就是复用数据库连接. 那么复用的意义在那里呢? 简单来说就是减少不必要的资源损耗. 除了数据库连接,可能在不同的情景或需求下,还会有很多其他对象需要进行复用,这个时候就会有所谓的 Object Pool(对象池). 小伙伴们应该也自己实现过类似的功能,或用ConcurrentBag,或用ConcurrentQueue,或用其他方案. 这也里分享一个在微软文档中的实现 Ho

  • .net core 读取本地指定目录下的文件的实例代码

    项目需求 asp.net core 读取log目录下的.log文件,.log文件的内容如下: xxx.log ------------------------------------------begin--------------------------------- 写入时间:2018-09-11 17:01:48  userid=1000  golds=10  -------------------------------------------end-------------------

  • .Net Core中使用Quartz.Net实践记录

    一.介绍 Quartz.NET是一个强大.开源.轻量的作业调度框架,是 OpenSymphony 的 Quartz API 的.NET移植,用C#改写,可用于winform和asp.net应用中.它灵活而不复杂.你能够用它来为执行一个作业而创建简单的或复杂的作业调度.它有很多特征,如:数据库支持,集群,插件,支持cron-like表达式等等. 通俗说它的功能是:比如说我想每天晚上2点让程序或网站执行某些代码,或者每隔5秒种我想查看是否有新的任务要处理等. Quartz.Net是根据Java的Qu

  • .NET Core中使用HttpClient的正确姿势

    前言 为了更方便在服务端调用 HTTP 请求,微软在 .NET Framework 4.x 的时候引入了 HttpClient.但 HttpClient 有很多严重问题,一直饱受诟病,比如 InfoQ 的这篇文章 t.cn/Evzy80y,吐槽了 HttpClient 不能立即关闭连接.性能消耗严重等的问题. Http协议的重要性相信不用我多说了,HttpClient相比传统JDK自带的URLConnection,增加了易用性和灵活性,它不仅是客户端发送Http请求变得容易,而且也方便了开发人员

  • .Net Core中间件之静态文件(StaticFiles)示例详解

    一.介绍 静态文件(static files),诸如 HTML.CSS.图片和 JavaScript 之类的资源会被 ASP.NET Core 应用直接提供给客户端. 在介绍静态文件中间件之前,先介绍 ContentRoot和WebRoot概念. ContentRoot:指web的项目的文件夹,包括bin和webroot文件夹. WebRoot:一般指ContentRoot路径下的wwwroot文件夹. 介绍这个两个概念是因为静态资源文件一般存放在WebRoot路径下,也就是wwwroot.下面

  • asp.net core webapi项目配置全局路由的方法示例

    一.前言 在开发项目的过程中,我新创建了一个controller,发现vs会给我们直接在controller头添加前缀,比如[Route("api/[controller]")],即在访问接口的时候会变成http://localhost:8000/api/values,但是如果控制器有很多个,或者要进行版本迭代时,我们会发现痛苦的时刻降临了,要一个一个的修改. 如果在这个时候可以进行全局配置前缀那真是福利呀,修改一处即可.为了能达到此目的我们就来运用一下吧. 二.配置 0.在配置前我们

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

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

  • 详解.Net Core中的日志组件(Logging)

    1.介绍 Logging组件是微软实现的日志记录组件包括控制台(Console).调试(Debug).事件日志(EventLog)和TraceSource,但是没有实现最常用用的文件记录日志功能(可以用其他第三方的如NLog.Log4Net.之前写过NLog使用的文章). 2.默认配置 新建.Net Core Web Api项目,添加下面代码. [Route("api/[controller]")] public class ValuesController : Controller

  • 详解.NET Core中的Worker Service

    当你想到ASP.NET Core时,可能会想到Web应用程序后端代码,包括MVC和WebAPI.MVC视图和Razor页面还允许使用后端代码生成带有HTML元素的前端UI.全新的Blazor更进一步,允许使用WebAssembly在Web浏览器中运行客户端.NET代码.最后,我们现在有了一个Worker Service应用程序的模板. 这是在ASP.NET Core早期预览中引入的.虽然项目模板最初列在Web模板下,但此后在向导中重新定位了一个级别.这是在.NET Core中创建长时间运行的跨平

  • 详解Android App中创建ViewPager组件的方法

    现在很多app一打开就是一个ViewPager,然后可以用手指滑,每滑一次就换一张图,底下还会有圈圈表示说现在滑到第几章~ 通常这些图片都是放功能简介或是使用教学之类的,我的需求很简单,就是上面提到的那样而已. 有两种做法,一种是找现有套件,查了一堆资料每个都跟我推荐ViewPagerIndicator这套,我之前也看过这套,只是看起来需要有fragment再加上google play范例好像载不到了,所以只好自己实做一个. Viewpager的实作可参考Android ViewPager使用详

  • 详解iOS App中UISwitch开关组件的基本创建及使用方法

    一.第一种创建UISwitch组件的方法,在代码中动态创建. 1.打开Xcode, 新建项目Switch,选择Single View Application. 2.打开ViewController.m文件在viewDidLoad方法里添加代码: 复制代码 代码如下: (void)viewDidLoad  {      [super viewDidLoad];      UISwitch *switchButton = [[UISwitch alloc] initWithFrame:CGRectM

  • 详解Android应用中使用TabHost组件进行布局的基本方法

    TabHost布局文件 我们先来了解一下布局文件的基本内容: 1. 根标签及id 设置Android自带id : XML布局文件中, 可以使用 标签设置, 其中的id 需要引用 android的自带id : android:id=@android:id/tabhost ; getHost()获取前提 : 设置了该id之后, 在Activity界面可以使用 getHost(), 获取这个TabHost 视图对象; 示例 : 复制代码 代码如下: <tabhost android:id="@a

  • 详解在WebStorm中添加Vue.js单文件组件的高亮及语法支持

    本文介绍了详解在WebStorm中添加Vue.js单文件组件的高亮及语法支持,分享给大家,具体如下: 一个小遗憾 能来看这篇文章的想必不用我来介绍vue是什么了.先让我们膜拜大神!vue项目的创建者尤大写了个sublime下语法高亮的插件,有人问他how about webstorm support?他是这么回答的.默哀一分钟. 添加高亮和语法支持 这个我是通过插件来实现的.网上目前有两个插件: 插件1:https://github.com/henjue/vue-for-idea 插件2:htt

  • 详解为什么Vue中不要用index作为key(diff算法)

    前言 Vue 中的 key 是用来做什么的?为什么不推荐使用 index 作为 key?常常听说这样的问题,本篇文章带你从原理来一探究竟. 另外本文的结论对于性能的毁灭是针对列表子元素顺序会交换.或者子元素被删除的特殊情况,提前说明清楚,喷子绕道. 本篇已经收录在 Github 仓库,欢迎 Star: https://github.com/sl1673495/blogs/issues/39 示例 以这样一个列表为例: <ul> <li>1</li> <li>

  • 详解Angular项目中共享模块的实现

    目录 一.共享CommonModule 二.共享MaterialModule 三.共享ConfirmDialog 一.共享CommonModule 创建share Modele:ng g m share import进来所有需要共享的模块都export出去, 暂时只有CommonModule,以后会有一些需要共享的组件. import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'

  • 详解Spring系列之@ComponentScan自动扫描组件

    目录 无注解方式component-scan使用 注解方式@ComponentScan使用 @ComponentScan的扫描规则 无注解方式component-scan使用 之前,我们需要扫描工程下一些类上所标注的注解,这些常用注解有: @Controller,@Service,@Component,@Repository 通过在Spring的配置文件中配置<context:component-scan>扫描对应包下扫描这些注解的方式: <beans xmlns="http:

随机推荐