@
前言
有三个重要的类Claim, ClaimsIdentity, ClaimsPrincipal,我们以一个持有合法证件的学生Bob做比方:
- ClaimsPrincipal就是持有证件的学生Bob;
- ClaimsIdentity就是学生Bob的证件:驾照或学生证;
- Claim就是Bob驾照中的各项信息:姓名、性别等;
在很多文章中描述的时候,会反复出现Identity、ClaimsIdentity,阅读的朋友可以把它们当做一回事!Identity我们在假设应用场景或描述的时候用的比较多,ClaimsIdentity是在ASP.NET Core中实现的代码中使用。这样说会有些不严谨,但便于理解。
下边就开始围绕上边这段话展开描述:
基于声明的认证(Claims-based Authentication)
在开始学习标识系统(Identity system)之前,很有必要搞清楚什么是基于声明的身份认证。
应用场景一
我们假设这样一个场景。Bob是一名大学生,准备去银行为自己开户。银行工作人员需要Bob提供他的有效证件,于是Bob就把自己的驾照提供给了银行工作人员。银行工作人员可以从驾照上获取到Bob的个人信息,例如姓名、出生日期和地址等。
Bob希望能够享受到银行针对大学生的优惠政策,于是银行工作人员又请Bob提供了学生证,同时从上边可以获取到姓名、所在的大学名称、院系以及学号等信息。(这里提供别人的学生证一定是不行的!)
使用以上生活中典型的应用场景,可以帮助我们进一步去理解基于声明(Claims-based Authentication)的身份认证。这个例子中驾照和身份证就是Bob拥有两个标识(Identity),就相当于有效的身份证明,即证明你是你的那张纸,这就是标识(Identity)。当然还可以提供更多的Identity,比如护照、户口本等等。
通过上边的描述,只需要清楚这么几点内容:
Claim
- 就是一个键值对,例如:Name:Bob;
- Claim可以是姓名、出身日期、地址、所在大学、学号等等信息。
Identity
- 很多条Claim组成了一个Identity;
- Identity就是一个有效的身份证明,即证明你是你的那张纸,例如驾照、学生证等等;
- 一个人可以拥有很多个Identity;
是不是很好理解,我们用图表示出来:
Bob去开户,先提供了一个Identity(这里就是驾照),接着又提供了一个Identity(学生证)
清楚了这些,我们继续看
在ASP.NET Core 中Identity是如何实现的
ASP.NET Core 是开源的,这能够让我们非常方便的去学习和理解它是如何构成并运行的。源码可以去github上查看。
也可以把源码下载下来,然后查看源码,路径为:
corefx-master(解压后主文件夹)/src/System.Security.Claims/src/System/Security
类ClaimsPrincipal
一个用户(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信息所组成了一个集合。
考察另外一个重要的类ClaimsIdentity
public class ClaimsIdentity : IIdentity
{
...........
...........
public virtual string AuthenticationType { get; }
public virtual string Name { get; }
public virtual bool IsAuthenticated { get; }
public virtual IEnumerable
public virtual IEnumerable
public virtual Claim FindFirst(string type);
public virtual bool HasClaim(string type, string value);
...........
...........
}
- 这里的AuthenticationType属性从字面意思就能很好的理解,返回认证的类型,在本示例中就可能是驾照或者学生证。在ASP.NET Core 中也可能是例如:微信、QQ、Cookies、Basic、Windows、Google等等。用AuthenticationType来验证和确定与身份相关联的声明用的什么方法,通俗点讲这是个什么证。
- 还有一个IsAuthenticated属性,用来获取Identity是否被验证,大家可能会感觉多余,在没有通过验证的时候又怎么会得到一个拥有Claims的Identity呢?有一个典型应用场景就是,允许游客访问你的网站,允许使用购物车。这个游客一样拥有由声明(Claims)组成的标识(Identity),只是这位游客还没有被验证。这一非常重要的区别需要牢记。
在ASP.NET Core 创建一个ClaimsIdentity的时候,IsAuthenticationType总会被初始化为true。一个通过验证的用户一定会是true,相反未被验证的游客,此属性为false。 - Claims是组成这个Identity的全部Claim集合。
现在我们把这个假设的应用场景用图示再总结一下:
在ASP.NET Core Identity中使用
现在我们创建一段网页中经常出现的登录代码:
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());
}
总结
- Claim就是一个键值对,用来描述一个特新,比如
姓名:Bob
就是一个Claim,生日:2009.9.15
这也是一个Claim。其中姓名或生日就是这个Claim的一种类型,即Claimtype。 - 一组claim就构成了一个identity,具有这些claims的identity就是 ClaimsIdentity ,也可以把ClaimsIdentity理解为“证件”,驾照就是一种ClaimsIdentity,学生证也是一种ClaimIdentity。
- ClaimsIdentity的持有者就是ClaimsPrincipal ,一个ClaimsPrincipal可以持有多个ClaimsIdentity,就比如Bob既持有驾照(ClaimIdentity),又持有身份证(ClaimIdentity)。
理解了Claim, ClaimsIdentity, ClaimsPrincipal这三个概念,就能理解生成登录Cookie为什么要用下面的代码?
...
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博客的这篇文章
这篇教程中,因为自己在理解的时候花了不少时间,请大家指证!