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


    原文地址: PREVENTING INSECURE OBJECT REFERENCES IN ASP.NET CORE 2.0
    作者: Tahir Naushad

    背景介绍

    在 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.csConfigureService方法中配置使用默认的数据保护组件。

        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接口, 并实现了OnActionExecutingOnActionExecuted方法
    • 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特性。
    运行代码之后,代码和之前的效果一样。

    本篇源代码

  • 相关阅读:
    maven pom 详细配置
    寻找二叉树最远的叶子结点
    控制两个线程的启动顺序
    tensorflow之神经网络实现流程总结
    ubuntu服务器安装FTP服务
    ubuntu服务器 安装 seafile 个人网盘
    软件工程实践总结作业~
    Beta 答辩总结
    Beta 冲刺 (7/7)
    Beta 冲刺 (6/7)
  • 原文地址:https://www.cnblogs.com/lwqlun/p/9726191.html
Copyright © 2020-2023  润新知