“跌倒了”指的是这一篇博文:爱与恨的抉择:ASP.NET 5+EntityFramework 7

如果想了解 ASP.NET Identity 的“历史”及“原理”,强烈建议读一下这篇博文:MVC5 - ASP.NET Identity登录原理 - Claims-based认证和OWIN,如果你有时间,也可以读下 Jesse Liu 的 Membership 三部曲:

其实说来惭愧,我自己对 ASP.NET Identity 的理解及运用,仅限在使用 AuthorizeAttribute、FormsAuthentication.SetAuthCookie 等一些操作,背后的原理及其发展历程并不是很了解,所以我当时在 ASP.NET 5 中进行身份验证操作,才会让自己有种“无助”的感觉,周末的时候,阅读了 Jesse Liu 的这几篇博文,然后又找了一些相关资料,自己似乎懂得了一些,但好像又没有完全理解,既然说不出来,那就用“笔”记下来。

ASP.NET Identity GitHub 地址:https://github.com/aspnet/Identity

ASP.NET 5 中,关于身份验证的变化其实不大,还是 MVC5 的那一套,只不过配置有的变化罢了,使用 VS2015 创建 MVC 项目的时候,点击“Change Authentication”会出现下面四个选项:

如果创建的是 ASP.NET 5 项目,Authentication 默认是不可更改:

使用 VS2015 分别创建 MVC5 及 ASP.NET 5 的示例项目,你会发现 MVC5 中关于身份验证的代码及配置非常复杂,而在 ASP.NET 5 中则相对来说简化下,首先,在 Startup.cs 文件中的 ConfigureServices 方法中,有如下配置:

  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. // Add EF services to the services container.
  4. services.AddEntityFramework(Configuration)
  5. .AddSqlServer()
  6. .AddDbContext<ApplicationDbContext>();
  7. // Add Identity services to the services container.
  8. services.AddDefaultIdentity<ApplicationDbContext, ApplicationUser, IdentityRole>(Configuration);
  9. services.AddIdentityEntityFramework<ApplicationDbContext, ApplicationUser, IdentityRole>(Configuration);
  10. services.AddIdentity<ApplicationUser, IdentityRole>(Configuration);
  11. // Add MVC services to the services container.
  12. services.AddMvc();
  13. }

上面代码中,AddDefaultIdentity 和 AddIdentityEntityFramework 其实是一个意思(“捆绑销售”),所在程序集:Microsoft.AspNet.Identity.EntityFramework,AddEntityFramework 和 AddIdentityEntityFramework 使用的是同一个 DbContext,当然也可以进行对身份验证上下文进行分开管理,比如我们有可能多个应用程序共享一个身份验证的上下文。ConfigureServices 方法的解释为:This method gets called by the runtime,表示这个方法在应用程序运行的时候注册使用的服务,有点类似于组件化的应用,比如 ASP.NET 5 只是一个基础 Web 站点,你可以在这个应用中添加你想要的组件或模块,比如你想使用 WebAPI,你只需要在 project.json 中添加 Microsoft.AspNet.Mvc.WebApiCompatShim 程序包,然后在 ConfigureServices 方法中进行服务注册就行了:services.AddWebApiConventions();。

AddDefaultIdentity 注册的三个基础类型:

  • IdentityDbContext< IdentityUser >:ApplicationDbContext 继承实现。
  • IdentityUser:ApplicationUser 继承实现。
  • IdentityRole

注册完成之后,就是配置使用了,在 Startup.cs 的 Configure 方法中进行配置使用:app.UseIdentity();,表示应用程序启用身份验证,如果把这段代码注释掉的话,你会发现整个应用程序的身份验证就失效了,Configure 方法解释是:Configure is called after ConfigureServices is called,在上面 AddDefaultIdentity 注册中,其实包含了很多内容,关于身份验证基本上就这三个类型,ASP.NET Identity 直接的操作通过注册的这三个类型进行以来注入,比如后面会遇到的 UserManager 和 SignInManager,但查看这部分的源代码,在 Microsoft.AspNet.Identity.EntityFramework 中并没有加入进来。

