Asp.Net Core Identity 隐私数据保护的实现_实用技巧

来源:脚本之家  责任编辑:小易  

ASP.NET和ASP的区别有以下几点:1、开发语言不同。ASP使用non-type脚本语言来开发应用程序或web网页,在web端添加代码和在客户端添加代码一致,导致代码杂乱无章。ASP.NET使用strongly-type编程语言开发应用程序或web网页,也可以使用.NET Framework框架开发,代码一致性较ASP好了许多。2、运行方式不同。ASP是解释运行的编程框架,使用js等脚本语言,运行速度慢。ASP.NET是编译性的编程框架,运行效率高且使用的是服务器端预先编译好的代码库。3、开发方式不同。ASP是面向过程设计语言,代码前后端不分离,写在同一个页面,代码杂乱无章,维护性和可重用性较低。ASP.NET则是面向对象设计语言,代码前后端分离,一部分专注于编写前端代码,一部分专注于编写相应的后台功能,代码井然有序,易维护且可重用性高。4、复杂性不同。ASP复杂性低,代码容易理解,但是开发效率不高。ASP.NET则较为复杂,但是具有相应的框架,程序设计快速且思路清晰www.zgxue.com防采集请勿采集本网。

前言

Asp.Net Core Identity 是Asp.Net Core 的重要组成部分,他为 Asp.Net Core 甚至其他 .Net Core 应用程序提供了一个简单易用且易于扩展的基础用户管理系统框架。它包含了基本的用户、角色、第三方登录、Claim等功能,使用 Identity Server 4 可以为其轻松扩展 OpenId connection 和 Oauth 2.0 相关功能。网上已经有大量相关文章介绍,不过这还不是 Asp.Net Core Identity 的全部,其中一个就是隐私数据保护。

ASP和asp.net的区别从编程语言种类,服务器,硬件环境的这三方面看区别,其中编程语言种类的区别是最主要的。第一,从编程语言种类看区别。ASP用VBScript、JavaScript等简单容易的脚本语言。结合

正文

乍一看,隐私数据保护是个什么东西,感觉好像知道,但又说不清楚。确实这个东西光说很难解释清楚,那就直接上图:

ASP.Net是什么 1 ASP.Net是什么?ASP.Net是建立在微软新一代.Net平台架构上,利用普通语言运行时(Common Language Runtime)在服务器后端为用户提供建立强大的企业级Web应用服务的编程框架。2.

这是用户表的一部分,有没有发现问题所在?用户名和 Email 字段变成了一堆看不懂的东西。仔细看会发现这串乱码好像还有点规律:guid + 冒号 +貌似是 base64 编码的字符串,当然这串字符串去在线解码结果还是一堆乱码,比如 id 为 1 的 UserName :svBqhhluYZSiPZVUF4baOQ== 在线解码后是²ðj†na”¢=•T†Ú9 。

ASP和ASP.NET是2个不同的东西,只是它们都是微软的,在IIS上可以很好的支持,所以可以混用在一起。ASP是解释型语言,在执行效率上不高,属于过期技术,但网上的源码不少,在开发低成本小网站上

这就是隐私数据保护,如果没有这个功能,那么用户名是明文存储的,虽然密码依然是hash难以破解,但如果被拖库,用户数据也会面临更大的风险。因为很多人喜欢在不同的网站使用相同的账号信息进行注册,避免遗忘。如果某个网站的密码被盗,其他网站被拖库,黑客就可以比对是否有相同的用户名,尝试撞库,甚至如果 Email 被盗,黑客还可以看着 Email 用找回密码把账号给 NTR 了。而隐私数据保护就是一层更坚实的后盾,哪怕被拖库,黑客依然看不懂里面的东西。

分析你的问题,你所谓C#.net大概是指在.net平台下使用C#做开发,这个说法就包括了在.net平台下用C#做B/S模式开发也就是包括Asp.net开发。C#只是一种开发语言,而.net是一个开发和用户体验环境。

