ASP.NET Core上的 Identity 简介

ASP.NET Core Identity:

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

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

有关如何要求对所有应用用户进行身份验证的信息,请参阅 创建具有受授权保护的用户数据的 ASP.NET Core应用

GitHub上提供了 Identity 源代码搭建 Identity 并查看生成的文件,以检查模板与 Identity 的交互情况。

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

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

有关 Identity 应用中的 Blazor 的详细信息,请参阅 ASP.NET Core Blazor 身份验证和授权及其后面的文章Blazor 文档中。

ASP.NET Core Identity 与 Microsoft 标识平台无关。 微软身份验证平台是:

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

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

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

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

Important

Duende Software 可能会要求你为 Duende Identity Server 的生产使用支付许可证费用。 有关详细信息,请参阅从 ASP.NET Core 在 .NET 5 迁移到 .NET 6

有关详细信息,请参阅 Duende Identity Server 文档(Duende Software 网站)

View 或下载示例代码如何下载)。

创建带有身份验证的 Blazor Web App

使用个人帐户创建 ASP.NET Core Blazor Web App project。

Note

Razor有关 Pages 体验,请参阅“创建Razor包含身份验证的 Pages 应用”部分。

有关 MVC 体验,请参阅“ 使用身份验证创建 MVC 应用 ”部分。

  • 选择 Blazor Web App 模板。 选择“下一步”。
  • 进行以下选择:
    • 身份验证类型个人帐户
    • 交互式呈现模式:服务器
    • 交互位置全局
  • 选择“创建”

生成的project包括 IdentityRazor 组件。 这些组件位于服务器project的 Components/Account 文件夹中。 例如:

  • Components/Account/Pages/Register.razor
  • Components/Account/Pages/Login.razor
  • Components/Account/Pages/Manage/ChangePassword.razor

Identity Razor 组件在文档中针对具体用例单独描述,并在每个版本中可能会发生变化。 当您使用个人帐户生成 Blazor Web App 时,生成的项目中包含 IdentityRazor 组件。 组件也可以在 项目模板( GitHub 存储库)的服务器项目文件夹中查看

Note

