65.9K
CodeProject 正在变化。 阅读更多。
Home

ASP.NET Core 2.1 中的类型化声明

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.73/5 (4投票s)

2019年2月3日

CPOL

5分钟阅读

viewsIcon

8378

如何使用类型化方法来使用声明

在本文中,我想讨论如何使用遵循类型化方法的声明,这个话题源于在应用程序的代码审查中遇到的一个 bug。

因此,我想通过使用具体类和很少使用的隐式转换功能来寻找一种方法来减轻未来的此类问题。

但首先,让我们看看声明是什么以及它们是如何使用的。

本文示例的 GitHub 存储库可以在这里找到。

目录

什么是声明?

如果我们看一下在授权或身份验证上下文中文声明的通用定义,那么声明就是应用程序对用户或角色的陈述。

正如维基百科文章中所述,声明的常见示例是,您可以将系统想象成一个夜总会保镖,而您的用户拥有一张由授权第三方颁发的驾驶执照,该执照声明了我们用户的一些信息,例如他们的出生日期,保镖可以验证他们的出生日期是否符合标准(年满 18 岁)以及驾照是否由授权方颁发。

根据前面描述的比喻,声明可以由其他受信任的系统颁发,它们作为特定用户或在某些情况下特定角色的元数据。这使得声明比用户仅仅拥有描述其权限的角色更具组合性,而不是仅仅使用基于角色的方法。

简而言之,声明是关于用户的元数据或属性,它声称了关于用户的一些信息,并且该声明由某个方颁发,由系统决定该方是否可信。

如何在 ASP.NET Core 2.1 中创建声明

现在我们将研究如何处理用户和角色的声明。

如果您正在构建 ASP.NET Core 应用程序,那么您很可能正在使用该库提供的Identity服务,这是一个向依赖项注入容器(服务提供程序)添加了几个类的包装器。我们最感兴趣的类是UserManager<T>。其中T分别代表我们的用户实体类型。

为了使用UserManager,我们需要将其注入到我们想要使用它的类中(关于此主题的几个话题可以在以前的文章中找到)。一旦我们有了UserManager的实例,我们就可以按照文档所述,通过添加、删除、替换声明来操作声明。

要创建声明,我们需要创建一个实例new Claim("ClaimType", "ClaimValue")。之后,使用此实例,我们可以将其添加到用户,或更新现有声明。

有关声明的所有构造函数重载的更多信息可以在这里找到。但正如我们所见,声明类型和声明值都是基于string的,这引出了我们当前的主题。

我们的自定义声明

因此,在这个示例中,我创建了一个名为UserCurrentDateTimeClaim的类,该类将表示我想在示例应用程序中使用的具体声明。类的定义如下:

using System;

namespace TypedClaims.Controllers
{
    class UserCurrentDateTimeClaim
    {
        private readonly DateTime _dateTimeValue;

        public UserCurrentDateTimeClaim(DateTime dateTimeValue)
        {
            _dateTimeValue = dateTimeValue;
        }
    }
}

此处需要说明的是,我们的自定义声明只能接收DateTime实例,当然,我们可以创建任意复杂或简单的声明。

现在我们需要让这个类能够充当声明,以便我们可以将其添加到用户,而无需创建适配器和其他复杂的转换机制。因此,我们将添加一个额外的方法,该方法将隐式地将我们的自定义声明转换为系统声明。

using System;
using System.Security.Claims;

namespace TypedClaims.Controllers
{
    class UserCurrentDateTimeClaim
    {
        private readonly DateTime _dateTimeValue;

        public UserCurrentDateTimeClaim(DateTime dateTimeValue)
        {
            _dateTimeValue = dateTimeValue;
        }

        public static implicit operator Claim(UserCurrentDateTimeClaim userCurrentDateTimeClaim)
        {
            return new Claim(nameof(UserCurrentDateTimeClaim), 
                             userCurrentDateTimeClaim._dateTimeValue.ToString());
        }
    }
}

implicit运算符将让我们的系统知道如何将我们的自定义类转换为Claim类型。因此,现在,我们可以像这样将此声明添加到用户await userManager.AddClaimAsync(currentUser, new UserCurrentDateTimeClaim(DateTime.Now));

这种方法的优点是,我们有 Consistent 的方式将特定声明添加到用户,该方法将尊重我们要存储的数据类型、ClaimType的名称以及我们感兴趣的任何其他附加声明信息,例如颁发者

当然,如果我们的声明包含一些验证逻辑,或者与底层存储类型一起工作的方***,我们可能还想将其从声明转换回我们的自定义类型,因此我们将添加另一个转换方法来执行反向操作。

using System;
using System.Security.Claims;

namespace TypedClaims.Controllers
{
    class UserCurrentDateTimeClaim
    {
        private readonly DateTime _dateTimeValue;

        public UserCurrentDateTimeClaim(DateTime dateTimeValue)
        {
            _dateTimeValue = dateTimeValue;
        }

        public static implicit operator Claim(UserCurrentDateTimeClaim userCurrentDateTimeClaim)
        {
            return new Claim(nameof(UserCurrentDateTimeClaim), 
                             userCurrentDateTimeClaim._dateTimeValue.ToString());
        }

        public static implicit operator UserCurrentDateTimeClaim(Claim claim)
        {
            return new UserCurrentDateTimeClaim(DateTime.Parse(claim.Value));
        }
    }
}

这样,一旦我们找到了我们想要的声明(我们将在示例中看到这一点),我们就可以将其转换回我们自己的自定义类型并对其进行操作。

示例

使用前面编写的自定义声明类,我在示例应用程序中创建了一个Post操作,该操作使用了我们的自定义声明。

public async Task AddUpdateDateClaim([FromServices]UserManager userManager)
        {
            var currentUser = await userManager.GetUserAsync(HttpContext.User);
            //tried it and we cannot convert directly into our own type on this line 
            //so first we need to retrieve the stored claim then switch it 
            //to our own implementation
            Claim updatedClaim = (await userManager.GetClaimsAsync(currentUser)).
                 FirstOrDefault(claim => claim.Type == nameof(UserCurrentDateTimeClaim)); 
            if (updatedClaim is null)
            {
                await userManager.AddClaimAsync
                    (currentUser, new UserCurrentDateTimeClaim(DateTime.Now));
            }
            else
            {
                UserCurrentDateTimeClaim exampleOfConvertingToCustomType = 
                 updatedClaim; // here, we have the stored claim converted back to our own type
                await userManager.ReplaceClaimAsync(currentUser, exampleOfConvertingToCustomType,
                    new UserCurrentDateTimeClaim(DateTime.Now));
            }
            return RedirectToAction("Index");
        }

结论

我希望您喜欢我使用类型化声明来消除由将声明存储为string(包括类型和值)所带来的问题的方法。

通过这种方法,您甚至可以将对象以 JSON 或二进制格式保存,然后对其进行序列化和反序列化,只要系统配置得当(考虑到 JSON 序列化选项),并且存储的值没有被其他方式篡改,那么您就应该有 Consistent 的方式在您自己的声明类之间切换。

如开头所述,

谢谢,下次再见。😀

© . All rights reserved.