然后是这个格式,基本能想到,冒号应该是分隔符,前面一个 guid,后面是加密后的内容。那问题就变成了 guid 又是干嘛的?直接把加密的内容存进去不就完了。这其实是微软开发框架注重细节的最佳体现,接下来结合代码就能一探究竟。

asp.net 不是语言也不是数据库,asp和asp.net都是一门技术,asp是老的,asp.net是微软推出的新的技术,注意,是技术,不是语言。asp.net用的语言是C#,用的框架是.Net框架,以上是程序开发的技术

启用隐私数据保护

//注册Identity服务(使用EF存储,在EF上下文之后注册)services.AddIdentity<ApplicationUser, ApplicationRole>(options =>{ //... options.Stores.ProtectPersonalData = true; //在这里启用隐私数据保护})//....AddPersonalDataProtection<AesProtector, AesProtectorKeyRing>(); //在这里配置数据加密器,一旦启用保护,这里必须配置,否则抛出异常

其中的AesProtector 和AesProtectorKeyRing 需要自行实现,微软并没有提供现成的类,至少我没有找到,估计也是这个功能冷门的原因吧。.Neter 都被微软给惯坏了,都是衣来伸手饭来张口。有没有发现AesProtectorKeyRing 中有KeyRing 字样?钥匙串,恭喜你猜对了,guid 就是这个钥匙串中一把钥匙的编号。也就是说如果加密的钥匙被盗,但不是全部被盗,那用户信息还不会全部泄露。微软这一手可真是狠啊!

接下来看看这两个类是什么吧。

AesProtector 是 ILookupProtector 的实现。接口包含两个方法,分别用于加密和解密,返回字符串,参数包含字符串数据和上面那个 guid,当然实际只要是字符串就行, guid 是我个人的选择,生成不重复字符串还是 guid 方便。

AesProtectorKeyRing 则是 ILookupProtectorKeyRing 的实现。接口包含1、获取当前正在使用的钥匙编号的只读属性,用于提供加密钥匙;2、根据钥匙编号获取字符串的索引器(我这里就是原样返回的。。。);3、获取所有钥匙编号的方法。

AesProtector

class AesProtector : ILookupProtector { private readonly object _locker; private readonly Dictionary<string, SecurityUtil.AesProtector> _protectors; private readonly DirectoryInfo _dirInfo; public AesProtector(IWebHostEnvironment environment) { _locker = new object(); _protectors = new Dictionary<string, SecurityUtil.AesProtector>(); _dirInfo = new DirectoryInfo($@"{environment.ContentRootPath}\App_Data\AesDataProtectionKey"); } public string Protect(string keyId, string data) { if (data.IsNullOrEmpty()) { return data; } CheckOrCreateProtector(keyId); return _protectors[keyId].Protect(Encoding.UTF8.GetBytes(data)).ToBase64String(); } public string Unprotect(string keyId, string data) { if (data.IsNullOrEmpty()) { return data; } CheckOrCreateProtector(keyId); return Encoding.UTF8.GetString(_protectors[keyId].Unprotect(data.ToBytesFromBase64String())); } private void CheckOrCreateProtector(string keyId) { if (!_protectors.ContainsKey(keyId)) { lock (_locker) { if (!_protectors.ContainsKey(keyId)) { var fileInfo = _dirInfo.GetFiles().FirstOrDefault(d => d.Name == $@"key-{keyId}.xml") ?? throw new FileNotFoundException(); using (var stream = fileInfo.OpenRead()) { XDocument xmlDoc = XDocument.Load(stream); _protectors.Add(keyId, new SecurityUtil.AesProtector(xmlDoc.Element("key")?.Element("encryption")?.Element("masterKey")?.Value.ToBytesFromBase64String() , xmlDoc.Element("key")?.Element("encryption")?.Element("iv")?.Value.ToBytesFromBase64String() , int.Parse(xmlDoc.Element("key")?.Element("encryption")?.Attribute("BlockSize")?.Value) , int.Parse(xmlDoc.Element("key")?.Element("encryption")?.Attribute("KeySize")?.Value) , int.Parse(xmlDoc.Element("key")?.Element("encryption")?.Attribute("FeedbackSize")?.Value) , Enum.Parse<PaddingMode>(xmlDoc.Element("key")?.Element("encryption")?.Attribute("Padding")?.Value) , Enum.Parse<CipherMode>(xmlDoc.Element("key")?.Element("encryption")?.Attribute("Mode")?.Value))); } } } } } }

