將驗證和 Identity 移轉至 ASP.NET Core 2.0
作者:Scott Addie 和 Hao Kung
ASP.NET Core 2.0 有新的驗證和 Identity 模型,可透過使用服務簡化設定。 使用驗證或 Identity 的 ASP.NET Core 1.x 應用程式可以更新為使用新的模型,如下所述。
更新命名空間
在 1.x 中,可以在 Microsoft.AspNetCore.Identity.EntityFrameworkCore
命名空間中找到 IdentityRole
和 IdentityUser
這類的類別。
在 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
方法中的UseAuthentication
取代UseIdentity
: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 (Plain Old CLR Object) 的 Entity Framework (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.Authentication
匯入取代 Microsoft.AspNetCore.Http.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 問題討論。