下面我们来根据 ASP.NET Identity 的源码,来看一个身份验证的流程,ASP.NET 5 中的身份验证和之前一样,只需要在需要验证的 Action 上面添加 Authorize 就行了,在上面 Startup.cs 中的身份验证配置很简单,启用的话只需要 app.UseIdentity(); 就可以了,而在之前的 MVC 程序的 Web.config 中需要配置一大堆东西,在 IdentityServiceCollectionExtensions 源码中,包含了一大堆默认配置,比如 ApplicationCookieAuthenticationType 注册:

  1. services.Configure<CookieAuthenticationOptions>(options =>
  2. {
  3. options.AuthenticationType = IdentityOptions.ApplicationCookieAuthenticationType;
  4. options.LoginPath = new PathString("/Account/Login");
  5. options.Notifications = new CookieAuthenticationNotifications
  6. {
  7. OnValidateIdentity = SecurityStampValidator.ValidateIdentityAsync
  8. };
  9. }, IdentityOptions.ApplicationCookieAuthenticationType);

我们也可以在 Configure 中进行自定义配置,配置方法:app.UseCookieAuthentication。当访问 Action 的身份验证失效后,跳转到“/Account/Login”进行登录,查看 AccountController 中的示例代码,你会发现有下面的东西:

  1. public class AccountController : Controller
  2. {
  3. public AccountController(UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager)
  4. {
  5. UserManager = userManager;
  6. SignInManager = signInManager;
  7. }
  8. public UserManager<ApplicationUser> UserManager { get; private set; }
  9. public SignInManager<ApplicationUser> SignInManager { get; private set; }
  10. }

查看整个的应用程序的代码,发现我们并没有注册 UserManager、SignInManager 类型的依赖注入,那是怎么注入的呢?其实注入的类型不是 UserManager 和 SignInManager,而是 IdentityUser,在 ConfigureServices 中我们添加过这样的代码:services.AddDefaultIdentity<ApplicationDbContext, ApplicationUser, IdentityRole>(Configuration);,这是最重要的,之后所有身份验证操作所用到的基类型都是从这里来的,在 IdentityServiceCollectionExtensions 中的 AddIdentity 操作中,我们发现了下面这样的代码:

  1. services.TryAdd(describe.Scoped<UserManager<TUser>, UserManager<TUser>>());
  2. services.TryAdd(describe.Scoped<SignInManager<TUser>, SignInManager<TUser>>());
  3. services.TryAdd(describe.Scoped<RoleManager<TRole>, RoleManager<TRole>>());

Scoped 所在程序集:Microsoft.Framework.DependencyInjection,DependencyInjection 为 ASP.NET 5 自带的依赖注入,如果你仔细查看其相关类型的源码,发现都是通过这个东西进行 IoC 管理的,下面我们看一个 Login 操作:

  1. [HttpPost]
  2. [AllowAnonymous]
  3. [ValidateAntiForgeryToken]
  4. public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
  5. {
  6. if (ModelState.IsValid)
  7. {
  8. var signInStatus = await SignInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, shouldLockout: false);
  9. switch (signInStatus)
  10. {
  11. case SignInStatus.Success:
  12. return RedirectToLocal(returnUrl);
  13. case SignInStatus.Failure:
  14. default:
  15. ModelState.AddModelError("", "Invalid username or password.");
  16. return View(model);
  17. }
  18. }
  19. // If we got this far, something failed, redisplay form
  20. return View(model);
  21. }

最主要的操作是,通过 ASP.NET Identity 的 SignInManager.PasswordSignInAsync 操作,进行验证身份密码,返回 SignInStatus 类型的验证结果:

  1. public enum SignInStatus
  2. {
  3. Success = 0,
  4. LockedOut = 1,
  5. RequiresVerification = 2,
  6. Failure = 3
  7. }

我们来看一下 SignInManager.PasswordSignInAsync 中究竟干了什么事:

  1. public virtual async Task<SignInResult> PasswordSignInAsync(TUser user, string password,
  2. bool isPersistent, bool shouldLockout, CancellationToken cancellationToken = default(CancellationToken))
  3. {
  4. if (user == null)
  5. {
  6. throw new ArgumentNullException(nameof(user));
  7. }
  8. var error = await PreSignInCheck(user, cancellationToken);
  9. if (error != null)
  10. {
  11. return error;
  12. }
  13. if (await IsLockedOut(user, cancellationToken))
  14. {
  15. return SignInResult.LockedOut;
  16. }
  17. if (await UserManager.CheckPasswordAsync(user, password, cancellationToken))
  18. {
  19. await ResetLockout(user, cancellationToken);
  20. return await SignInOrTwoFactorAsync(user, isPersistent, cancellationToken);
  21. }
  22. if (UserManager.SupportsUserLockout && shouldLockout)
  23. {
  24. // If lockout is requested, increment access failed count which might lock out the user
  25. await UserManager.AccessFailedAsync(user, cancellationToken);
  26. if (await UserManager.IsLockedOutAsync(user, cancellationToken))
  27. {
  28. return SignInResult.LockedOut;
  29. }
  30. }
  31. return SignInResult.Failed;
  32. }

