ASP.NET Core 上的标识简介

作者:Rick Anderson

ASP.NET Core 标识:

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

用户可以创建一个具有存储在标识中的登录信息的帐户,也可以使用外部登录提供程序。 支持的外部登录提供程序包括Facebook、Google、Microsoft 帐户和 Twitter

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

通常使用 SQL Server 数据库配置标识,以存储用户名、密码和配置文件数据。 另外,还可以使用另一个永久性存储,例如 Azure 表存储。

本主题介绍如何使用标识注册、登录和注销用户。 有关创建使用标识的应用的更多详细说明,请参阅本文末尾的后续步骤部分。

Microsoft 标识平台是:

  • Azure Active Directory (Azure AD)开发人员平台的演变。
  • 与 ASP.NET Core 标识无关。

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

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

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

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

查看或下载示例代码如何下载)

创建具有身份验证的 Web 应用

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

生成的项目提供ASP.NET Core 标识作为Razor 类库 标识 Razor 类库公开 Identity 区域的终结点。 例如:

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

应用迁移

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

测试注册和登录

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

查看标识数据库

配置标识服务

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

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        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;
    });
}

前面突出显示的代码用默认选项值配置标识。 服务通过依赖关系注入提供给应用程序。

通过调用 UseAuthentication来启用标识。 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();
    });
}

模板生成的应用不使用授权 包括 app.UseAuthorization 以确保在应用添加授权时,按正确的顺序添加。 必须按前面的代码中所示的顺序调用 UseRoutingUseAuthenticationUseAuthorizationUseEndpoints

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

基架注册、登录和注销

检查注册

当用户单击 "注册" 链接时,将调用 RegisterModel.OnPostAsync 操作。 用户是通过CreateAsync_userManager 对象创建的。 _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();
}

如果已成功创建用户,则会通过调用 _signInManager.SignInAsync登录该用户。

请参阅帐户确认以了解在注册时要阻止立即登录的步骤。

Log in

发生下列情况时,会显示登录窗体:

  • 选择 "登录" 链接。
  • 用户尝试访问他们无权访问的受限制的页面,未经系统的身份验证。

提交登录页上的窗体时,将调用 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();
}

Base Controller 类公开可从控制器方法访问的 User 属性。 例如,可以枚举 User.Claims 并做出授权决策。 有关详情,请参阅在 ASP.NET Core 中授权简介

Log out

"注销" 链接调用 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>

测试标识

默认 web 项目模板允许匿名访问主页。 若要测试标识,请添加[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()
        {
        }
    }
}

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

浏览标识

更详细地了解标识:

标识组件

所有标识相关 NuGet 包都包含在ASP.NET Core 共享框架中。

标识的主包为AspNetCore 此程序包包含用于 ASP.NET Core 标识的核心接口集,由 Microsoft.AspNetCore.Identity.EntityFrameworkCore包含。

迁移到 ASP.NET Core 标识

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

设置密码强度

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

AddDefaultIdentity 和 AddIdentity

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

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

禁止发布静态标识资产

若要防止将静态标识资产(用于标识 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 标识是将登录功能添加到 ASP.NET Core 应用的成员资格系统。 用户可以创建一个具有存储在标识中的登录信息的帐户,也可以使用外部登录提供程序。 支持的外部登录提供程序包括Facebook、Google、Microsoft 帐户和 Twitter

可以使用 SQL Server 数据库配置标识,以存储用户名、密码和配置文件数据。 另外,还可以使用另一个永久性存储,例如 Azure 表存储。

查看或下载示例代码如何下载)

本主题介绍如何使用标识注册、登录和注销用户。 有关创建使用标识的应用的更多详细说明,请参阅本文末尾的后续步骤部分。

AddDefaultIdentity 和 AddIdentity

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

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

创建具有身份验证的 Web 应用

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

生成的项目提供ASP.NET Core 标识作为Razor 类库 标识 Razor 类库公开 Identity 区域的终结点。 例如:

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

应用迁移

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

测试注册和登录

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

查看标识数据库

配置标识服务

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

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
    {
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });

    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection")));
    services.AddDefaultIdentity<IdentityUser>()
        .AddDefaultUI(UIFramework.Bootstrap4)
        .AddEntityFrameworkStores<ApplicationDbContext>();

    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;
    });

    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

前面的代码用默认选项值配置标识。 服务通过依赖关系注入提供给应用程序。

通过调用UseAuthentication来启用标识。 UseAuthentication 将身份验证中间件添加到请求管道。

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

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

    app.UseAuthentication();

    app.UseMvc();
}

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

基架注册、登录和注销

按照基架标识操作,并使用授权说明生成本部分中所示的代码。

检查注册

当用户单击 "注册" 链接时,将调用 RegisterModel.OnPostAsync 操作。 用户是通过CreateAsync_userManager 对象创建的。 _userManager 由依赖关系注入提供):

public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
    returnUrl = returnUrl ?? Url.Content("~/");
    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);
            var callbackUrl = Url.Page(
                "/Account/ConfirmEmail",
                pageHandler: null,
                values: new { 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>.");

            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();
}

如果已成功创建用户,则会通过调用 _signInManager.SignInAsync登录该用户。

注意: 请参阅帐户确认以了解在注册时要阻止立即登录的步骤。

Log in

发生下列情况时,会显示登录窗体:

  • 选择 "登录" 链接。
  • 用户尝试访问他们无权访问的受限制的页面,未经系统的身份验证。

提交登录页上的窗体时,将调用 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();
}

Base Controller 类公开可从控制器方法访问的 User 属性。 例如,可以枚举 User.Claims 并做出授权决策。 有关详情,请参阅在 ASP.NET Core 中授权简介

Log out

"注销" 链接调用 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
            {
                // This needs to be a redirect so that the browser performs a new
                // request and the identity for the user gets updated.
                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>


测试标识

默认 web 项目模板允许匿名访问主页。 若要测试标识,请将[Authorize]添加到 "隐私" 页。

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

namespace WebApp1.Pages
{
    [Authorize]
    public class PrivacyModel : PageModel
    {
        public void OnGet()
        {
        }
    }
}

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

浏览标识

更详细地了解标识:

标识组件

所有标识相关 NuGet 包都包含在AspNetCore 元包中。

标识的主包为AspNetCore 此程序包包含用于 ASP.NET Core 标识的核心接口集,由 Microsoft.AspNetCore.Identity.EntityFrameworkCore包含。

迁移到 ASP.NET Core 标识

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

设置密码强度

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

后续步骤

上一篇:ASP.NET Core 身份验证概述

下一篇:Spa 的身份验证和授权

关注微信小程序
程序员编程王-随时随地学编程

扫描二维码
程序员编程王

扫一扫关注最新编程教程