码迷,mamicode.com
首页 > Web开发 > 详细

Asp.Net Identity学习笔记+MVC5默认项目解析_授权&Claim

时间:2016-08-13 06:28:21      阅读:10303      评论:0      收藏:0      [点我收藏+]

标签:

Identity学习笔记


授权

最常用的授权就是给Controller或Action打上[Authorize]标签
没错这个就是MVC的授权过滤器
看下他的授权关键源码

  1. protected virtual bool AuthorizeCore(HttpContextBase httpContext)
  2. {
  3. if (httpContext == null)
  4. {
  5. throw new ArgumentNullException("httpContext");
  6. }
  7. IPrincipal user = httpContext.User;
  8. return user.Identity.IsAuthenticated && (this._usersSplit.Length <= 0 || this._usersSplit.Contains(user.Identity.Name, StringComparer.OrdinalIgnoreCase)) && (this._rolesSplit.Length <= 0 || this._rolesSplit.Any(new Func<string, bool>(user.IsInRole)));
  9. }

我个人猜测关键大概就在最后一行user.Identity.IsAuthenticated,如果用户验证成功则为true,这是个只读属性,肯定会在某个地方设置为true才能授权成功.
就拿之前的登入案例的第一段代码说,授权的关键就在于

  1. ClaimsIdentity ident = await UserManager.CreateIdentityAsync(user,DefaultAuthenticationTypes.ApplicationCookie);
  2. AuthManager.SignOut();
  3. AuthManager.SignIn(new AuthenticationProperties {IsPersistent = false}, ident);
  4. return Redirect(returnUrl);

创建用户标识(ClaimsIdentity),保存到user.Identity(通过方法AuthManager.SignIn),ClaimsIdentity实现IIdentity(它有个成员IsAuthenticated).
看下ClaimsIdentity源码

  1. public virtual bool IsAuthenticated
  2. {
  3. get
  4. {
  5. return !string.IsNullOrEmpty(this.m_authenticationType);
  6. }
  7. }

代码中是判断授权类别字段是否存在,也就是当你创建了有授权类别的ClaimsIdentity就算是授权了

以角色授权

角色授权是更细粒度的授权,上面简单使用[Authorize]只区分了登入用户和未登入用户.
使用角色授权可以赋予用户角色,并限制角色的访问,最简单的方式是给Action添加[Authorize(Roles="Role1,Role2")]限制能访问该Action的用户必须是XXX角色.

角色的使用方法与用户很类似,都要用到IdentityRole和RoleManager
在MVC默认项目中并没有用到角色授权,但我也简单介绍一下,如果想详细学习请看Pro Asp.Net 5 platform Identity部分

IdentityRole

对应的表为AspNetRoles
技术分享
用户与角色是多对多关心,所以还有一个AspNetUserRoles
技术分享

  1. public class ApplicationRole : IdentityRole
  2. {
  3. //可扩展字段
  4. }

IdnetityRole的默认成员

名称 描述
Id 定义角色的唯一标识符
Name 定义角色名称
Users 返回一个代表角色成员的IdentityUserRole对象集合

RoleManager

  1. public class ApplicationRoleManager : RoleManager<ApplicationRole>, IDisposable
  2. {
  3. public ApplicationRoleManager(RoleStore<ApplicationRole> store) : base(store) { }
  4. //同样地,用于Owin
  5. public static ApplicationRoleManager Create(
  6. IdentityFactoryOptions<ApplicationRoleManager> options,
  7. IOwinContext context)
  8. {
  9. return new ApplicationRoleManager(new RoleStore<ApplicationRole>(context.Get<ApplicationDbContext>()));
  10. }
  11. }

RoleManager常用成员

名称 描述
CreateAsync(role) 创建一个新角色
DeleteAsync(role) 删除指定角色
FindByIdAsync(id) 找到指定ID的角色
FindByNameAsync(name) 找到指定名称的角色
RoleExistsAsync(name) 如果存在指定名称的角色,返回true
UpdateAsync(role) 将修改存储到指定角色
Roles 返回已被定义的角色枚举

