在本文中,将探讨什么是基于声明的认证,ASP.NET Core Identity如何实现它,以及身份系统的整体工作原理。
ASP.NET Core Identity 是随 .NET Core 一起提供的新型会员系统。与之前的版本不同,它采用了基于声明的认证,这是与之前版本的 ASP.NET Identity 最大的不同之一。
在开始探索身份系统之前,需要先了解基于声明的认证。
让考虑一个场景:Bob 是一名大学生,他去银行开户。银行经理想要看到一些能够证明 Bob 身份的文件。Bob 出示了他的驾照,上面有他的名字、出生日期和地址。
现在 Bob 还想申请银行只为学生提供的折扣。经理再次要求提供证明 Bob 是学生的文件。这次 Bob 出示了他的学生证,上面有他的名字、大学名称、系名称和学生编号。
这个现实生活中的场景可以用来类比基于声明的认证。在这里,Bob 是使用基于声明的认证的用户。他使用了两种身份(用户可以有多个身份)—— 驾照和学生证。如果身份附加了声明——比如名字、出生日期、学生编号等。
ASP.NET Core 的一个好处是它是开源的,如果需要,可以查看源代码以了解其工作原理。ASP.NET Core Identity的源代码可以在以下 GitHub 仓库中找到:
https://github.com/aspnet/Identity
在当前的 ASP.NET Core 实现中,用户是 ClaimsPrincipal 类型,它实现了 IPrincipal 接口。ClaimsPrincipal 类在 System.Security.Claims 命名空间下实现。
public class ClaimsPrincipal : IPrincipal
{
public virtual IIdentity Identity { get; }
public virtual IEnumerable Identities { get; }
public virtual IEnumerable Claims { get; }
public virtual bool HasClaim(Predicate match);
public virtual bool HasClaim(string type, string value);
public virtual bool IsInRole(string role);
}
从实现中可以看到,ClaimsPrincipal 类有一个 Identities 属性,它返回 ClaimsIdentity 的集合。这意味着用户可以有多个身份。
还有一个属性 Identity。不要被它迷惑,它返回与此 ClaimsPrincipal 关联的主要声明身份。另一个重要的属性是 Claims,它返回一个集合,包含与此 ClaimsPrincipal 关联的所有 ClaimsIdentity 的所有声明。
public class ClaimsIdentity : IIdentity
{
public virtual string AuthenticationType { get; }
public virtual string Name { get; }
public virtual bool IsAuthenticated { get; }
public virtual IEnumerable Claims { get; }
public virtual IEnumerable FindAll(Predicate match);
public virtual Claim FindFirst(string type);
public virtual bool HasClaim(string type, string value);
}
AuthenticationType 返回类型,可能是 Basic、Windows、Cookie 等。Claims 属性返回与此身份关联的声明集合。
现在如果尝试将 Bob 的例子映射到这些类,它将看起来像这样:
ASP.NET Core Identity 实现了一些 API(如 SignInManager、UserManager、RoleManager 等),简化了与身份对象的交互。在 ASP.NET Core 项目中工作时,依赖注入将为这些类提供对象,以便可以使用它们。
例如,SignInManager 实现了以下公共方法来登录用户:
public virtual async Task SignInAsync(TUser user, AuthenticationProperties authenticationProperties,
string authenticationMethod = null)
{
var userPrincipal = await CreateUserPrincipalAsync(user);
if (authenticationMethod != null)
{
userPrincipal.Identities.First().AddClaim(
new Claim(ClaimTypes.AuthenticationMethod, authenticationMethod));
}
await Context.SignInAsync(IdentityConstants.ApplicationScheme,
userPrincipal,
authenticationProperties ?? new AuthenticationProperties());
}