将身份验证和 Identity 迁移到 ASP.NET Core 2.0
作者:Scott Addie 和 Hao Kung
ASP.NET Core 2.0 具有用于身份验证和 Identity 的新模型,可使用服务简化配置。 可将使用身份验证或 Identity 的 ASP.NET Core 1.x 应用程序更新为使用新模型,如下所述。
更新命名空间
在 1.x 中,类(例如IdentityRole
和 IdentityUser
)位于 Microsoft.AspNetCore.Identity.EntityFrameworkCore
命名空间中。
在 2.0 中,Microsoft.AspNetCore.Identity 命名空间成为几个这样的类的新 home。 对于默认 Identity 代码,受影响的类包括 ApplicationUser
和 Startup
。 调整 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.cs
的 ConfigureServices
方法中注册。 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 迁移说明。
基于 Cookie 的身份验证
选择以下两个选项之一,并在 Startup.cs
中进行必要的更改:
使用带 Identity 的 Cookie
在
Configure
方法中将UseIdentity
替换为UseAuthentication
:app.UseAuthentication();
在
ConfigureServices
方法中调用AddIdentity
方法以添加 cookie 身份验证服务。(可选)在
ConfigureServices
方法中调用ConfigureApplicationCookie
或ConfigureExternalCookie
方法以调整 Identitycookie 设置。services.AddIdentity<ApplicationUser, IdentityRole>() .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders(); services.ConfigureApplicationCookie(options => options.LoginPath = "/Account/LogIn");
使用不带 Identity 的 Cookie
将
Configure
方法中的UseCookieAuthentication
方法调用替换为UseAuthentication
:app.UseAuthentication();
在
ConfigureServices
方法中调用AddAuthentication
和AddCookie
方法:// 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 基类中的 AutomaticAuthenticate
和 AutomaticChallenge
属性应在一个身份验证方案中进行设置。 没有有效的方法来强制执行此操作。
在 2.0 中,这两个属性已作为单独 AuthenticationOptions
实例的属性被删除。 可以在 Startup.cs
的 ConfigureServices
方法中的 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 更改的影响。 例如,应用应该在 IIS 或 HTTP.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.cs
和 ManageController.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.cs
的 ManageLogins
操作中。 在 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 讨论议题。