ASP.NET Core 上的 Identity 简介

作者:Rick Anderson

ASP.NET Core Identity:

  • 一个 API,它支持用户界面 (UI) 登录功能。
  • 管理用户、密码、配置文件数据、角色、声明、令牌、电子邮件确认等等。

用户可使用存储在 Identity 中的登录信息创建帐户,或者可使用外部登录提供程序。 支持的外部登录提供程序包括 Facebook、Google、Microsoft 帐户和 Twitter

有关如何全局要求所有用户进行身份验证的信息,请参阅需要通过身份验证的用户

Identity GitHub 上提供了源代码。 支架 Identity 并查看生成的文件,以查看模板与 Identity的交互。

Identity 通常使用 SQL Server 数据库进行配置,以存储用户名、密码和配置文件数据。 或者,可使用其他持久性存储,例如 Azure 表存储。

在本主题中,你将学习如何使用 Identity 来注册、登录和注销用户。 注意:模板会将用户的用户名和电子邮件看做是相同的。 若要更详细了解如何创建使用 Identity 的应用,请参阅后续步骤

Identity ASP.NET Core与Microsoft 标识平台无关。 Microsoft 标识平台是:

  • Azure Active Directory (Azure AD) 开发人员平台的演变。
  • 用于在 ASP.NET Core 应用中进行身份验证和授权的备用标识解决方案。

ASP.NET Core Identity 将用户界面 (UI) 登录功能添加到 ASP.NET Core Web 应用。 若要保护 Web API 和 SPA,请使用以下项之一:

IdentityServer4 是适用于 ASP.NET Core 的 OpenID Connect 和 OAuth 2.0 框架。 IdentityServer4 支持以下安全功能:

  • 身份验证即服务 (AaaS)
  • 跨多个应用程序类型的单一登录/注销 (SSO)
  • API 的访问控制
  • Federation Gateway

有关详细信息,请参阅欢迎使用 Server4。

查看或下载示例代码下载方法)。

创建使用身份验证的 Web 应用

使用个人用户帐户创建“ASP.NET Core Web 应用程序”项目。

  • 选择“ASP.NET Core Web 应用”模板。 将项目命名为 WebApp1,使其命名空间与项目下载项相同。 单击“确定” 。
  • 在“身份验证类型”输入中,选择“个人用户帐户”。

生成的项目将 ASP.NET CoreIdentity作为Razor类库提供。 IdentityRazor 类库公开具有 Identity 区域的终结点。 例如:

  • /Identity/Account/Login
  • /Identity/Account/Logout
  • /Identity/Account/Manage

应用迁移

应用迁移以初始化数据库。

在包管理器控制台 (PMC) 中运行以下命令:

Update-Database

测试注册和登录

运行应用并注册用户。 根据屏幕大小,你可能需要选择导航切换按钮来查看“注册”和“登录”链接。

查看 Identity 数据库

  • 在“视图”菜单中选择“SQL Server 对象资源管理器”(SSOX)。
  • 导航到“(localdb)MSSQLLocalDB(SQL Server 13)”。 右键单击“dbo.AspNetUsers”>“查看数据”:

SQL Server 对象资源管理器 中 AspNetUsers 表上的上下文菜单

配置 Identity 服务

这些服务添加在 Program.cs 中。 典型模式是按以下顺序调用方法:

  1. Add{Service}
  2. builder.Services.Configure{Service}
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebApp1.Data;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();

builder.Services.Configure<IdentityOptions>(options =>
{
    // Password settings.
    options.Password.RequireDigit = true;
    options.Password.RequireLowercase = true;
    options.Password.RequireNonAlphanumeric = true;
    options.Password.RequireUppercase = true;
    options.Password.RequiredLength = 6;
    options.Password.RequiredUniqueChars = 1;

    // Lockout settings.
    options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
    options.Lockout.MaxFailedAccessAttempts = 5;
    options.Lockout.AllowedForNewUsers = true;

    // User settings.
    options.User.AllowedUserNameCharacters =
    "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
    options.User.RequireUniqueEmail = false;
});

builder.Services.ConfigureApplicationCookie(options =>
{
    // Cookie settings
    options.Cookie.HttpOnly = true;
    options.ExpireTimeSpan = TimeSpan.FromMinutes(5);

    options.LoginPath = "/Identity/Account/Login";
    options.AccessDeniedPath = "/Identity/Account/AccessDenied";
    options.SlidingExpiration = true;
});

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