PasswordSignInAsync 还有一个重写方法,是获取用户信息的:UserManager.FindByNameAsync(userName, cancellationToken);,接着查看 FindByNameAsync 的定义,会找到这段代码:Store.FindByNameAsync(userName, cancellationToken),Store 是什么?类型定义为:IUserStore<TUser> Store,它就像一个仓库,为用户验证提供查询及存储服务,除了 IUserStore,在 UserManager 中,你还会发现有很多的“Store”,比如 IUserLoginStore、IUserRoleStore、IUserClaimStore 等等,但都是继承于 IUserStore,在 ConfigureServices 进行配置服务的时候,services.AddIdentity 还有一个 AddEntityFrameworkStores 方法,范型类型为 TContext,上面所有的 Store 上下文都是从它继承来的,再查看 AddEntityFrameworkStores 的实现:

  1. public static IdentityBuilder AddEntityFrameworkStores<TContext>(this IdentityBuilder builder)
  2. where TContext : DbContext
  3. {
  4. builder.Services.Add(IdentityEntityFrameworkServices.GetDefaultServices(builder.UserType, builder.RoleType, typeof(TContext)));
  5. return builder;
  6. }

builder.Services.Add 所起到的作用就是往 IoC 中注入类型,这样所有用到此类型的引用,都可以通过构造函数注入方式获取其实现,再查看 GetDefaultServices 的具体实现,因为看不懂代码,就不贴出来了,其实里面操作的就三个类型:TUser、TRole 和 TContext,这也是 ASP.NET Identity 操作的三个基本类型,在 Identity 操作中,基本上是两大操作类,一个是 SignInManager,另一个就是 UserManager,其实查看

SignInManager 的具体实现代码,你会发现,关于用户的获取及存储,都是通过 UserManager 进行操作的,而 UserManager 又是通过 IUserStore 的具体实现类进行操作的,SignInManager 只不过是一个用户验证的操作类,比如我们一开始说到的 SignInManager.PasswordSignInAsync,上面已经贴出代码了,你会看到基本上都是 UserManager.什么,比如 UserManager.CheckPasswordAsync、UserManager.SupportsUserLockout、UserManager.AccessFailedAsync 等等,在 PasswordSignInAsync 代码实现中,不关于用户操作的,最核心的就是这段代码:SignInOrTwoFactorAsync(user, isPersistent, cancellationToken);,查看其具体实现:

  1. private async Task<SignInResult> SignInOrTwoFactorAsync(TUser user, bool isPersistent,
  2. CancellationToken cancellationToken, string loginProvider = null)
  3. {
  4. if (UserManager.SupportsUserTwoFactor &&
  5. await UserManager.GetTwoFactorEnabledAsync(user, cancellationToken) &&
  6. (await UserManager.GetValidTwoFactorProvidersAsync(user, cancellationToken)).Count > 0)
  7. {
  8. if (!await IsTwoFactorClientRememberedAsync(user, cancellationToken))
  9. {
  10. // Store the userId for use after two factor check
  11. var userId = await UserManager.GetUserIdAsync(user, cancellationToken);
  12. Context.Response.SignIn(StoreTwoFactorInfo(userId, loginProvider));
  13. return SignInResult.TwoFactorRequired;
  14. }
  15. }
  16. // Cleanup external cookie
  17. if (loginProvider != null)
  18. {
  19. Context.Response.SignOut(IdentityOptions.ExternalCookieAuthenticationType);
  20. }
  21. await SignInAsync(user, isPersistent, loginProvider, cancellationToken);
  22. return SignInResult.Success;
  23. }

再次抛开一大堆的 UserManager 操作,找到最核心的:Context.Response.SignIn(StoreTwoFactorInfo(userId, loginProvider)),StoreTwoFactorInfo 方法返回类型为 ClaimsIdentity,在返回之前,根据 userId 创建 Claim 对象,并添加到 ClaimsIdentity 集合中,接下来的操作就是:Context.Response.SignIn,将用户身份信息输入到当前上下文,接着查看 HttpResponse 抽象类关于 SignIn 的定义:

  1. public virtual void SignIn(IEnumerable<ClaimsIdentity> identities);
  2. public virtual void SignIn(ClaimsIdentity identity);
  3. public abstract void SignIn(AuthenticationProperties properties, IEnumerable<ClaimsIdentity> identities);
  4. public virtual void SignIn(AuthenticationProperties properties, params ClaimsIdentity[] identities);
  5. public virtual void SignIn(AuthenticationProperties properties, ClaimsIdentity identity);

