将身份验证和 Identity 迁移到 ASP.NET Core 2.0

作者:Scott AddieHao Kung

ASP.NET Core 2.0 具有用于身份验证和 Identity 的新模型,可使用服务简化配置。 可将使用身份验证或 Identity 的 ASP.NET Core 1.x 应用程序更新为使用新模型,如下所述。

更新命名空间

在 1.x 中,类(例如IdentityRoleIdentityUser)位于 Microsoft.AspNetCore.Identity.EntityFrameworkCore 命名空间中。

在 2.0 中,Microsoft.AspNetCore.Identity 命名空间成为几个这样的类的新 home。 对于默认 Identity 代码,受影响的类包括 ApplicationUserStartup。 调整 using 语句以解析受影响的引用。

身份验证中间件和服务

1.x 项目中通过中间件配置身份验证。 系统为要支持的每个身份验证方案调用中间件方法。

下面的 1.x 示例在 Startup.cs 中通过 Identity 配置 Facebook 身份验证:

public void ConfigureServices(IServiceCollection services)
{
    services.AddIdentity<ApplicationUser, IdentityRole>()
            .AddEntityFrameworkStores<ApplicationDbContext>();
}

public void Configure(IApplicationBuilder app, ILoggerFactory loggerfactory)
{
    app.UseIdentity();
    app.UseFacebookAuthentication(new FacebookOptions {
        AppId = Configuration["auth:facebook:appid"],
        AppSecret = Configuration["auth:facebook:appsecret"]
    });
}

2.0 项目中通过服务配置身份验证。 每个身份验证方案都在 Startup.csConfigureServices 方法中注册。 UseIdentity 方法通过 UseAuthentication 注册。

下面的 2.0 示例在 Startup.cs 中通过 Identity 配置 Facebook 身份验证:

public void ConfigureServices(IServiceCollection services)
{
    services.AddIdentity<ApplicationUser, IdentityRole>()
            .AddEntityFrameworkStores<ApplicationDbContext>();

    // If you want to tweak Identity cookies, they're no longer part of IdentityOptions.
    services.ConfigureApplicationCookie(options => options.LoginPath = "/Account/LogIn");
    services.AddAuthentication()
            .AddFacebook(options =>
            {
                options.AppId = Configuration["auth:facebook:appid"];
                options.AppSecret = Configuration["auth:facebook:appsecret"];
            });
}

public void Configure(IApplicationBuilder app, ILoggerFactory loggerfactory) {
    app.UseAuthentication();
}

UseAuthentication 方法将添加一个身份验证中间件组件,该组件负责自动执行身份验证和处理远程身份验证请求。 它将所有单独的中间件组件替换成一个通用中间件组件。

下面是每个主要身份验证方案的 2.0 迁移说明。

选择以下两个选项之一,并在 Startup.cs 中进行必要的更改:

  1. 使用带 Identity 的 Cookie

    • Configure 方法中将 UseIdentity 替换为 UseAuthentication

      app.UseAuthentication();
      
    • ConfigureServices 方法中调用 AddIdentity 方法以添加 cookie 身份验证服务。

    • (可选)在 ConfigureServices 方法中调用 ConfigureApplicationCookieConfigureExternalCookie 方法以调整 Identitycookie 设置。

      services.AddIdentity<ApplicationUser, IdentityRole>()
              .AddEntityFrameworkStores<ApplicationDbContext>()
              .AddDefaultTokenProviders();
      
      services.ConfigureApplicationCookie(options => options.LoginPath = "/Account/LogIn");
      
  2. 使用不带 Identity 的 Cookie

    • Configure 方法中的 UseCookieAuthentication 方法调用替换为 UseAuthentication

      app.UseAuthentication();
      
    • ConfigureServices 方法中调用 AddAuthenticationAddCookie 方法:

      // If you don't want the cookie to be automatically authenticated and assigned to HttpContext.User,
      // remove the CookieAuthenticationDefaults.AuthenticationScheme parameter passed to AddAuthentication.
      services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
              .AddCookie(options =>
              {
                  options.LoginPath = "/Account/LogIn";
                  options.LogoutPath = "/Account/LogOff";
              });
      

JWT 持有者身份验证

Startup.cs 中,进行下列更改:

  • Configure 方法中的 UseJwtBearerAuthentication 方法调用替换为 UseAuthentication

    app.UseAuthentication();
    
  • ConfigureServices 方法中调用 AddJwtBearer 方法:

    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(options =>
            {
                options.Audience = "http://localhost:5001/";
                options.Authority = "http://localhost:5000/";
            });
    

    此代码片段不使用 Identity,因此应通过将 JwtBearerDefaults.AuthenticationScheme 传递给 AddAuthentication 方法来设置默认方案。