app.MapRazorPages();

app.Run();

上述代码用默认选项值来配置 Identity。 可通过依赖关系注入向应用提供服务。

通过调用 UseAuthentication 启用 Identity。 UseAuthentication 向请求管道添加身份验证中间件

模板生成的应用不使用授权app.UseAuthorization 会被包含来内,确保在应用添加授权时按正确的顺序添加它。 UseRoutingUseAuthentication、 和 UseAuthorization 必须按照前面代码中显示的顺序调用。

有关 IdentityOptions 的详细信息,请参阅 IdentityOptions应用程序启动

构建 Register、Login、LogOut 和 RegisterConfirmation 的基架

添加 RegisterLoginLogOutRegisterConfirmation 文件。 按照 基架标识进入具有 Razor 授权说明的项目 ,生成本部分所示的代码。

检查注册

当用户单击 Register 页面上的“注册”按钮时,会调用 RegisterModel.OnPostAsync 操作。 用户由 CreateAsync(TUser) 在 对象上 _userManager 创建:

public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
    returnUrl = returnUrl ?? Url.Content("~/");
    ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync())
                                          .ToList();
    if (ModelState.IsValid)
    {
        var user = new IdentityUser { UserName = Input.Email, Email = Input.Email };
        var result = await _userManager.CreateAsync(user, Input.Password);
        if (result.Succeeded)
        {
            _logger.LogInformation("User created a new account with password.");

            var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
            code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
            var callbackUrl = Url.Page(
                "/Account/ConfirmEmail",
                pageHandler: null,
                values: new { area = "Identity", userId = user.Id, code = code },
                protocol: Request.Scheme);

            await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
                $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");

            if (_userManager.Options.SignIn.RequireConfirmedAccount)
            {
                return RedirectToPage("RegisterConfirmation", 
                                      new { email = Input.Email });
            }
            else
            {
                await _signInManager.SignInAsync(user, isPersistent: false);
                return LocalRedirect(returnUrl);
            }
        }
        foreach (var error in result.Errors)
        {
            ModelState.AddModelError(string.Empty, error.Description);
        }
    }

    // If we got this far, something failed, redisplay form
    return Page();
}

禁用默认帐户验证

使用默认模板时,会将用户重定向到 Account.RegisterConfirmation,用户可以从中选择一个链接来确认帐户。 默认值Account.RegisterConfirmation仅用于测试,应在生产应用中禁用自动帐户验证。

若要要求使用已确认的帐户并阻止在注册时立即登录,请在 中/Areas/Identity/Pages/Account/RegisterConfirmation.cshtml.cs设置 DisplayConfirmAccountLink = false

[AllowAnonymous]
public class RegisterConfirmationModel : PageModel
{
    private readonly UserManager<IdentityUser> _userManager;
    private readonly IEmailSender _sender;

    public RegisterConfirmationModel(UserManager<IdentityUser> userManager, IEmailSender sender)
    {
        _userManager = userManager;
        _sender = sender;
    }

    public string Email { get; set; }

    public bool DisplayConfirmAccountLink { get; set; }

    public string EmailConfirmationUrl { get; set; }

    public async Task<IActionResult> OnGetAsync(string email, string returnUrl = null)
    {
        if (email == null)
        {
            return RedirectToPage("/Index");
        }

        var user = await _userManager.FindByEmailAsync(email);
        if (user == null)
        {
            return NotFound($"Unable to load user with email '{email}'.");
        }

        Email = email;
        // Once you add a real email sender, you should remove this code that lets you confirm the account
        DisplayConfirmAccountLink = false;
        if (DisplayConfirmAccountLink)
        {
            var userId = await _userManager.GetUserIdAsync(user);
            var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
            code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
            EmailConfirmationUrl = Url.Page(
                "/Account/ConfirmEmail",
                pageHandler: null,
                values: new { area = "Identity", userId = userId, code = code, returnUrl = returnUrl },
                protocol: Request.Scheme);
        }

        return Page();
    }
}

登录

在以下情况中,会显示“登录”窗体:

  • 选择“登录”链接时。
  • 用户尝试访问他们无权访问的受限页面,或者他们未通过系统身份验证时。