指向.NET引用源的文档链接通常加载存储库的默认分支,该分支表示下一版本的.NET的当前开发。 若要为特定版本选择标签,请使用“切换分支或标签”下拉菜单。 有关详细信息,请参阅 如何选择 ASP.NET Core源代码的版本标记(dotnet/AspNetCore.Docs #26205)

有关详细信息,请参阅 ASP.NET Core Blazor 身份验证和授权以及 Blazor 文档中的后续文章。 主要 ASP.NET Core文档集的 Security 和 Identity 区域中的大部分文章适用于 Blazor 应用。 但是, Blazor 文档集包含取代或添加信息的文章和指南。 建议先研究一般 ASP.NET Core文档集,然后访问 BlazorSecurity 和 Identity 文档中的文章。

创建带有身份验证的 Razor Pages 应用

使用个人帐户创建 ASP.NET Core Web 应用程序(Razor页面)项目。

  • 选择 ASP.NET Core Web 应用(Razor Pages)模板。 选择“下一步”。
  • 对于 身份验证类型,请选择 “个人帐户”。
  • 选择“创建”

生成的项目提供 ASP.NET Core Identity 作为 Razor 类库(RCL)。 Identity Razor 类库提供了属于 Identity 区域的终结点。 例如:

  • Areas/Identity/Pages/Account/Register
  • Areas/Identity/Pages/Account/Login
  • Areas/Identity/Pages/Account/Manage/ChangePassword

在特定用例的文档中,各个页面会被单独描述并且可能随着每个发布版本进行更改。 若要查看 RCL 中的所有页面,请参阅 ASP.NET Core 引用源(dotnet/aspnetcore GitHub 存储库,Identity/UI/src/Areas/Identity/Pages 文件夹)。 可以搭建单个页面或所有页面到应用中。 有关详细信息,请参阅 ASP.NET Core 项目中的 Scaffold Identity

使用身份验证创建 MVC 应用

使用个人帐户创建 ASP.NET Core MVC project。

  • 选择 ASP.NET Core Web 应用 (Model-View-Controller) 模板。 选择“下一步”。
  • 对于 身份验证类型,请选择 “个人帐户”。
  • 选择“创建”

生成的项目提供 ASP.NET Core Identity 作为 Razor 类库(RCL)。 Identity Razor 类库基于 Razor Pages 并通过 Identity 区域公开终结点。 例如:

  • Areas/Identity/Pages/Account/Register
  • Areas/Identity/Pages/Account/Login
  • Areas/Identity/Pages/Account/Manage/ChangePassword

在特定用例的文档中,各个页面会被单独描述并且可能随着每个发布版本进行更改。 若要查看 RCL 中的所有页面,请参阅 ASP.NET Core 引用源(dotnet/aspnetcore GitHub 存储库,Identity/UI/src/Areas/Identity/Pages 文件夹)。 可以搭建单个页面或所有页面到应用中。 有关详细信息,请参阅 ASP.NET Core 项目中的 Scaffold Identity

应用迁移

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

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

Update-Database

测试注册和登录

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

查看 Identity 数据库

  • View 菜单中,选择 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。 可通过依赖关系注入向应用提供服务。

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

模板生成的应用不使用授权。 包含 app.UseAuthorization 是为了确保如果应用添加授权,它会按正确的顺序被添加。 必须按上述代码中所示的顺序调用 UseRoutingUseAuthenticationUseAuthorization

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

ASP.NET Core Identity 指标

ASP.NET Core Identity指标为用户管理和身份验证过程提供监视功能。 这些指标可帮助你检测可能指示安全威胁的异常登录模式,跟踪标识作的性能,并了解用户如何与身份验证功能(如双重身份验证)交互。 这种可观测性对于具有严格安全要求或遇到高身份验证流量的应用尤其有用。

有关可用指标及其用法的完整详细信息,请参阅ASP.NET Core指标

构建注册、登录、注销和注册确认的基架

添加 RegisterLoginLogOutRegisterConfirmation 文件。 按照使用授权将Scaffold标识集成到中的说明,生成在本节中显示的代码。

检查寄存器

当用户单击 页面上的“注册”按钮时,会调用 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 仅用于测试,应在生产应用中禁用自动帐户验证

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

[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 操作。 在 PasswordSignInAsync 对象上调用 _signInManager

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 中的用户声明。

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

@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 包。

Identity 的主包是 Microsoft.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 目标添加到应用的project文件中:

<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

有关如何要求对所有应用用户进行身份验证的信息,请参阅 创建具有受授权保护的用户数据的 ASP.NET Core应用

GitHub上提供了 Identity 源代码搭建 Identity 并查看生成的文件,以检查模板与 Identity 的交互情况。

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

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

ASP.NET Core Identity 与 Microsoft 标识平台无关。 微软身份验证平台是:

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

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

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

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

Important

Duende Software 可能会要求你为 Duende Identity Server 的生产使用支付许可证费用。 有关详细信息,请参阅从 ASP.NET Core 在 .NET 5 迁移到 .NET 6

有关详细信息,请参阅 Duende Identity Server 文档(Duende Software 网站)

View 或下载示例代码如何下载)。

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

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

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

生成的项目提供 ASP.NET Core Identity 作为 Razor 类库。 Identity Razor 类库提供了属于 Identity 区域的终结点。 例如:

  • /Identity/账户/登录
  • /Identity/Account/Logout
  • /Identity/账户/管理

应用迁移

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

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

Update-Database

测试注册和登录

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

查看 Identity 数据库

  • View 菜单中,选择 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。 可通过依赖关系注入向应用提供服务。

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

模板生成的应用不使用授权。 包含 app.UseAuthorization 是为了确保如果应用添加授权,它会按正确的顺序被添加。 必须按上述代码中所示的顺序调用 UseRoutingUseAuthenticationUseAuthorization

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

构建注册、登录、注销和注册确认的基架

添加 RegisterLoginLogOutRegisterConfirmation 文件。 按照使用授权将Scaffold标识集成到中的说明,生成在本节中显示的代码。

检查寄存器

当用户单击 页面上的“注册”按钮时,会调用 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 仅用于测试,应在生产应用中禁用自动帐户验证

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

[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 操作。 在 PasswordSignInAsync 对象上调用 _signInManager

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 中的用户声明。

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

@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 包。

Identity 的主包是 Microsoft.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 目标添加到应用的project文件中:

<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

有关如何要求对所有应用用户进行身份验证的信息,请参阅 创建具有受授权保护的用户数据的 ASP.NET Core应用

GitHub上提供了 Identity 源代码搭建 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 apps。 若要保护 Web API 和 SPA,请使用以下项之一:

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

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

有关详细信息,请参阅 Duende IdentityServer 概述

有关其他身份验证提供程序的详细信息,请参阅 ASP.NET Core 的 Community OSS 身份验证选项

View 或下载示例代码如何下载)。

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

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

  • 选择 File>New>Project
  • 选择 ASP.NET Core Web 应用程序。 将 project WebApp1 命名为与project下载相同的命名空间。 单击“确定”。
  • 选择 ASP.NET Core Web Application,然后选择 Change Authentication
  • 选择“个人用户帐户”,然后单击“确定”。

生成的项目提供 ASP.NET Core Identity 作为 Razor 类库。 Identity Razor 类库提供了属于 Identity 区域的终结点。 例如:

  • /Identity/账户/登录
  • /Identity/Account/Logout
  • /Identity/账户/管理

应用迁移

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

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

PM> Update-Database

测试注册和登录

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

查看 Identity 数据库

  • View 菜单中,选择 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。 可通过依赖关系注入向应用提供服务。

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

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。 可通过依赖关系注入向应用提供服务。

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

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应用程序启动

构建注册、登录、注销和注册确认的基架

添加 RegisterLoginLogOutRegisterConfirmation 文件。 按照使用授权将Scaffold标识集成到中的说明,生成在本节中显示的代码。

检查寄存器

当用户单击 页面上的“注册”按钮时,会调用 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 仅用于测试,应在生产应用中禁用自动帐户验证

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

[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 操作。 在 PasswordSignInAsync 对象上调用 _signInManager

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 中的用户声明。

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

@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 包。

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

迁移到 ASP.NET Core Identity

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

设置密码强度

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

阻止发布静态 Identity 资源

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

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

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

后续步骤