注意

  • 你可以自己继承IdentityRole扩展自己的Role,但是默认情况下EF Code First会映射这个继承关系,直接的影响是你的AspNetRoles表会多出一个字段Discriminator,用于区分你的Role和IdentityRole.
  • 我尝试隐藏这个字段不管是用Fluent Api方式还是使用模型注解都失败了,求指导.
  • 有人可能会问为什么继承IdentityUser自定用户的时候为什么不会自动生成Discriminator,其实我也很疑惑,我初步的猜想是因为用的数据库上下文是ApplicationDbContext : IdentityDbContext<ApplicationUser>,手动指定了要用的用户类,这个IdentityDbContext还有个重载IdentityDbContext<TUser, TRole, TKey, TUserLogin, TUserRole, TUserClaim>,我尝试过IdentityDbContext<ApplicationUser, ApplicationRole, string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>(手动指定我的用户类和角色类,但最后还是报错,求指点)
  • 根据试验用户名,Id和角色信息会保存到浏览器Cookie中,所以当更新某个用户的角色信息后要记得更新它浏览器的Cookie
  • 当授权检测失败(用户未登入,用户不是XX角色…)都会跳转到LoginPath(在app.UseCookieAuthentication设置的),所以某个已登入用户访问未授权页面也会跳转到这个LoginPath.你可以添加一小段代码反馈正确的信息给这个用户
  1. public ActionResult Login(string returnUrl) {
  2. if (HttpContext.User.Identity.IsAuthenticated) {
  3. return View("Error", new string[] { "Access Denied" });
  4. }
  5. //省略

基于声明的(Claims)

这部分我讲从较具体的层面(代码)解释声明,其实我是看不懂别人的抽象的说法.

虽然Claims翻译成声明但实际这个特性与声明一词好像没什么关系.因此下文我都尽量地使用Claims.
先亮一个定义

声明(Claims)是关于用户的一些信息片段

另外,我打算使用上面角色授权的例子来解释Claims,因为角色授权内部已经用到了Claims.

在开始解释之前先介绍几个比较重要的类/接口,前面有用户这些类/接口

IPrincipal

这个安全上下文对象包含了上面的identity以及一些角色和组的信息,每一个线程都会关联一个Principal的对象,但是这个对象是属性进程或者AppDomain级别的。
在控制器中User,HttpContext.User,System.Web.HttpContext.Current.User,都是同一个对象IPrincipal(实际是System.Security.Claims.ClaimsPrincipal类型)

名称 描述
Identity 返回IIdentity接口的实现,它描述了与请求相关联的用户
IsInRole(role) 如果用户是指定角色的成员,则返回true。参见“以角色授权用户”小节,其中描述了以角色进行授权管理的细节

IIdentity

通过User.Identity能获取

名称 描述
AuthenticationType 返回一个字符串,描述了用于认证用户的机制(到现在为止都是用ApplicationCookie)
IsAuthenticated 如果用户已被认证,返回true。
Name 返回当前用户的用户名

其实现类

CalimsIdentity

名称 描述
Claims 返回表示用户声明(Claims)的Claim对象枚举
AddClaim(claim) 给用户添加一个声明(Claim)
AddClaims(claims) 给用户添加Claim对象的枚举。
HasClaim(predicate) 如果用户含有与指定谓词匹配的声明(Claim)时,返回true。参见“运用声明(Claims)”中的示例谓词
RemoveClaim(claim) 删除用户的声明(Claim)。

Claim

名称 描述
Issuer 返回提供声明(Claim)的系统名称
Subject 返回声明(Claim)所指用户的ClaimsIdentity对象
Type 返回声明(Claim)所表示的信息类型
Value 返回声明(Claim)所表示的信息片段

声明提供者不只有应用程序本身,也可以通过外部提供声明,比如第三方登入可以提供声明

下面就把上面的代码搬下来讲

用户登入

  1. ClaimsIdentity ident = await UserManager.CreateIdentityAsync(user,DefaultAuthenticationTypes.ApplicationCookie);
  2. AuthManager.SignOut();
  3. AuthManager.SignIn(new AuthenticationProperties {IsPersistent = false}, ident);
  4. return Redirect(returnUrl);

首先创建Identity
使用UserManager.CreateIdentityAsync=>内部调用ClaimsIdentityFactory.CreateAsyncIdentity关键的创建过程在这个方法内

ClaimsIdentity claimsIdentity = new ClaimsIdentity(authenticationType, this.UserNameClaimType, this.RoleClaimType);
claimsIdentity.AddClaim(new Claim(this.UserIdClaimType, this.ConvertIdToString(user.Id), "http://www.w3.org/2001/XMLSchema#string"));
claimsIdentity.AddClaim(new Claim(this.UserNameClaimType, user.UserName, "http://www.w3.org/2001/XMLSchema#string"));
claimsIdentity.AddClaim(new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", "ASP.NET Identity", "http://www.w3.org/2001/XMLSchema#string"));
if (manager.SupportsUserSecurityStamp)
{
claimsIdentity.AddClaim(new Claim(this.SecurityStampClaimType, await manager.GetSecurityStampAsync(user.Id).WithCurrentCulture<string>()));
}
//...

加了4个Claim,这四个Claim用来保存用户Id,用户名,identityprovider(不知道用来干嘛),SecurityStamp(不知道用来干嘛).
这样直接造成的影响是
技术分享
展开其中一个看看
技术分享

到这里应该能理解声明(Claims)是关于用户的一些信息片段这句话吧.传统的做法会在登入后把用户信息保存到Session中(可能直接保存User类或者新创建一个UserInfo类保存之),在用户再访问网站时候能获取这个用户的信息.现在直接使用Claims来添加用户的信息片段,使用上更加的灵活,细腻,可扩展.

用户授权

在使用Identity时候,可以使用这种方式限制访问的用户,实际上内部用到了Claims

  1. [Authorize(Roles ="角色1,角色2",Users="用户1,用户2")]
  2. public ActionResult ActionName(){}

下面代码搬运自AuthorizeAttribute的AuthorizeCore最后一行代码,这是授权过滤器的核心

  1. return user.Identity.IsAuthenticated && (this._usersSplit.Length <= 0 || this._usersSplit.Contains(user.Identity.Name, StringComparer.OrdinalIgnoreCase)) && (this._rolesSplit.Length <= 0 || this._rolesSplit.Any(new Func<string, bool>(user.IsInRole)));
  2. //_usersSplit,_rolesSplit是Users,Roles字符串用逗号切割生成的字符串数组

解释下这段代码

  • 用户必须登入
  • 当前用户的用户名必须存在于_usersSplit
  • 用户必须在_rolesSplit用户组中

IsInRole
user.IsInRole是IPrincipal中的方法

  1. public virtual bool IsInRole(string role)
  2. {
  3. for (int i = 0; i < this.m_identities.Count; i++)
  4. {
  5. //查看当前用户的Identity中是否有RoleClaimType声明,且这个声明的值为role
  6. if (this.m_identities[i] != null && this.m_identities[i].HasClaim(this.m_identities[i].RoleClaimType, role))
  7. {
  8. return true;
  9. }
  10. }
  11. return false;
  12. }

看下默认情况下的RoleClaimType
技术分享
再看下HasClaim

  1. public virtual bool HasClaim(string type, string value)
  2. {
  3. if (type == null)
  4. {
  5. throw new ArgumentNullException("type");
  6. }
  7. if (value == null)
  8. {
  9. throw new ArgumentNullException("value");
  10. }
  11. //遍历当前Identity所有Claims,找到类型为type且值为value,如果找到了则返回true
  12. foreach (Claim current in this.Claims)
  13. {
  14. if (current != null && current != null && string.Equals(current.Type, type, StringComparison.OrdinalIgnoreCase) && string.Equals(current.Value, value, StringComparison.Ordinal))
  15. {
  16. return true;
  17. }
  18. }
  19. return false;
  20. }

由于授权内部是基于Claim的,所以可以在程序运行时动态地给用户添加角色User.Identity.Claims.Add(new Claim(ClaimTypes.Role, "Role")).
你甚至可以自定义Claim授权器

  1. public class ClaimsAccessAttribute : AuthorizeAttribute {
  2. public string Issuer { get; set; }
  3. public string ClaimType { get; set; }
  4. public string Value { get; set; }
  5. protected override bool AuthorizeCore(HttpContextBase context) {
  6. return context.User.Identity.IsAuthenticated
  7. && context.User.Identity is ClaimsIdentity
  8. && ((ClaimsIdentity)context.User.Identity).HasClaim(x =>
  9. x.Issuer == Issuer && x.Type == ClaimType && x.Value == Value
  10. );
  11. }
  12. }
  13. [ClaimsAccess(Issuer="RemoteClaims", ClaimType=ClaimTypes.PostalCode,
  14. Value="DC 20500")]
  15. public string OtherAction() {
  16. return "This is the protected action";
  17. }

最后用Signin登入,内部实现我没研究大概会去影响Cookie

其他细节

Claim Type

Claim类有个Type成员,他是字符串类型,通常都是用一个Url表示
技术分享
你创建自己的Claim时候可以自定义Type,也可以用内置的一些字符串(保存在System.Security.Claims.ClaimTypes)

命名空间

其实上面说的声明什么的都是集成在Asp.Net中的并不是Identity发明的
他们的命名空间大都是System.Security.ClaimsSystem.Security.Principal
Identity包最多写了一些扩展方法而已





Asp.Net Identity学习笔记+MVC5默认项目解析_授权&Claim

标签:

原文地址:http://www.cnblogs.com/Recoding/p/5767048.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!