提交登录页上的窗体时,将调用 OnPostAsync 操作。 对 _signInManager 对象调用 PasswordSignInAsync

public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
    returnUrl = returnUrl ?? Url.Content("~/");

    if (ModelState.IsValid)
    {
        // This doesn't count login failures towards account lockout
        // To enable password failures to trigger account lockout, 
        // set lockoutOnFailure: true
        var result = await _signInManager.PasswordSignInAsync(Input.Email,
                           Input.Password, Input.RememberMe, lockoutOnFailure: true);
        if (result.Succeeded)
        {
            _logger.LogInformation("User logged in.");
            return LocalRedirect(returnUrl);
        }
        if (result.RequiresTwoFactor)
        {
            return RedirectToPage("./LoginWith2fa", new
            {
                ReturnUrl = returnUrl,
                RememberMe = Input.RememberMe
            });
        }
        if (result.IsLockedOut)
        {
            _logger.LogWarning("User account locked out.");
            return RedirectToPage("./Lockout");
        }
        else
        {
            ModelState.AddModelError(string.Empty, "Invalid login attempt.");
            return Page();
        }
    }

    // If we got this far, something failed, redisplay form
    return Page();
}

若要了解如何做出授权决定,请参阅 ASP.NET Core 中的授权简介

注销

“注销”链接会调用 LogoutModel.OnPost 操作。

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;

namespace WebApp1.Areas.Identity.Pages.Account
{
    [AllowAnonymous]
    public class LogoutModel : PageModel
    {
        private readonly SignInManager<IdentityUser> _signInManager;
        private readonly ILogger<LogoutModel> _logger;

        public LogoutModel(SignInManager<IdentityUser> signInManager, ILogger<LogoutModel> logger)
        {
            _signInManager = signInManager;
            _logger = logger;
        }

        public void OnGet()
        {
        }

        public async Task<IActionResult> OnPost(string returnUrl = null)
        {
            await _signInManager.SignOutAsync();
            _logger.LogInformation("User logged out.");
            if (returnUrl != null)
            {
                return LocalRedirect(returnUrl);
            }
            else
            {
                return RedirectToPage();
            }
        }
    }
}

在前面的代码中,代码 return RedirectToPage(); 需要是重定向,这样浏览器才会执行新请求,用户的标识才会更新。

SignOutAsync 清除存储在 中的 cookie用户声明。

Post 在 中 Pages/Shared/_LoginPartial.cshtml指定:

@using Microsoft.AspNetCore.Identity
@inject SignInManager<IdentityUser> SignInManager
@inject UserManager<IdentityUser> UserManager

<ul class="navbar-nav">
@if (SignInManager.IsSignedIn(User))
{
    <li class="nav-item">
        <a  class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" 
                                              title="Manage">Hello @User.Identity.Name!</a>
    </li>
    <li class="nav-item">
        <form class="form-inline" asp-area="Identity" asp-page="/Account/Logout" 
                                  asp-route-returnUrl="@Url.Page("/", new { area = "" })" 
                                  method="post" >
            <button  type="submit" class="nav-link btn btn-link text-dark">Logout</button>
        </form>
    </li>
}
else
{
    <li class="nav-item">
        <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Register">Register</a>
    </li>
    <li class="nav-item">
        <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Login">Login</a>
    </li>
}
</ul>

测试 Identity

默认 Web 项目模板允许匿名访问主页。 若要测试 Identity,请添加 [Authorize]

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;

namespace WebApp1.Pages
{
    [Authorize]
    public class PrivacyModel : PageModel
    {
        private readonly ILogger<PrivacyModel> _logger;

        public PrivacyModel(ILogger<PrivacyModel> logger)
        {
            _logger = logger;
        }

        public void OnGet()
        {
        }
    }
}

如果已登录,请注销。请运行应用并选择Privacy链接。 将被重定向到登录页。

了解 Identity

若要更详细地了解 Identity:

Identity 组件

所有依赖 Identity 的 NuGet 包都包含在 ASP.NET Core 共享框架中。

的主包IdentityMicrosoft。AspNetCore.Identity 此包包含 ASP.NET Core Identity的核心接口集,由 包含Microsoft.AspNetCore.Identity.EntityFrameworkCore