OpenID Connect (OIDC) 身份验证

Startup.cs 中,进行下列更改:

  • Configure 方法中的 UseOpenIdConnectAuthentication 方法调用替换为 UseAuthentication

    app.UseAuthentication();
    
  • ConfigureServices 方法中调用 AddOpenIdConnect 方法:

    services.AddAuthentication(options =>
    {
        options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    })
    .AddCookie()
    .AddOpenIdConnect(options =>
    {
        options.Authority = Configuration["auth:oidc:authority"];
        options.ClientId = Configuration["auth:oidc:clientid"];
    });
    
  • OpenIdConnectOptions 操作中的 PostLogoutRedirectUri 属性替换为 SignedOutRedirectUri

    .AddOpenIdConnect(options =>
    {
        options.SignedOutRedirectUri = "https://contoso.com";
    });
    

Facebook 身份验证

Startup.cs 中,进行下列更改:

  • Configure 方法中的 UseFacebookAuthentication 方法调用替换为 UseAuthentication

    app.UseAuthentication();
    
  • ConfigureServices 方法中调用 AddFacebook 方法:

    services.AddAuthentication()
            .AddFacebook(options =>
            {
                options.AppId = Configuration["auth:facebook:appid"];
                options.AppSecret = Configuration["auth:facebook:appsecret"];
            });
    

Google 身份验证

Startup.cs 中,进行下列更改:

  • Configure 方法中的 UseGoogleAuthentication 方法调用替换为 UseAuthentication

    app.UseAuthentication();
    
  • ConfigureServices 方法中调用 AddGoogle 方法:

    services.AddAuthentication()
            .AddGoogle(options =>
            {
                options.ClientId = Configuration["auth:google:clientid"];
                options.ClientSecret = Configuration["auth:google:clientsecret"];
            });
    

Microsoft 帐户身份验证

有关 Microsoft 帐户身份验证的详细信息,请参阅此 GitHub 问题

Startup.cs 中,进行下列更改:

  • Configure 方法中的 UseMicrosoftAccountAuthentication 方法调用替换为 UseAuthentication

    app.UseAuthentication();
    
  • ConfigureServices 方法中调用 AddMicrosoftAccount 方法:

    services.AddAuthentication()
            .AddMicrosoftAccount(options =>
            {
                options.ClientId = Configuration["auth:microsoft:clientid"];
                options.ClientSecret = Configuration["auth:microsoft:clientsecret"];
            });
    

Twitter 身份验证

Startup.cs 中,进行下列更改:

  • Configure 方法中的 UseTwitterAuthentication 方法调用替换为 UseAuthentication

    app.UseAuthentication();
    
  • ConfigureServices 方法中调用 AddTwitter 方法:

    services.AddAuthentication()
            .AddTwitter(options =>
            {
                options.ConsumerKey = Configuration["auth:twitter:consumerkey"];
                options.ConsumerSecret = Configuration["auth:twitter:consumersecret"];
            });
    

设置默认身份验证方案

在 1.x 中,AuthenticationOptions 基类中的 AutomaticAuthenticateAutomaticChallenge 属性应在一个身份验证方案中进行设置。 没有有效的方法来强制执行此操作。

在 2.0 中,这两个属性已作为单独 AuthenticationOptions 实例的属性被删除。 可以在 Startup.csConfigureServices 方法中的 AddAuthentication 方法调用中配置这两个属性:

services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme);

在上面的代码片段中,默认方案设置为 CookieAuthenticationDefaults.AuthenticationScheme(“Cookie”)。

或者,使用 AddAuthentication 方法的重载版本来设置多个属性。 在下面的重载方法示例中,默认方案设置为 CookieAuthenticationDefaults.AuthenticationScheme。 也可以在单独的 [Authorize] 属性或授权策略中指定身份验证方案。

services.AddAuthentication(options =>
{
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
});

如果满足以下条件之一,请在 2.0 中定义默认方案:

  • 希望用户自动登录
  • 在不指定方案的情况下使用 [Authorize] 属性或授权策略

AddIdentity 方法是此项规则的例外情况。 此方法将为你添加 Cookie,并将默认身份验证和质询方案设置为应用程序 cookieIdentityConstants.ApplicationScheme。 此外,它还将默认登录方案设置为外部 cookieIdentityConstants.ExternalScheme

使用 HttpContext 身份验证扩展

IAuthenticationManager 接口是进入 1.x 身份验证系统的主要入口点。 该接口已替换为 Microsoft.AspNetCore.Authentication 命名空间中的一组新 HttpContext 扩展方法。

例如,1.x 项目引用 Authentication 属性:

// Clear the existing external cookie to ensure a clean login process
await HttpContext.Authentication.SignOutAsync(_externalCookieScheme);

