ASP.NET Core Identity

ASP.NET Core Identity 源码

ASP.NET Core Identity

怎么用

  1. StartUp

    • ConfigureServices

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      public void ConfigureServices(IServiceCollection services)
      {
      //EF 配置
      services.AddDbContext<ApplicationDbContext>(options =>
      options.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));

      //注入Identity依赖
      services.AddIdentity<ApplicationUser, IdentityRole>()
      .AddEntityFrameworkStores<ApplicationDbContext>()
      .AddDefaultTokenProviders();

      //以下不重要....跟Identity无关
      //Add application services.
      services.AddTransient<IEmailSender, EmailSender>();

      services.AddMvc();
      }
    • Configure

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      public void Configure(IApplicationBuilder app, IHostingEnvironment env)
      {
      if (env.IsDevelopment())
      {
      app.UseDeveloperExceptionPage();
      app.UseDatabaseErrorPage();
      }
      else
      {
      app.UseExceptionHandler("/Home/Error");
      }

      app.UseStaticFiles();

      app.UseAuthentication();

      app.UseMvc(routes =>
      {
      routes.MapRoute(
      name: "default",
      template: "{controller=Home}/{action=Index}/{id?}");
      });
      }
  2. 三大核心对象

    • SignInManager: 主要处理注册登录相关业务逻辑。

      e.g.

      • 用户密码登录

        1
        var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
      • 注册并登录

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
        var result = await _userManager.CreateAsync(user, model.Password);
        if (result.Succeeded)
        {
        _logger.LogInformation("User created a new account with password.");

        var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
        var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme);
        await _emailSender.SendEmailConfirmationAsync(model.Email, callbackUrl);

        await _signInManager.SignInAsync(user, isPersistent: false);
        _logger.LogInformation("User created a new account with password.");
        }
    • UserManager: 处理用户相关添加删除,修改密码,添加删除角色等。

      e.g.

      • 创建用户

        1
        2
        var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
        var result = await _userManager.CreateAsync(user, model.Password);
      • 查询用户

        1
        var user = await _userManager.FindByIdAsync(userId);
    • RoleManager:角色相关添加删除更新等。

