有三个重要的类Claim, ClaimsIdentity, ClaimsPrincipal,我们以一个持有合法证件的学生Bob做比方:
在很多文章中描述的时候,会反复出现Identity、ClaimsIdentity,阅读的朋友可以把它们当做一回事!Identity我们在假设应用场景或描述的时候用的比较多,ClaimsIdentity是在ASP.NET Core中实现的代码中使用。这样说会有些不严谨,但便于理解。
下边就开始围绕上边这段话展开描述:
在开始学习标识系统(Identity system)之前,很有必要搞清楚什么是基于声明的身份认证。
我们假设这样一个场景。Bob是一名大学生,准备去银行为自己开户。银行工作人员需要Bob提供他的有效证件,于是Bob就把自己的驾照提供给了银行工作人员。银行工作人员可以从驾照上获取到Bob的个人信息,例如姓名、出生日期和地址等。
Bob希望能够享受到银行针对大学生的优惠政策,于是银行工作人员又请Bob提供了学生证,同时从上边可以获取到姓名、所在的大学名称、院系以及学号等信息。(这里提供别人的学生证一定是不行的!)
使用以上生活中典型的应用场景,可以帮助我们进一步去理解基于声明(Claims-based Authentication)的身份认证。这个例子中驾照和身份证就是Bob拥有两个标识(Identity),就相当于有效的身份证明,即证明你是你的那张纸,这就是标识(Identity)。当然还可以提供更多的Identity,比如护照、户口本等等。
通过上边的描述,只需要清楚这么几点内容:
是不是很好理解,我们用图表示出来:
Bob去开户,先提供了一个Identity(这里就是驾照),接着又提供了一个Identity(学生证)
清楚了这些,我们继续看
ASP.NET Core 是开源的,这能够让我们非常方便的去学习和理解它是如何构成并运行的。源码可以去github上查看。
也可以把源码下载下来,然后查看源码,路径为:
corefx-master(解压后主文件夹)/src/System.Security.Claims/src/System/Security
一个用户(User,它通常出现在HttpContext中)被声明成ClaimsPrincipal类型(继承自IPrincipal接口)。可能你会感到疑惑,这个类型名为什么不命名成ClaimsIdentity呢?相信看过下边的内容能够让你明白是怎么回事。
ClaimsPrincipal类来自System.Security.Claims命名空间。
public class ClaimsPrincipal : IPrincipal
{
...........
...........
public virtual IIdentity Identity { get; }
public virtual IEnumerable<ClaimsIdentity> Identities { get; }
public virtual IEnumerable<Claim> Claims { get; }
...........
...........
public virtual bool HasClaim(Predicate<Claim> match);
public virtual bool HasClaim(string type, string value);
public virtual bool IsInRole(string role);
...........
...........
}
从ClaimsPrincipal类中可以看到有一个返回类型为ClaimsIdentity集合的Identities属性,代表着一个User可以拥有多个Identity(本示例中就是Bob拥有驾照和学生证)。
在这里的另外一个属性Identity,不要被迷惑,他返回的是ClaimsPrincipal中主要的(如果有多个)那一个ClaimIdentity,ASP.NET Core中会获取第一个ClaimsIdentity。
另外一个重要的属性Claims属性,每一个ClaimsIdentity都有由很多个Claim组成,Claims属性返回了ClaimsPrincipal中所有ClaimsIdentity所包含得全部Claim信息所组成了一个集合。
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);
…
…
}
现在我们把这个假设的应用场景用图示再总结一下:
现在我们创建一段网页中经常出现的登录代码:
public async Task<IActionResult> Login(string returnUrl = null)
{
const string Issuer = "https://gov.uk";
var claims = new List<Claim> {
new Claim(ClaimTypes.Name, "Andrew", ClaimValueTypes.String, Issuer),
new Claim(ClaimTypes.Surname, "Lock", ClaimValueTypes.String, Issuer),
new Claim(ClaimTypes.Country, "UK", ClaimValueTypes.String, Issuer),
new Claim("ChildhoodHero", "Ronnie James Dio", ClaimValueTypes.String)
};
var userIdentity = new ClaimsIdentity(claims, "Passport");
var userPrincipal = new ClaimsPrincipal(userIdentity);
await HttpContext.Authentication.SignInAsync("Cookie", userPrincipal,
new AuthenticationProperties
{
ExpiresUtc = DateTime.UtcNow.AddMinutes(20),
IsPersistent = false,
AllowRefresh = false
});
return RedirectToLocal(returnUrl);
}
这段代码中,我们首先采用硬编码构建了Claims,但在实际应用中信息可能来源于数据库或其它地方。
Once you have built up your claims you can create a new ClaimsIdentity, passing in your claim list, and specifying the AuthenticationType (to ensure that your identity has IsAuthenticated=true). Finally you can create a new ClaimsPrincipal using your identity and sign the user in. In this case we are telling the AuthenticationManager to use the “Cookie” authentication handler, which we must have configured as part of our middleware pipeline.
然后使用Claims作为参数创建了ClaimsIdentity。最后使用ClaimsIdentity创建了ClaimsPrincipal。告诉AuthenticationManager使用Cookie处理方法验证,我们需要在configured中配置中间件管道。
在ASP.NET Core Identity 中使用Identity对象的简单交互已经创建了一些API,例如SignInManager, UserManager ,RoleManager等,在ASP.NET Core 项目中通过依赖注入就可以使用它们。
例如,登录管理就通过下边一个public方法来完成登录:
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());
}
姓名:Bob
就是一个Claim,生日:2009.9.15
这也是一个Claim。其中姓名或生日就是这个Claim的一种类型,即Claimtype。...
var claimsIdentity = new ClaimsIdentity(new Claim[] { new Claim(ClaimTypes.Name, loginName) }, "Basic");
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
await context.Authentication.SignInAsync(_cookieAuthOptions.AuthenticationScheme, claimsPrincipal);
...
要用Cookie代表一个通过验证的主体,必须包含Claim, ClaimsIdentity, ClaimsPrincipal这三个信息,以一个持有合法驾照的人做比方,ClaimsPrincipal就是持有证件的人Bob,ClaimsIdentity就是证件驾照,"Basic"就是证件类型(这里假设是驾照),Claim就是驾照中的信息。
如果感觉困扰,可以再看下dudu博客的这篇文章
这篇教程中,因为自己在理解的时候花了不少时间,请大家指证!