在以前如果使用 SignIn,其调用方式是 System.Web.Security.FormsAuthentication.SetAuthCookie("userName", false);,采用的是 Forms 认证,但是在 ASP.NET 5 中,已经访问不到 SetAuthCookie 了,原来的 SetAuthCookie 实现方式不知道是怎样的,如果在 ASP.NET 5 中实现 SetAuthCookie 类似的效果,我们该怎么做呢?只需要在 Startup.cs 的 Configure 方法中进行下面配置:

  1. //app.UseIdentity();
  2. app.UseCookieAuthentication((cookieOptions) =>
  3. {
  4. cookieOptions.AuthenticationType = IdentityOptions.ApplicationCookieAuthenticationType;
  5. cookieOptions.AuthenticationMode = AuthenticationMode.Active;
  6. cookieOptions.CookieHttpOnly = true;
  7. cookieOptions.CookieName = ".CookieName";
  8. cookieOptions.LoginPath = new PathString("/Account/Login");
  9. //cookieOptions.CookieDomain = ".mysite.com";
  10. }, "AccountAuthorize");

其实我们下面进行自定义的配置和上面注释的 UseIdentity 是一样的效果,只不过有些操作是在 Microsoft.AspNet.Identity.IdentityServiceCollectionExtensions 中默认完成的,注意上面配置中,我们将之前的 services.AddDefaultIdentity<ApplicationDbContext, ApplicationUser, IdentityRole>(Configuration); 代码给注释了,再来看下 Account 中的 Login 代码:

  1. [AllowAnonymous]
  2. public void Login(string returnUrl = null)
  3. {
  4. var userId = "xishuai";
  5. var identity = new ClaimsIdentity(IdentityOptions.ApplicationCookieAuthenticationType);
  6. identity.AddClaim(new Claim(ClaimTypes.Name, userId));
  7. Response.SignIn(identity);
  8. }

上面的操作其实就是之前的 SignInManager.PasswordSignInAsync 一样,只不过是一个简化版本,另外,IdentityOptions.ApplicationCookieAuthenticationType 也没什么神奇的地方,就是一个类型字符串:

  1. public static string ApplicationCookieAuthenticationType { get; set; } = typeof(IdentityOptions).Namespace + ".Application";
  2. public static string ExternalCookieAuthenticationType { get; set; } = typeof(IdentityOptions).Namespace + ".External";
  3. public static string TwoFactorUserIdCookieAuthenticationType { get; set; } = typeof(IdentityOptions).Namespace + ".TwoFactorUserId";
  4. public static string TwoFactorRememberMeCookieAuthenticationType { get; set; } = typeof(IdentityOptions).Namespace + ".TwoFactorRemeberMe";

Login 登录验证效果:

总结:上面也说了不少内容,说真的,其实我也不知道自己说了什么,有几点感触需要总结下,在多个应用程序共享身份验证的时候(CookieDomain),不管是使用 FormsAuthentication,还是使用 SignInManager.SignInAsync,又或者使用 UserStore 进行用户管理,但用户进行验证的程序只有一个,这个按照自己的想法,想怎么实现就怎么实现,其他的应用程序都只不过是判断用户是否通过验证请求、及获取用户标识的,就这两个操作,用户的验证不管上面的何种实现,我们都可以通过 User.Identity 获取用户验证的信息,类型为 IIdentity

不知者无罪,知罪却不赎罪,那就是有罪!!!