在 2.0 项目中,导入 Microsoft.AspNetCore.Authentication 命名空间,并删除 Authentication 属性引用:

// Clear the existing external cookie to ensure a clean login process
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);

Windows 身份验证 (HTTP.sys / IISIntegration)

Windows 身份验证有两种变体:

  • 主机仅允许经过身份验证的用户。 此变体不受 2.0 更改的影响。

  • 主机允许匿名用户和经过身份验证的用户。 此变体受 2.0 更改的影响。 例如,应用应该在 IISHTTP.sys 层允许匿名用户,但在控制器级别对用户进行授权。 在此方案中,在 Startup.ConfigureServices 方法中设置默认方案。

    对于 Microsoft.AspNetCore.Server.IISIntegration,将默认方案设置为 IISDefaults.AuthenticationScheme

    using Microsoft.AspNetCore.Server.IISIntegration;
    
    services.AddAuthentication(IISDefaults.AuthenticationScheme);
    

    对于 Microsoft.AspNetCore.Server.HttpSys,将默认方案设置为 HttpSysDefaults.AuthenticationScheme

    using Microsoft.AspNetCore.Server.HttpSys;
    
    services.AddAuthentication(HttpSysDefaults.AuthenticationScheme);
    

    未设置默认方案会导致授权(质询)请求无法正常工作并出现以下意外情况:

    System.InvalidOperationException:未指定任何 authenticationScheme,并且未找到 DefaultChallengeScheme。

有关详细信息,请参阅在 ASP.NET Core 中配置 Windows 身份验证

IdentityCookieOptions 实例

2.0 更改的副作用是切换为使用命名选项而不是 cookie 选项实例。 自定义 Identitycookie 方案名称的功能将被删除。

例如,1.x 项目使用构造函数注入IdentityCookieOptions 参数传递到 AccountController.csManageController.cs 中。 可从提供的实例中访问外部 cookie 身份验证方案:

public AccountController(
    UserManager<ApplicationUser> userManager,
    SignInManager<ApplicationUser> signInManager,
    IOptions<IdentityCookieOptions> identityCookieOptions,
    IEmailSender emailSender,
    ISmsSender smsSender,
    ILoggerFactory loggerFactory)
{
    _userManager = userManager;
    _signInManager = signInManager;
    _externalCookieScheme = identityCookieOptions.Value.ExternalCookieAuthenticationScheme;
    _emailSender = emailSender;
    _smsSender = smsSender;
    _logger = loggerFactory.CreateLogger<AccountController>();
}

2.0 项目中不再需要上述构造函数注入,并且可以删除 _externalCookieScheme 字段:

public AccountController(
    UserManager<ApplicationUser> userManager,
    SignInManager<ApplicationUser> signInManager,
    IEmailSender emailSender,
    ISmsSender smsSender,
    ILoggerFactory loggerFactory)
{
    _userManager = userManager;
    _signInManager = signInManager;
    _emailSender = emailSender;
    _smsSender = smsSender;
    _logger = loggerFactory.CreateLogger<AccountController>();
}

1.x 项目使用 _externalCookieScheme 字段,如下所示:

// Clear the existing external cookie to ensure a clean login process
await HttpContext.Authentication.SignOutAsync(_externalCookieScheme);

在 2.0 项目中,将上面的代码替换为以下代码。 可以直接使用 IdentityConstants.ExternalScheme 常量。

// Clear the existing external cookie to ensure a clean login process
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);

通过导入以下命名空间来解析新添加的 SignOutAsync 调用:

using Microsoft.AspNetCore.Authentication;

添加 IdentityUser POCO 导航属性

已删除基本 IdentityUser POCO(普通旧 CLR 对象)的实体框架 (EF) Core 导航属性。 如果 1.x 项目使用了这些属性,请手动将它们添加回 2.0 项目:

/// <summary>
/// Navigation property for the roles this user belongs to.
/// </summary>
public virtual ICollection<IdentityUserRole<int>> Roles { get; } = new List<IdentityUserRole<int>>();

/// <summary>
/// Navigation property for the claims this user possesses.
/// </summary>
public virtual ICollection<IdentityUserClaim<int>> Claims { get; } = new List<IdentityUserClaim<int>>();

/// <summary>
/// Navigation property for this users login accounts.
/// </summary>
public virtual ICollection<IdentityUserLogin<int>> Logins { get; } = new List<IdentityUserLogin<int>>();

若要防止在运行 EF Core 迁移时出现重复的外键,请将以下内容添加到 IdentityDbContext 类的 OnModelCreating 方法(在 base.OnModelCreating(); 调用后):