AesProtectorKeyRing

class AesProtectorKeyRing : ILookupProtectorKeyRing { private readonly object _locker; private readonly Dictionary<string, XDocument> _keyRings; private readonly DirectoryInfo _dirInfo; public AesProtectorKeyRing(IWebHostEnvironment environment) { _locker = new object(); _keyRings = new Dictionary<string, XDocument>(); _dirInfo = new DirectoryInfo($@"{environment.ContentRootPath}\App_Data\AesDataProtectionKey"); ReadKeys(_dirInfo); } public IEnumerable<string> GetAllKeyIds() { return _keyRings.Keys; } public string CurrentKeyId => NewestActivationKey(DateTimeOffset.Now)?.Element("key")?.Attribute("id")?.Value ?? GenerateKey(_dirInfo)?.Element("key")?.Attribute("id")?.Value; public string this[string keyId] => GetAllKeyIds().FirstOrDefault(id => id == keyId) ?? throw new KeyNotFoundException(); private void ReadKeys(DirectoryInfo dirInfo) { foreach (var fileInfo in dirInfo.GetFiles().Where(f => f.Extension == ".xml")) { using (var stream = fileInfo.OpenRead()) { XDocument xmlDoc = XDocument.Load(stream); _keyRings.TryAdd(xmlDoc.Element("key")?.Attribute("id")?.Value, xmlDoc); } } } private XDocument GenerateKey(DirectoryInfo dirInfo) { var now = DateTimeOffset.Now; if (!_keyRings.Any(item => DateTimeOffset.Parse(item.Value.Element("key")?.Element("activationDate")?.Value) <= now && DateTimeOffset.Parse(item.Value.Element("key")?.Element("expirationDate")?.Value) > now)) { lock (_locker) { if (!_keyRings.Any(item => DateTimeOffset.Parse(item.Value.Element("key")?.Element("activationDate")?.Value) <= now && DateTimeOffset.Parse(item.Value.Element("key")?.Element("expirationDate")?.Value) > now)) { var masterKeyId = Guid.NewGuid().ToString(); XDocument xmlDoc = new XDocument(); xmlDoc.Declaration = new XDeclaration("1.0", "utf-8", "yes"); XElement key = new XElement("key"); key.SetAttributeValue("id", masterKeyId); key.SetAttributeValue("version", 1); XElement creationDate = new XElement("creationDate"); creationDate.SetValue(now); XElement activationDate = new XElement("activationDate"); activationDate.SetValue(now); XElement expirationDate = new XElement("expirationDate"); expirationDate.SetValue(now.AddDays(90)); XElement encryption = new XElement("encryption"); encryption.SetAttributeValue("BlockSize", 128); encryption.SetAttributeValue("KeySize", 256); encryption.SetAttributeValue("FeedbackSize", 128); encryption.SetAttributeValue("Padding", PaddingMode.PKCS7); encryption.SetAttributeValue("Mode", CipherMode.CBC); SecurityUtil.AesProtector protector = new SecurityUtil.AesProtector(); XElement masterKey = new XElement("masterKey"); masterKey.SetValue(protector.GenerateKey().ToBase64String()); XElement iv = new XElement("iv"); iv.SetValue(protector.GenerateIV().ToBase64String()); xmlDoc.Add(key); key.Add(creationDate); key.Add(activationDate); key.Add(expirationDate); key.Add(encryption); encryption.Add(masterKey); encryption.Add(iv); xmlDoc.Save( $@"{dirInfo.FullName}\key-{masterKeyId}.xml"); _keyRings.Add(masterKeyId, xmlDoc); return xmlDoc; } return NewestActivationKey(now); } } return NewestActivationKey(now); } private XDocument NewestActivationKey(DateTimeOffset now) { return _keyRings.Where(item => DateTimeOffset.Parse(item.Value.Element("key")?.Element("activationDate")?.Value) <= now && DateTimeOffset.Parse(item.Value.Element("key")?.Element("expirationDate")?.Value) > now) .OrderByDescending(item => DateTimeOffset.Parse(item.Value.Element("key")?.Element("expirationDate")?.Value)).FirstOrDefault().Value; } }