迁移到 ASP.NET Core Identity

有关迁移现有 Identity 存储的详细信息和指南,请参阅 迁移身份验证和 Identity

设置密码强度

有关设置最小密码要求的示例,请查看配置

AddDefaultIdentity 和 AddIdentity

ASP.NET Core 2.1 中引入了 AddDefaultIdentity。 调用 AddDefaultIdentity 类似于调用以下内容:

有关详细信息 ,请参阅 AddDefaultIdentity source

禁止发布静态 Identity 资产

若要防止将静态 Identity 资产(Identity UI 的样式表和 JavaScript 文件)发布到 Web 根,请将以下 ResolveStaticWebAssetsInputsDependsOn 属性和 RemoveIdentityAssets 目标添加到应用的项目文件中:

<PropertyGroup>
  <ResolveStaticWebAssetsInputsDependsOn>RemoveIdentityAssets</ResolveStaticWebAssetsInputsDependsOn>
</PropertyGroup>

<Target Name="RemoveIdentityAssets">
  <ItemGroup>
    <StaticWebAsset Remove="@(StaticWebAsset)" Condition="%(SourceId) == 'Microsoft.AspNetCore.Identity.UI'" />
  </ItemGroup>
</Target>

后续步骤

作者:Rick Anderson

ASP.NET Core Identity:

  • 一个 API,它支持用户界面 (UI) 登录功能。
  • 管理用户、密码、配置文件数据、角色、声明、令牌、电子邮件确认等等。

用户可使用存储在 Identity 中的登录信息创建帐户,或者可使用外部登录提供程序。 支持的外部登录提供程序包括 Facebook、Google、Microsoft 帐户和 Twitter

有关如何全局要求所有用户进行身份验证的信息,请参阅需要通过身份验证的用户

Identity GitHub 上提供了源代码。 支架 Identity 和 查看生成的文件,以查看与 Identity的模板交互。

Identity 通常使用 SQL Server 数据库进行配置,以存储用户名、密码和配置文件数据。 或者,可使用其他持久性存储,例如 Azure 表存储。

在本主题中,你将学习如何使用 Identity 来注册、登录和注销用户。 注意:模板会将用户的用户名和电子邮件看做是相同的。 若要更详细了解如何创建使用 Identity 的应用,请参阅后续步骤

Microsoft 标识平台是:

  • Azure Active Directory (Azure AD) 开发人员平台的演变。
  • ASP.NET Core 应用中用于身份验证和授权的替代标识解决方案。
  • 与 ASP.NET Core Identity无关。

ASP.NET Core Identity 将用户界面 (UI) 登录功能添加到 ASP.NET Core Web 应用。 若要保护 Web API 和 SPA,请使用以下项之一:

IdentityServer4 是适用于 ASP.NET Core 的 OpenID Connect 和 OAuth 2.0 框架。 IdentityServer4 支持以下安全功能:

  • 身份验证即服务 (AaaS)
  • 跨多个应用程序类型的单一登录/注销 (SSO)
  • API 的访问控制
  • Federation Gateway

有关详细信息,请参阅欢迎使用 Server4。

查看或下载示例代码下载方法)。

创建使用身份验证的 Web 应用

使用个人用户帐户创建“ASP.NET Core Web 应用程序”项目。

  • 选择“文件”>“新建”>“项目” 。
  • 选择“ASP.NET Core Web 应用程序”。 将项目命名为 WebApp1,使其命名空间与项目下载项相同。 单击“确定” 。
  • 选择 ASP.NET Core Web 应用程序,然后选择“更改身份验证”。
  • 选择“个人用户帐户”,然后单击“确定”。

生成的项目将 ASP.NET CoreIdentity作为Razor类库提供。 IdentityRazor 类库公开具有 Identity 区域的终结点。 例如:

  • /Identity/Account/Login
  • Identity Account/Logout
  • /Identity/Account/Manage

应用迁移

应用迁移以初始化数据库。

在包管理器控制台 (PMC) 中运行以下命令:

PM> Update-Database

测试注册和登录

运行应用并注册用户。 根据屏幕大小,你可能需要选择导航切换按钮来查看“注册”和“登录”链接。