protected override void OnModelCreating(ModelBuilder builder)
{
    base.OnModelCreating(builder);
    // Customize the ASP.NET Core Identity model and override the defaults if needed.
    // For example, you can rename the ASP.NET Core Identity table names and more.
    // Add your customizations after calling base.OnModelCreating(builder);

    builder.Entity<ApplicationUser>()
        .HasMany(e => e.Claims)
        .WithOne()
        .HasForeignKey(e => e.UserId)
        .IsRequired()
        .OnDelete(DeleteBehavior.Cascade);

    builder.Entity<ApplicationUser>()
        .HasMany(e => e.Logins)
        .WithOne()
        .HasForeignKey(e => e.UserId)
        .IsRequired()
        .OnDelete(DeleteBehavior.Cascade);

    builder.Entity<ApplicationUser>()
        .HasMany(e => e.Roles)
        .WithOne()
        .HasForeignKey(e => e.UserId)
        .IsRequired()
        .OnDelete(DeleteBehavior.Cascade);
}

替换 GetExternalAuthenticationSchemes

为了支持异步版本,已删除同步方法 GetExternalAuthenticationSchemes。 1.x 项目在 Controllers/ManageController.cs 中具有以下代码:

var otherLogins = _signInManager.GetExternalAuthenticationSchemes().Where(auth => userLogins.All(ul => auth.AuthenticationScheme != ul.LoginProvider)).ToList();

此方法也出现在 Views/Account/Login.cshtml 中:

@{
    var loginProviders = SignInManager.GetExternalAuthenticationSchemes().ToList();
    if (loginProviders.Count == 0)
    {
        <div>
            <p>
                There are no external authentication services configured. See <a href="https://go.microsoft.com/fwlink/?LinkID=532715">this article</a>
                for details on setting up this ASP.NET application to support logging in via external services.
            </p>
        </div>
    }
    else
    {
        <form asp-controller="Account" asp-action="ExternalLogin" asp-route-returnurl="@ViewData["ReturnUrl"]" method="post" class="form-horizontal">
            <div>
                <p>
                    @foreach (var provider in loginProviders)
                    {
                        <button type="submit" class="btn btn-default" name="provider" value="@provider.AuthenticationScheme" title="Log in using your @provider.DisplayName account">@provider.AuthenticationScheme</button>
                    }
                </p>
            </div>
        </form>
    }
}

在 2.0 项目中,请使用 GetExternalAuthenticationSchemesAsync 方法。 ManageController.cs 中的更改类似于以下代码:

var schemes = await _signInManager.GetExternalAuthenticationSchemesAsync();
var otherLogins = schemes.Where(auth => userLogins.All(ul => auth.Name != ul.LoginProvider)).ToList();

Login.cshtml 中,在 foreach 循环中访问的 AuthenticationScheme 属性将更改为 Name

@{
    var loginProviders = (await SignInManager.GetExternalAuthenticationSchemesAsync()).ToList();
    if (loginProviders.Count == 0)
    {
        <div>
            <p>
                There are no external authentication services configured. See <a href="https://go.microsoft.com/fwlink/?LinkID=532715">this article</a>
                for details on setting up this ASP.NET application to support logging in via external services.
            </p>
        </div>
    }
    else
    {
        <form asp-controller="Account" asp-action="ExternalLogin" asp-route-returnurl="@ViewData["ReturnUrl"]" method="post" class="form-horizontal">
            <div>
                <p>
                    @foreach (var provider in loginProviders)
                    {
                        <button type="submit" class="btn btn-default" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>
                    }
                </p>
            </div>
        </form>
    }
}

ManageLoginsViewModel 属性更改

ManageLoginsViewModel 对象用于 ManageController.csManageLogins 操作中。 在 1.x 项目中,该对象的 OtherLogins 属性返回类型为 IList<AuthenticationDescription>。 此返回类型需要导入 Microsoft.AspNetCore.Http.Authentication

using System.Collections.Generic;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.AspNetCore.Identity;

namespace AspNetCoreDotNetCore1App.Models.ManageViewModels
{
    public class ManageLoginsViewModel
    {
        public IList<UserLoginInfo> CurrentLogins { get; set; }

        public IList<AuthenticationDescription> OtherLogins { get; set; }
    }
}

在 2.0 项目中,返回类型将更改为 IList<AuthenticationScheme>。 这一新的返回类型需要将 Microsoft.AspNetCore.Http.Authentication 导入替换为 Microsoft.AspNetCore.Authentication 导入。

using System.Collections.Generic;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;

namespace AspNetCoreDotNetCore2App.Models.ManageViewModels
{
    public class ManageLoginsViewModel
    {
        public IList<UserLoginInfo> CurrentLogins { get; set; }

        public IList<AuthenticationScheme> OtherLogins { get; set; }
    }
}

其他资源

有关详细信息,请参阅 GitHub 上的 Auth 2.0 讨论议题。