原理

  1. StartUp

    • ConfigureServices

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      services.AddIdentity<ApplicationUser, IdentityRole>();

      //以上代码主要实现如下:
      services.AddAuthentication(options =>
      {
      options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
      options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
      options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
      })
      .AddCookie(IdentityConstants.ApplicationScheme, o =>
      {
      o.LoginPath = new PathString("/Account/Login");
      o.Events = new CookieAuthenticationEvents
      {
      OnValidatePrincipal = SecurityStampValidator.ValidatePrincipalAsync
      };
      })
      .AddCookie(IdentityConstants.ExternalScheme, o =>
      {
      o.Cookie.Name = IdentityConstants.ExternalScheme;
      o.ExpireTimeSpan = TimeSpan.FromMinutes(5);
      })
      .AddCookie(IdentityConstants.TwoFactorRememberMeScheme,
      o => o.Cookie.Name = IdentityConstants.TwoFactorRememberMeScheme)
      .AddCookie(IdentityConstants.TwoFactorUserIdScheme, o =>
      {
      o.Cookie.Name = IdentityConstants.TwoFactorUserIdScheme;
      o.ExpireTimeSpan = TimeSpan.FromMinutes(5);
      });

      //..........................................................

      //错误信息描述
      services.TryAddScoped<IdentityErrorDescriber>();

      //..........................................................

      services.TryAddScoped<UserManager<TUser>, AspNetUserManager<TUser>>();
      services.TryAddScoped<SignInManager<TUser>, SignInManager<TUser>>();
      services.TryAddScoped<RoleManager<TRole>, AspNetRoleManager<TRole>>();
      1. services.AddAuthentication 主要注入了 IAuthenticationServiceIAuthenticationHandlerProviderIAuthenticationSchemeProvider 几个重要的实例

        1
        2
        3
        4
        services.TryAddScoped<IAuthenticationService, AuthenticationService>();

        services.TryAddScoped<IAuthenticationHandlerProvider, AuthenticationHandlerProvider>();
        services.TryAddSingleton<IAuthenticationSchemeProvider, AuthenticationSchemeProvider>();
        • IAuthenticationService

          用来实现 HttpContext 下的几个扩展方法:Authenticate/Challenge/Forbid/SignIn/SignOut

        • IAuthenticationSchemeProvider

          IAuthenticationService 会先根据 IAuthenticationSchemeProvider 获取指定的 AuthenticationScheme(认证方案)

          AuthenticationScheme 有三个主要属性 Name (方案名称) , DisplayName , HandlerType (AuthenticationHandler的类型)

        • IAuthenticationHandlerProvider

          IAuthenticationHandlerProvider 根据指定的 AuthenticationScheme (认证方案) 的 HandlerType 属性获取到 AuthenticationHandler

          以下是一个 AuthenticationServiceSignInAsync 的实现

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          public virtual async Task SignInAsync(HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties)
          {
          if (principal == null)
          {
          throw new ArgumentNullException(nameof(principal));
          }

          if (scheme == null)
          {
          //通过 AuthenticationSchemeProvider 获取 DefaultSignInScheme
          var defaultScheme = await Schemes.GetDefaultSignInSchemeAsync();
          scheme = defaultScheme?.Name;
          if (scheme == null)
          {
          throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultSignInScheme found.");
          }
          }

          //通过 AuthenticationHandlerProvider 获取到 DefaultSignInScheme 对应的AuthenticationHandlerProvider
          var handler = await Handlers.GetHandlerAsync(context, scheme) as IAuthenticationSignInHandler;
          if (handler == null)
          {
          throw new InvalidOperationException($"No IAuthenticationSignInHandler is configured to handle sign in for the scheme: {scheme}");
          }

          await handler.SignInAsync(principal, properties);
          }
      2. AddCookie

        AddCookie 会将 CookieAuthenticationHandler 作为 AuthenticationSchemeHandlerType

        1
        2
        3
        4
        5
        public static AuthenticationBuilder AddCookie(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action<CookieAuthenticationOptions> configureOptions)
        {
        builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<CookieAuthenticationOptions>, PostConfigureCookieAuthenticationOptions>());
        return builder.AddScheme<CookieAuthenticationOptions, CookieAuthenticationHandler>(authenticationScheme, displayName, configureOptions);
        }

        AuthenticationScheme 添加到 AuthenticationOptions 中。

        AuthenticationOptions 会将 AuthenticationScheme 添加到 SchemesSchemeMap 两个属性。AuthenticationSchemeProvider 也是通过这两个属性获取到 AuthenticationScheme 的。

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        //
        // Summary:
        // Returns the schemes in the order they were added (important for request handling
        // priority)
        public IEnumerable<AuthenticationSchemeBuilder> Schemes { get; }
        //
        // Summary:
        // Maps schemes by name.
        public IDictionary<string, AuthenticationSchemeBuilder> SchemeMap { get; }

        //
        // Summary:
        // Adds an Microsoft.AspNetCore.Authentication.AuthenticationScheme.
        //
        // Parameters:
        // name:
        // The name of the scheme being added.
        //
        // configureBuilder:
        // Configures the scheme.
        public void AddScheme(string name, Action<AuthenticationSchemeBuilder> configureBuilder);
    • Configure

  2. 三大核心对象

    • 实现原理

      先看下 SignManagerSignIn/Out 的实现

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      public virtual async Task SignInAsync(TUser user, AuthenticationProperties authenticationProperties, string authenticationMethod = null)
      {
      var userPrincipal = await CreateUserPrincipalAsync(user);
      // Review: should we guard against CreateUserPrincipal returning null?
      if (authenticationMethod != null)
      {
      userPrincipal.Identities.First().AddClaim(new Claim(ClaimTypes.AuthenticationMethod, authenticationMethod));
      }
      await Context.SignInAsync(IdentityConstants.ApplicationScheme,
      userPrincipal,
      authenticationProperties ?? new AuthenticationProperties());
      }

      /// <summary>
      /// Signs the current user out of the application.
      /// </summary>
      public virtual async Task SignOutAsync()
      {
      await Context.SignOutAsync(IdentityConstants.ApplicationScheme);
      await Context.SignOutAsync(IdentityConstants.ExternalScheme);
      await Context.SignOutAsync(IdentityConstants.TwoFactorUserIdScheme);
      }

      没错,最终都是调用 HttpContext 下的扩展方法实现,而这些扩展方法是通过 AuthenticationService 实现的, AuthenticationService 又是根据 AuthenticationScheme 中的 AuthenticationHandler 来实现。最终,这里是由 CookieAuthenticationHandler 来实现

    • 怎么实现数据持久化

      通过 Microsoft.AspNetCore.Identity.EntityFrameworkCore

      1
      services.AddEntityFrameworkStores<ApplicationDbContext>()
  3. 本地化提示信息

    可以在 AddIdentity 的实现中找到以下代码,IdentityErrorDescriber 用于错误提示。(e.g. 用户名和密码不匹配的提示)。

    现在全都是英文提示,注意到注入方法 TryAddScoped (如果已经注入过此类型,不再注入),可以在AddIdentity 之前注入自己重写的 IdentityErrorDescriber,比如 ChineseIdentityErrorDescriber

    1
    2
    //错误信息描述
    services.TryAddScoped<IdentityErrorDescriber>();

参考链接