查看 Identity 数据库

  • 在“视图”菜单中选择“SQL Server 对象资源管理器”(SSOX)。
  • 导航到“(localdb)MSSQLLocalDB(SQL Server 13)”。 右键单击“dbo.AspNetUsers”>“查看数据”:

SQL Server 对象资源管理器 中 AspNetUsers 表上的上下文菜单

配置 Identity 服务

这些服务添加在 ConfigureServices 中。 典型模式是调用所有 Add{Service} 方法,然后调用所有 services.Configure{Service} 方法。

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
     // options.UseSqlite(
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection")));
    services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
        .AddEntityFrameworkStores<ApplicationDbContext>();
    services.AddRazorPages();

    services.Configure<IdentityOptions>(options =>
    {
        // Password settings.
        options.Password.RequireDigit = true;
        options.Password.RequireLowercase = true;
        options.Password.RequireNonAlphanumeric = true;
        options.Password.RequireUppercase = true;
        options.Password.RequiredLength = 6;
        options.Password.RequiredUniqueChars = 1;

        // Lockout settings.
        options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
        options.Lockout.MaxFailedAccessAttempts = 5;
        options.Lockout.AllowedForNewUsers = true;

        // User settings.
        options.User.AllowedUserNameCharacters =
        "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
        options.User.RequireUniqueEmail = false;
    });

    services.ConfigureApplicationCookie(options =>
    {
        // Cookie settings
        options.Cookie.HttpOnly = true;
        options.ExpireTimeSpan = TimeSpan.FromMinutes(5);

        options.LoginPath = "/Identity/Account/Login";
        options.AccessDeniedPath = "/Identity/Account/AccessDenied";
        options.SlidingExpiration = true;
    });
}

上述突出显示的代码用默认选项值来配置 Identity。 可通过依赖关系注入向应用提供服务。

通过调用 UseAuthentication 启用 Identity。 UseAuthentication 向请求管道添加身份验证中间件

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseDatabaseErrorPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthentication();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}
public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        // options.UseSqlite(
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection")));
    services.AddDatabaseDeveloperPageExceptionFilter();
    services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
        .AddEntityFrameworkStores<ApplicationDbContext>();
    services.AddRazorPages();

    services.Configure<IdentityOptions>(options =>
    {
        // Password settings.
        options.Password.RequireDigit = true;
        options.Password.RequireLowercase = true;
        options.Password.RequireNonAlphanumeric = true;
        options.Password.RequireUppercase = true;
        options.Password.RequiredLength = 6;
        options.Password.RequiredUniqueChars = 1;

        // Lockout settings.
        options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
        options.Lockout.MaxFailedAccessAttempts = 5;
        options.Lockout.AllowedForNewUsers = true;

        // User settings.
        options.User.AllowedUserNameCharacters =
        "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
        options.User.RequireUniqueEmail = false;
    });

    services.ConfigureApplicationCookie(options =>
    {
        // Cookie settings
        options.Cookie.HttpOnly = true;
        options.ExpireTimeSpan = TimeSpan.FromMinutes(5);

        options.LoginPath = "/Identity/Account/Login";
        options.AccessDeniedPath = "/Identity/Account/AccessDenied";
        options.SlidingExpiration = true;
    });
}

上述代码用默认选项值来配置 Identity。 可通过依赖关系注入向应用提供服务。

通过调用 UseAuthentication 启用 Identity。 UseAuthentication 向请求管道添加身份验证中间件

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseMigrationsEndPoint();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthentication();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

模板生成的应用不使用授权app.UseAuthorization 会被包含来内,确保在应用添加授权时按正确的顺序添加它。 必须按上述代码中所示的顺序调用 UseRoutingUseAuthenticationUseAuthorizationUseEndpoints

有关 IdentityOptionsStartup 的详细信息,请参阅 IdentityOptions应用程序启动

构建 Register、Login、LogOut 和 RegisterConfirmation 的基架

添加 RegisterLoginLogOutRegisterConfirmation 文件。 按照 基架标识进入具有 Razor 授权说明的项目 ,生成本部分所示的代码。

检查注册

当用户单击 Register 页面上的“注册”按钮时,会调用 RegisterModel.OnPostAsync 操作。 用户由 CreateAsync(TUser) 在 对象上 _userManager 创建:

public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
    returnUrl = returnUrl ?? Url.Content("~/");
    ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync())
                                          .ToList();
    if (ModelState.IsValid)
    {
        var user = new IdentityUser { UserName = Input.Email, Email = Input.Email };
        var result = await _userManager.CreateAsync(user, Input.Password);
        if (result.Succeeded)
        {
            _logger.LogInformation("User created a new account with password.");

            var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
            code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
            var callbackUrl = Url.Page(
                "/Account/ConfirmEmail",
                pageHandler: null,
                values: new { area = "Identity", userId = user.Id, code = code },
                protocol: Request.Scheme);

            await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
                $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");

            if (_userManager.Options.SignIn.RequireConfirmedAccount)
            {
                return RedirectToPage("RegisterConfirmation", 
                                      new { email = Input.Email });
            }
            else
            {
                await _signInManager.SignInAsync(user, isPersistent: false);
                return LocalRedirect(returnUrl);
            }
        }
        foreach (var error in result.Errors)
        {
            ModelState.AddModelError(string.Empty, error.Description);
        }
    }

    // If we got this far, something failed, redisplay form
    return Page();
}

禁用默认帐户验证

使用默认模板时,会将用户重定向到 Account.RegisterConfirmation,用户可以从中选择一个链接来确认帐户。 默认值Account.RegisterConfirmation仅用于测试,应在生产应用中禁用自动帐户验证。

若要要求已确认帐户并阻止在注册时立即登录,请在 中/Areas/Identity/Pages/Account/RegisterConfirmation.cshtml.cs设置 DisplayConfirmAccountLink = false

[AllowAnonymous]
public class RegisterConfirmationModel : PageModel
{
    private readonly UserManager<IdentityUser> _userManager;
    private readonly IEmailSender _sender;

    public RegisterConfirmationModel(UserManager<IdentityUser> userManager, IEmailSender sender)
    {
        _userManager = userManager;
        _sender = sender;
    }

    public string Email { get; set; }

    public bool DisplayConfirmAccountLink { get; set; }

    public string EmailConfirmationUrl { get; set; }

    public async Task<IActionResult> OnGetAsync(string email, string returnUrl = null)
    {
        if (email == null)
        {
            return RedirectToPage("/Index");
        }

        var user = await _userManager.FindByEmailAsync(email);
        if (user == null)
        {
            return NotFound($"Unable to load user with email '{email}'.");
        }

        Email = email;
        // Once you add a real email sender, you should remove this code that lets you confirm the account
        DisplayConfirmAccountLink = false;
        if (DisplayConfirmAccountLink)
        {
            var userId = await _userManager.GetUserIdAsync(user);
            var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
            code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
            EmailConfirmationUrl = Url.Page(
                "/Account/ConfirmEmail",
                pageHandler: null,
                values: new { area = "Identity", userId = userId, code = code, returnUrl = returnUrl },
                protocol: Request.Scheme);
        }

        return Page();
    }
}

登录

在以下情况中,会显示“登录”窗体:

  • 选择“登录”链接时。
  • 用户尝试访问他们无权访问的受限页面,或者他们未通过系统身份验证时。

提交登录页上的窗体时,将调用 OnPostAsync 操作。 对 _signInManager 对象调用 PasswordSignInAsync

public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
    returnUrl = returnUrl ?? Url.Content("~/");

    if (ModelState.IsValid)
    {
        // This doesn't count login failures towards account lockout
        // To enable password failures to trigger account lockout, 
        // set lockoutOnFailure: true
        var result = await _signInManager.PasswordSignInAsync(Input.Email,
                           Input.Password, Input.RememberMe, lockoutOnFailure: true);
        if (result.Succeeded)
        {
            _logger.LogInformation("User logged in.");
            return LocalRedirect(returnUrl);
        }
        if (result.RequiresTwoFactor)
        {
            return RedirectToPage("./LoginWith2fa", new
            {
                ReturnUrl = returnUrl,
                RememberMe = Input.RememberMe
            });
        }
        if (result.IsLockedOut)
        {
            _logger.LogWarning("User account locked out.");
            return RedirectToPage("./Lockout");
        }
        else
        {
            ModelState.AddModelError(string.Empty, "Invalid login attempt.");
            return Page();
        }
    }

    // If we got this far, something failed, redisplay form
    return Page();
}