跌倒了,再爬起来:ASP.NET 5 Identity的更多相关文章

  1. 坎坷路:ASP.NET 5 Identity 身份验证(上集)

    之所以为上集,是因为我并没有解决这个问题,写这篇博文的目的是纪录一下我所遇到的问题,以免自己忘记,其实已经忘了差不多了,写的过程也是自己回顾的过程,并且之前收集有关 ASP.NET 5 身份验证的书签 ...

  2. ASP.NET 5 Identity

    ASP.NET 5 Identity   “跌倒了”指的是这一篇博文:爱与恨的抉择:ASP.NET 5+EntityFramework 7 如果想了解 ASP.NET Identity 的“历史”及“ ...

  3. ASP.NET Core Identity Hands On(1)——Identity 初次体验

    ASP.NET Core Identity是用于构建ASP.NET Core Web应用程序的成员资格系统,包括成员资格.登录和用户数据存储 这是来自于 ASP.NET Core Identity 仓 ...

  4. ASP.NET Core Identity Hands On(2)——注册、登录、Claim

    上一篇文章(ASP.NET Core Identity Hands On(1)--Identity 初次体验)中,我们初识了Identity,并且详细分析了AspNetUsers用户存储表,这篇我们将 ...

  5. ASP.NET Core Identity 实战(2)——注册、登录、Claim

    上一篇文章(ASP.NET Core Identity Hands On(1)--Identity 初次体验)中,我们初识了Identity,并且详细分析了AspNetUsers用户存储表,这篇我们将 ...

  6. ASP.NET Core Identity 实战(4)授权过程

    这篇文章我们将一起来学习 Asp.Net Core 中的(注:这样描述不准确,稍后你会明白)授权过程 前情提要 在之前的文章里,我们有提到认证和授权是两个分开的过程,而且认证过程不属于Identity ...

  7. ASP.NET Core Identity 实战(3)认证过程

    如果你没接触过旧版Asp.Net Mvc中的 Authorize 或者 Cookie登陆,那么你一定会疑惑 认证这个名词,这太正式了,这到底代表这什么? 获取资源之前得先过两道关卡Authentica ...

  8. 用一个应用场景理解ASP.NET Core Identity是什么?

    目录 前言 基于声明的认证(Claims-based Authentication) 应用场景一 在ASP.NET Core 中Identity是如何实现的 类ClaimsPrincipal 考察另外 ...

  9. 用例子看ASP.NET Core Identity是什么?

    原文:用例子看ASP.NET Core Identity是什么? 目录 前言 基于声明的认证(Claims-based Authentication) Claim 在ASP.NET Core Iden ...

  10. ASP.NET Core Identity 框架 - ASP.NET Core 基础教程 - 简单教程,简单编程

    原文:ASP.NET Core Identity 框架 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core Identity 框架 前面我们使用了 N 多个章节, ...

随机推荐

  1. 解决FF浏览器无法执行window.close()脚本

    在FF浏览器中输入about:config 查找dom.allow_scripts_to_close_windows 将值改为true

  2. 通读AFN①--从创建manager到数据解析完毕

    流程梳理 今天开始会写几篇关于AFN源码解读的一些Blog,首先要梳理一下AFN的整体结构(主要是讨论2.x版本的Session访问模块): 我们先看看我们最常用的一段代码: AFHTTPSessio ...

  3. / fluxChatDemo / 系列 ——项目安装坑洼简要

    第一部分 1.使用import引入时,路径选错 2.React.Component 注意大写 (极浅的坑都掉,原谅我初级中的初级~还是贴出来吧) 3.不知为何运行起来没有内容,都怪自己不熟就上路,以为 ...

  4. 一些gcd计数问题

    数论什么的全都忘光了吧QAQ 做了几道简单的题练习一下. bzoj1101: [POI2007]Zap 求有多少对数满足 gcd(x,y)=d, 1<=x<=a, 1<=y<= ...

  5. 取消TableViewCell选中状态的外观变化

    tabelViewcell 使用Xib创建自定义外观的时候,在tableview选中的时候,cell的外观会发生变化,在定义Xib中如下图将选中的外观状态取消掉 也有其他选项,可以选择控制选中的时候的 ...

  6. JavaScript资源大全中文版(Awesome最新版--转载自张果老师博客)

    JavaScript资源大全中文版(Awesome最新版)   目录 前端MVC 框架和库 包管理器 加载器 打包工具 测试框架 框架 断言 覆盖率 运行器 QA 工具 基于 Node 的 CMS 框 ...

  7. 安装jdk

    检查已安装jdk,如果有,先删除 rpm -qa|grep java rpm -e --nodeps filename 从oracle官方网站下载jdk安装包:jdk-8u111-linux-x64. ...

  8. Unity 5.3.5p8 C#编译器升级

    Unity 5.3.5p8的C#编译器升级 注意:该版本是单独升级C#编译器的测试版!请使用文中提供的下载链接! 基于Unity 5.3.5p8的C#编译器升级!下载链接 试用该版本前请先备份项目,遇 ...

  9. 为首次部署MongoDB做好准备:容量计划和监控

    如果你已经完成了自己新的MongoDB应用程序的开发,并且现在正准备将它部署进产品中,那么你和你的运营团队需要讨论一些关键的问题: 最佳部署实践是什么? 为了确保应用程序满足它所必须的服务层次我们需要 ...

  10. Key/Value之王Memcached初探:三、Memcached解决Session的分布式存储场景的应用

    一.高可用的Session服务器场景简介 1.1 应用服务器的无状态特性 应用层服务器(这里一般指Web服务器)处理网站应用的业务逻辑,应用的一个最显著的特点是:应用的无状态性. PS:提到无状态特性 ...