这两个类也是注册到 Asp.Net Core DI 中的服务,所有 DI 的功能都支持。

在其中我还使用了我在其他地方写的底层基础工具类,如果想看完整实现可以去我的 Github 克隆代码实际运行并体验。在这里大致说一下这两个类的设计思路。既然微软设计了钥匙串功能,那自然是要利用好。我在代码里写死每个钥匙有效期90天,过期后会自动生成并使用新的钥匙,钥匙的详细信息使用xml文档保存在项目文件夹中,具体见下面的截图。Identity 会使用最新钥匙进行加密并把钥匙编号一并存入数据库,在读取时会根据编号找到对应的加密器解密数据。这个过程由 EF Core 的值转换器(EF Core 2.1 增加)完成,也就是说 Identity 向 DbContext 中需要加密的字段注册了值转换器。所以我也不清楚早期 Identity 有没有这个功能,不使用 EF Core 的情况下这个功能是否可用。

如果希望对自定义用户数据进行保护,为对应属性标注 [PersonalData] 特性即可。Identity 已经对内部的部分属性进行了标记,比如上面提到的 UserName 。

有几个要特别注意的点:

1、在有数据的情况下不要随便开启或关闭数据保护功能,否则可能导致严重后果。

2、钥匙一定要保护好,保存好。否则可能泄露用户数据或者再也无法解密用户数据,从删库到跑路那种 Shift + Del 的事千万别干。

3、被保护的字段无法在数据库端执行模糊搜索,只能精确匹配。如果希望进行数据分析,只能先用 Identity 把数据读取到内存才能继续做其他事。

4、钥匙的有效期不宜过短,因为在用户登录时 Identity 并不知道用户是什么时候注册的,应该用哪个钥匙,所以 Identity 会用所有钥匙加密一遍然后查找是否有精确匹配的记录。钥匙的有效期越短,随着网站运行时间的增加,钥匙数量会增加,要尝试的钥匙也会跟着增加,最后对系统性能产生影响。当然这可以用缓存来缓解。

效果预览:

  本文地址:https://www.cnblogs.com/coredx/p/12210232.html

  完整源代码:Github

  里面有各种小东西,这只是其中之一,不嫌弃的话可以Star一下。

