ASP.NET 5 Identity

 

“跌倒了”指的是这一篇博文:爱与恨的抉择: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 方法中,有如下配置:

public void ConfigureServices(IServiceCollection services)
{
// Add EF services to the services container.
services.AddEntityFramework(Configuration)
.AddSqlServer()
.AddDbContext<ApplicationDbContext>(); // Add Identity services to the services container.
services.AddDefaultIdentity<ApplicationDbContext, ApplicationUser, IdentityRole>(Configuration);
services.AddIdentityEntityFramework<ApplicationDbContext, ApplicationUser, IdentityRole>(Configuration);
services.AddIdentity<ApplicationUser, IdentityRole>(Configuration); // Add MVC services to the services container.
services.AddMvc();
}

上面代码中,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 注册:

services.Configure<CookieAuthenticationOptions>(options =>
{
options.AuthenticationType = IdentityOptions.ApplicationCookieAuthenticationType;
options.LoginPath = new PathString("/Account/Login");
options.Notifications = new CookieAuthenticationNotifications
{
OnValidateIdentity = SecurityStampValidator.ValidateIdentityAsync
};
}, IdentityOptions.ApplicationCookieAuthenticationType);

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

public class AccountController : Controller
{
public AccountController(UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager)
{
UserManager = userManager;
SignInManager = signInManager;
} public UserManager<ApplicationUser> UserManager { get; private set; }
public SignInManager<ApplicationUser> SignInManager { get; private set; }
}

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

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

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

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
if (ModelState.IsValid)
{
var signInStatus = await SignInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, shouldLockout: false);
switch (signInStatus)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid username or password.");
return View(model);
}
} // If we got this far, something failed, redisplay form
return View(model);
}

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

public enum SignInStatus
{
Success = 0,
LockedOut = 1,
RequiresVerification = 2,
Failure = 3
}

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

public virtual async Task<SignInResult> PasswordSignInAsync(TUser user, string password,
bool isPersistent, bool shouldLockout, CancellationToken cancellationToken = default(CancellationToken))
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var error = await PreSignInCheck(user, cancellationToken);
if (error != null)
{
return error;
}
if (await IsLockedOut(user, cancellationToken))
{
return SignInResult.LockedOut;
}
if (await UserManager.CheckPasswordAsync(user, password, cancellationToken))
{
await ResetLockout(user, cancellationToken);
return await SignInOrTwoFactorAsync(user, isPersistent, cancellationToken);
}
if (UserManager.SupportsUserLockout && shouldLockout)
{
// If lockout is requested, increment access failed count which might lock out the user
await UserManager.AccessFailedAsync(user, cancellationToken);
if (await UserManager.IsLockedOutAsync(user, cancellationToken))
{
return SignInResult.LockedOut;
}
}
return SignInResult.Failed;
}

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 的实现:

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

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);,查看其具体实现:

private async Task<SignInResult> SignInOrTwoFactorAsync(TUser user, bool isPersistent,
CancellationToken cancellationToken, string loginProvider = null)
{
if (UserManager.SupportsUserTwoFactor &&
await UserManager.GetTwoFactorEnabledAsync(user, cancellationToken) &&
(await UserManager.GetValidTwoFactorProvidersAsync(user, cancellationToken)).Count > 0)
{
if (!await IsTwoFactorClientRememberedAsync(user, cancellationToken))
{
// Store the userId for use after two factor check
var userId = await UserManager.GetUserIdAsync(user, cancellationToken);
Context.Response.SignIn(StoreTwoFactorInfo(userId, loginProvider));
return SignInResult.TwoFactorRequired;
}
}
// Cleanup external cookie
if (loginProvider != null)
{
Context.Response.SignOut(IdentityOptions.ExternalCookieAuthenticationType);
}
await SignInAsync(user, isPersistent, loginProvider, cancellationToken);
return SignInResult.Success;
}

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

public virtual void SignIn(IEnumerable<ClaimsIdentity> identities);
public virtual void SignIn(ClaimsIdentity identity);
public abstract void SignIn(AuthenticationProperties properties, IEnumerable<ClaimsIdentity> identities);
public virtual void SignIn(AuthenticationProperties properties, params ClaimsIdentity[] identities);
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 方法中进行下面配置:

//app.UseIdentity();
app.UseCookieAuthentication((cookieOptions) =>
{
cookieOptions.AuthenticationType = IdentityOptions.ApplicationCookieAuthenticationType;
cookieOptions.AuthenticationMode = AuthenticationMode.Active;
cookieOptions.CookieHttpOnly = true;
cookieOptions.CookieName = ".CookieName";
cookieOptions.LoginPath = new PathString("/Account/Login");
//cookieOptions.CookieDomain = ".mysite.com";
}, "AccountAuthorize");

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

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

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

public static string ApplicationCookieAuthenticationType { get; set; } = typeof(IdentityOptions).Namespace + ".Application";
public static string ExternalCookieAuthenticationType { get; set; } = typeof(IdentityOptions).Namespace + ".External";
public static string TwoFactorUserIdCookieAuthenticationType { get; set; } = typeof(IdentityOptions).Namespace + ".TwoFactorUserId";
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. IdentityServer(12)- 使用 ASP.NET Core Identity

    IdentityServer具有非常好的扩展性,其中用户及其数据(包括密码)部分你可以使用任何想要的数据库进行持久化. 如果需要一个新的用户数据库,那么ASP.NET Core 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. IdentityServer4 中文文档 -14- (快速入门)使用 ASP.NET Core Identity

    IdentityServer4 中文文档 -14- (快速入门)使用 ASP.NET Core Identity 原文:http://docs.identityserver.io/en/release ...

  6. IdentityServer4【QuickStart】之使用asp.net core Identity

    使用asp.net core Identity IdentityServer灵活的设计中有一部分是可以将你的用户和他们的数据保存到数据库中的.如果你以一个新的用户数据库开始,那么,asp.net co ...

  7. Adding ASP.NET MVC5 Identity Authentication to an existing project

    Configuring Identity to your existing project is not hard thing. You must install some NuGet package ...

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

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

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

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

随机推荐

  1. 关于.NET,.NET Framework 和ASP.NET的总结

    .NET 1.1.        .NET是 Microsoft XML Web services 平台和技术. 1.2.        一个.NET应用是一个运行于.NET Framework之上的 ...

  2. 示例:Netty 处理 TCP数据分包协议

    一个.Netty解决TCP协议的数据分包的想法 我们知道通过TCP协议发送接收数据时,假设数据过大.接收到的数据会是分包的.比方:                                   ...

  3. Sqlserver 高并发和大数据存储方案

    Sqlserver 高并发和大数据存储方案 随着用户的日益递增,日活和峰值的暴涨,数据库处理性能面临着巨大的挑战.下面分享下对实际10万+峰值的平台的数据库优化方案.与大家一起讨论,互相学习提高!   ...

  4. 【Eclipse高速发展-文章插件】google V8,对于调试Nodejs

    安装的的时候能够通过 Help>Eclipse Marketplace的 搜索方式安装 或者 Help>Install New Software Name:googleV8 Locatio ...

  5. [ACM] HDU 2295 Radar (二分法+DLX 重复覆盖)

    Radar Problem Description N cities of the Java Kingdom need to be covered by radars for being in a s ...

  6. OpenWRT GPIO人口控制 WLED

    Linux根据系统GPIO系统架构简介 关于这个GPIO我一直认为非常非常长的时间easy.但.当你需要给一个特定的系统,参与这些GPIO什么时候.你会找到.不对,实例,mt7620n. GPIO#7 ...

  7. mysql 解压缩和赋权

    拉开拉链mysql紧凑根文件夹 注意ini配置文件的内容 basedir = D:\mysql-5.6.17-winx64  datadir = D:\mysql-5.6.17-winx64  por ...

  8. k8s google sample - guestbook

    Redis读写分离作为存储 PHP网页作为前端 github地址 https://github.com/kubernetes/kubernetes/blob/release-1.1/examples/ ...

  9. ImageView建立selector在录音中遇到的小问题及解决方案

    随着两张照片做了一个selector,采用ImageView的src要么background采用selector当点击,总不会出现点击效果,这就是为什么?经过一番折腾,后来发现"揭秘&quo ...

  10. Java代码优化(转)

    前言 代码优化,一个很重要的课题.可能有些人觉得没用,一些细小的地方有什么好修改的,改与不改对于代码的运行效率有什么影响呢?这个问题我是这么考虑的,就像大海里面的鲸鱼一样,它吃一条小虾米有用吗?没用, ...