前言
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 的全部,其中一个就是隐私数据保护。
正文
乍一看,隐私数据保护是个什么东西,感觉好像知道,但又说不清楚。确实这个东西光说很难解释清楚,那就直接上图:
这是用户表的一部分,有没有发现问题所在?用户名和 Email 字段变成了一堆看不懂的东西。仔细看会发现这串乱码好像还有点规律:guid + 冒号 +貌似是 base64 编码的字符串,当然这串字符串去在线解码结果还是一堆乱码,比如 id 为 1 的 UserName :svBqhhluYZSiPZVUF4baOQ== 在线解码后是²ðj"htmlcode">
//注册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") "key")"encryption")"masterKey")"key")"encryption")"iv")"key")"encryption")"BlockSize")"key")"encryption")"KeySize")"key")"encryption")"FeedbackSize")"key")"encryption")"Padding")"key")"encryption")"Mode")"htmlcode">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)"key")"id")"key")"id")".xml")) { using (var stream = fileInfo.OpenRead()) { XDocument xmlDoc = XDocument.Load(stream); _keyRings.TryAdd(xmlDoc.Element("key")"id")"key")"activationDate")"key")"expirationDate")"key")"activationDate")"key")"expirationDate")"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")"activationDate")"key")"expirationDate")"key")"expirationDate")"text-align: center">本文地址:https://www.cnblogs.com/coredx/p/12210232.html
完整源代码:Github
里面有各种小东西,这只是其中之一,不嫌弃的话可以Star一下。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。