分析:ASP.NET不是一种语言,而是创建动态Web页的一种强大的服务器端技术,它是Microsoft.NET Framework中一套用于生成Web应用程序和Web服务的技术。ASP.NET页在服务器上执行,并生成发送到桌面或浏览器的标记(如HTML、XML或者WML)。可以使用任何.NET兼容语言(比如Visual Basic、C#)编写Web服务文件中的服务器端(而不是客户端)逻辑。ASP.NET页使用一种由事件驱动的、已编译的编程模型,这种模型可以提高性能并支持将用户界面层同应用程序逻辑层相隔离。注意:Web应用程序和Web服务可调用公共语言运行库的任意功能,例如类型安全、继承、语言互操作、版本控制和集成安全性等。ASP.NET技术有几个显著的特性,既:1、强大性和适应性 因为ASP.NET是基于通用语言的编译运行的程序,所以它的强大性和适应性,可以使它运行在支持.NET Framework所有平台上。ASP.NET同时也是language-independent语言独立化的,所以,可以选择一种最适合自己的语言来编写应用程序,或者可以用多种语言来写应用程序,这样的多种程序语言协同工作的能力可以保护基于COM+开发的程序,并能够完整的移植向ASP.NET。2、简单性和易学性.NET Framework封装了大量的类库,使ASP.NET完成一些常见的任务如表单的提交、客户端的身份验证、分布系统,并可以使网站配置变得非常简单。3、高效可管理性 ASP.NET使用一种字符基础的、分级的配置系统,使服务器环境和应用程序的设置更加简单。ASP.NET已经被刻意设计成为一种可以用于多处理器的开发工具,它在多处理器的环境下用特殊的无缝连接技术,可以很大的提高运行速度。即使现在的ASP.NET应用软件是为一个处理器开发的,将来多处理器运行时不需要任何改变都能提高运行效能。总结:ASP.NET不是一种语言,而是创建动态Web页的一种强大的服务器端技术,它是Microsoft.NET Framework中一套用于生成Web应用程序和Web服务的技术,利用公共语言运行时(Common Language Runtime)在服务器后端为用户提供建立强大的企业级Web应用服务的编程框架内容来自www.zgxue.com请勿采集。


  • 本文相关:
  • .net core3 用windows 桌面应用开发asp.net core网站
  • asp.net core 3.0 grpc拦截器的使用
  • asp.net core 3.0使用grpc的具体方法
  • 浅谈asp.net core的几种托管方式
  • asp.net core 授权详解
  • asp.net技巧:为blog打造个性日历
  • asp.net通过配置文件连接access的方法
  • aspx后台传递json到前台的两种接收方法推荐
  • asp.net 页面中动态增加的控件、添加事件
  • asp.net core 中的模型绑定操作详解
  • asp.net 获取机器硬件信息(cpu频率、磁盘可用空间、内存容量等)
  • asp.net mvc中htmlhelper控件7个大类中各个控件使用详解
  • .net core使用socket与树莓派进行通信详解
  • asp.net core全面扫盲贴
  • asp.net微信公众号添加菜单
  • ASP.NET是什么
  • ASP.NET和ASP的区别是什么?
  • .Net 和ASP.Net 有什么区别呢?
  • asp.net是什么?
  • ASP和asp.net有什么区别的呢?最主要的区别是什么呢?
  • ASP NET 是什么?
  • .net和asp的区别
  • asp.net与.net的区别
  • asp.net有什么用,主要是做什么的?
  • asp.net中与的区别?
  • 网站首页网页制作脚本下载服务器操作系统网站运营平面设计媒体动画电脑基础硬件教程网络安全基础应用实用技巧自学过程首页asp.net实用技巧.net core3 用windows 桌面应用开发asp.net core网站asp.net core 3.0 grpc拦截器的使用asp.net core 3.0使用grpc的具体方法浅谈asp.net core的几种托管方式asp.net core 授权详解asp.net技巧:为blog打造个性日历asp.net通过配置文件连接access的方法aspx后台传递json到前台的两种接收方法推荐asp.net 页面中动态增加的控件、添加事件asp.net core 中的模型绑定操作详解asp.net 获取机器硬件信息(cpu频率、磁盘可用空间、内存容量等)asp.net mvc中htmlhelper控件7个大类中各个控件使用详解.net core使用socket与树莓派进行通信详解asp.net core全面扫盲贴asp.net微信公众号添加菜单java正则表达式 pattern和matche未将对象引用设置到对象的实例 (asp.net(c#)网页跳转七种方法小结未能加载文件或程序集“xxx”或它asp.net“服务器应用程序不可用”asp.net中的几种弹出框提示基本实asp.net gridview 72般绝技asp.net生成excel并导出下载五种asp.net汉字转拼音和获取汉字首字asp.net对路径"xxxxx"asp.net html控件的file控件实现多文件上asp.net mvc5网站开发咨询管理的架构(十asp.net 保留文件夹详解asp.net微信开发(接口指南)asp.net mvc 控制器与视图asp.net cookie的处理流程深入分析asp.net core 通过中间件防止图片盗链的实asp.net利用jquery弹出层加载数据代码.net 中的装箱与拆箱实现过程asp.net 固定标题列与栏位的具体实现
    免责声明 - 关于我们 - 联系我们 - 广告联系 - 友情链接 - 帮助中心 - 频道导航
    Copyright © 2017 www.zgxue.com All Rights Reserved