若要了解如何做出授权决定,请参阅 ASP.NET Core 中的授权简介

注销

“注销”链接会调用 LogoutModel.OnPost 操作。

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;

namespace WebApp1.Areas.Identity.Pages.Account
{
    [AllowAnonymous]
    public class LogoutModel : PageModel
    {
        private readonly SignInManager<IdentityUser> _signInManager;
        private readonly ILogger<LogoutModel> _logger;

        public LogoutModel(SignInManager<IdentityUser> signInManager, ILogger<LogoutModel> logger)
        {
            _signInManager = signInManager;
            _logger = logger;
        }

        public void OnGet()
        {
        }

        public async Task<IActionResult> OnPost(string returnUrl = null)
        {
            await _signInManager.SignOutAsync();
            _logger.LogInformation("User logged out.");
            if (returnUrl != null)
            {
                return LocalRedirect(returnUrl);
            }
            else
            {
                return RedirectToPage();
            }
        }
    }
}

在前面的代码中,代码 return RedirectToPage(); 需要是重定向,这样浏览器才会执行新请求,用户的标识才会更新。

SignOutAsync 清除存储在 中的 cookie用户声明。

Post 在 中 Pages/Shared/_LoginPartial.cshtml指定:

@using Microsoft.AspNetCore.Identity
@inject SignInManager<IdentityUser> SignInManager
@inject UserManager<IdentityUser> UserManager

<ul class="navbar-nav">
@if (SignInManager.IsSignedIn(User))
{
    <li class="nav-item">
        <a  class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" 
                                              title="Manage">Hello @User.Identity.Name!</a>
    </li>
    <li class="nav-item">
        <form class="form-inline" asp-area="Identity" asp-page="/Account/Logout" 
                                  asp-route-returnUrl="@Url.Page("/", new { area = "" })" 
                                  method="post" >
            <button  type="submit" class="nav-link btn btn-link text-dark">Logout</button>
        </form>
    </li>
}
else
{
    <li class="nav-item">
        <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Register">Register</a>
    </li>
    <li class="nav-item">
        <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Login">Login</a>
    </li>
}
</ul>

测试 Identity

默认 Web 项目模板允许匿名访问主页。 若要测试 Identity,请添加 [Authorize]

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;

namespace WebApp1.Pages
{
    [Authorize]
    public class PrivacyModel : PageModel
    {
        private readonly ILogger<PrivacyModel> _logger;

        public PrivacyModel(ILogger<PrivacyModel> logger)
        {
            _logger = logger;
        }

        public void OnGet()
        {
        }
    }
}

如果已登录,请注销。请运行应用并选择Privacy链接。 将被重定向到登录页。

了解 Identity

若要更详细地了解 Identity:

Identity 组件

所有依赖 Identity 的 NuGet 包都包含在 ASP.NET Core 共享框架中。

的主包IdentityMicrosoft。AspNetCore.Identity 此包包含 ASP.NET Core Identity的核心接口集,由 包含Microsoft.AspNetCore.Identity.EntityFrameworkCore

迁移到 ASP.NET Core Identity

有关迁移现有 Identity 存储的详细信息和指南,请参阅 迁移身份验证和 Identity

设置密码强度

有关设置最小密码要求的示例,请查看配置

AddDefaultIdentity 和 AddIdentity

ASP.NET Core 2.1 中引入了 AddDefaultIdentity。 调用 AddDefaultIdentity 类似于调用以下内容:

有关详细信息 ,请参阅 AddDefaultIdentity 源

禁止发布静态 Identity 资产

若要防止将静态 Identity 资产(Identity UI 的样式表和 JavaScript 文件)发布到 Web 根,请将以下 ResolveStaticWebAssetsInputsDependsOn 属性和 RemoveIdentityAssets 目标添加到应用的项目文件中:

<PropertyGroup>
  <ResolveStaticWebAssetsInputsDependsOn>RemoveIdentityAssets</ResolveStaticWebAssetsInputsDependsOn>
</PropertyGroup>

<Target Name="RemoveIdentityAssets">
  <ItemGroup>
    <StaticWebAsset Remove="@(StaticWebAsset)" Condition="%(SourceId) == 'Microsoft.AspNetCore.Identity.UI'" />
  </ItemGroup>
</Target>

后续步骤