ASP.NET Core 6 身分驗證 自訂登入頁面問題解決

註:在此專案中有使用自訂模型,自訂 User 繼承 IdentityUser 和自訂的 Role 繼承 IdentityRole ,在本文中的 User 和 Role 類別請自行替換為自訂的使用者和角色類別

筆者依照 從頭開始使用 ASP.NET Core 自訂身分驗證功能 這篇 新增 scaffold > 新增識別選擇 Account\Login 後出現錯誤,是因為有使用自訂身分驗證類別,新增 scaffold 識別時有部分程式碼需要自行調整,故留下此篇文章做紀錄,希望能夠幫助到有緣人。
剛新增完後執行的錯誤訊息如下:
    
[18:08:26 FTL] Application startup exception
System.InvalidOperationException: Scheme already exists: Identity.Application
   at Microsoft.AspNetCore.Authentication.AuthenticationOptions.AddScheme(String name, Action`1 configureBuilder)
   at Microsoft.AspNetCore.Authentication.AuthenticationBuilder.<>c__DisplayClass4_0`2.<AddSchemeHelper>b__0(AuthenticationOptions o)
   at Microsoft.Extensions.Options.ConfigureNamedOptions`1.Configure(String name, TOptions options)
   at Microsoft.Extensions.Options.OptionsFactory`1.Create(String name)
   at Microsoft.Extensions.Options.UnnamedOptionsManager`1.get_Value()
   
   ...
   
      at Program.<Main>$(String[] args) in C:\Users\ruyut\Documents\RiderProjects\2022\ruyut_test\ruyut_test\Program.cs:line 102
   at Program.<Main>(String[] args)
    

雖然有提到第 102 行,不過第 102 行就只是 run 而已,不是重點
    
    app.Run();
    

還好有 git,一看就知道新增頁面時在 Program.cs 自動增加了下面這兩行
    
builder.Services.AddDefaultIdentity<User>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<RuyutDbContext>();
    

那就使用自動新增的,可以把之前寫的驗證相關的程式碼註解掉了,應該就可以解決這個問題
    
// builder.Services.AddIdentity<User, Role>()
//     .AddEntityFrameworkStores<RuyutDbContext>()
//     .AddDefaultUI()
//     .AddDefaultTokenProviders();
    

註: 新的 AddDefaultIdentity 就包含了 AddDefaultUI() 和 AddDefaultTokenProviders() ,所以不需要補上這兩行。 詳情可以查看 Microsoft.Learn - Custom user data

又出現了其他的問題
    
Unhandled exception. System.AggregateException: Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: ruyut_test.Service.RolesPermissionService Lifetime: Scoped ImplementationType: ruyut_test.Service.RolesPermissionService': Unable to resolve service for type 'Microsoft.AspNetCore.Identity.RoleManager`1[ruyut_test.Models.Role]' while attempting to activate 'ruyut_test.Service.RolesPermissionService'.)
 ---> System.InvalidOperationException: Error while validating the service descriptor 'ServiceType: ruyut_test.Service.RolesPermissionService Lifetime: Scoped ImplementationType: ruyut_test.Service.RolesPermissionService': Unable to resolve service for type 'Microsoft.AspNetCore.Identity.RoleManager`1[ruyut_test.Models.Role]' while attempting to activate 'ruyut_test.Service.RolesPermissionService'.
 ---> System.InvalidOperationException: Unable to resolve service for type 'Microsoft.AspNetCore.Identity.RoleManager`1[ruyut_test.Models.Role]' while attempting to activate 'ruyut_test.Service.RolesPermissionService'.
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(ResultCache lifetime, Type serviceType, Type implementationType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, Int32 slot)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.GetCallSite(ServiceDescriptor serviceDescriptor, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.ValidateService(ServiceDescriptor descriptor)
   --- End of inner exception stack trace ---
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.ValidateService(ServiceDescriptor descriptor)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider..ctor(ICollection`1 serviceDescriptors, ServiceProviderOptions options)
   --- End of inner exception stack trace ---
   at Microsoft.Extensions.DependencyInjection.ServiceProvider..ctor(ICollection`1 serviceDescriptors, ServiceProviderOptions options)
   at Microsoft.Extensions.DependencyInjection.ServiceCollectionContainerBuilderExtensions.BuildServiceProvider(IServiceCollection services, ServiceProviderOptions options)
   at Microsoft.Extensions.DependencyInjection.DefaultServiceProviderFactory.CreateServiceProvider(IServiceCollection containerBuilder)
   at Microsoft.Extensions.Hosting.Internal.ServiceFactoryAdapter`1.CreateServiceProvider(Object containerBuilder)
   at Microsoft.Extensions.Hosting.HostBuilder.CreateServiceProvider()
   at Microsoft.Extensions.Hosting.HostBuilder.Build()
   at Microsoft.AspNetCore.Builder.WebApplicationBuilder.Build()
   at Program.<Main>$(String[] args) in C:\Users\ruyut\Documents\RiderProjects\2022\ruyut_test\ruyut_test\Program.cs:line 59
   at Program.<Main>(String[] args)
    

看來是自訂角色類別(繼承 IdentityRole 的類別)沒有放上去,補充一下
    
builder.Services.AddDefaultIdentity<User>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddRoles<Role>()
    .AddEntityFrameworkStores<RuyutDbContext>();
    

執行時沒有出現錯誤,但開啟網頁時沒有畫面,發現上方網址有點問題,使用工具查看 http 狀態代號顯示 401。開啟網頁後自動跳轉到的網址如下:
    
https://localhost:7183/Identity/Account/Login?ReturnUrl=%2FIdentity%2FAccount%2FLogin%3FReturnUrl%3D%252FIdentity%252FAccount%252FLogin%253FReturnUrl%253D%25252FIdentity%25252FAccount%25252FLogin%25253FReturnUrl%25253D%2525252FIdentity%2525252FAccount%2525252FLogin%2525253FReturnUrl%2525253D%252525252FIdentity%252525252FAccount%252525252FLogin%252525253FReturnUrl%252525253D%25252525252FIdentity%25252525252FAccount%25252525252FLogin%25252525253FReturnUrl%25252525253D%2525252525252FIdentity%2525252525252FAccount%2525252525252FLogin%2525252525253FReturnUrl%2525252525253D%252525252525252FIdentity%252525252525252FAccount%252525252525252FLogin%252525252525253FReturnUrl%252525252525253D%25252525252525252FIdentity%25252525252525252FAccount%25252525252525252FLogin%25252525252525253FReturnUrl%25252525252525253D%2525252525252525252FIdentity%2525252525252525252FAccount%2525252525252525252FLogin%2525252525252525253FReturnUrl%2525252525252525253D%252525252525252525252FIdentity%252525252525252525252FAccount%252525252525252525252FLogin%252525252525252525253FReturnUrl%252525252525252525253D%25252525252525252525252FIdentity%25252525252525252525252FAccount%25252525252525252525252FLogin%25252525252525252525253FReturnUrl%25252525252525252525253D%2525252525252525252525252FIdentity%2525252525252525252525252FAccount%2525252525252525252525252FLogin%2525252525252525252525253FReturnUrl%2525252525252525252525253D%252525252525252525252525252FIdentity%252525252525252525252525252FAccount%252525252525252525252525252FLogin%252525252525252525252525253FReturnUrl%252525252525252525252525253D%25252525252525252525252525252FIdentity%25252525252525252525252525252FAccount%25252525252525252525252525252FLogin%25252525252525252525252525253FReturnUrl%25252525252525252525252525253D%2525252525252525252525252525252FIdentity%2525252525252525252525252525252FAccount%2525252525252525252525252525252FLogin%2525252525252525252525252525253FReturnUrl%2525252525252525252525252525253D%252525252525252525252525252525252FIdentity%252525252525252525252525252525252FAccount%252525252525252525252525252525252FLogin%252525252525252525252525252525253FReturnUrl%252525252525252525252525252525253D%25252525252525252525252525252525252FIdentity%25252525252525252525252525252525252FAccount%25252525252525252525252525252525252FLogin%25252525252525252525252525252525253FReturnUrl%25252525252525252525252525252525253D%2525252525252525252525252525252525252FIdentity%2525252525252525252525252525252525252FAccount%2525252525252525252525252525252525252FLogin%2525252525252525252525252525252525253FReturnUrl%2525252525252525252525252525252525253D%252525252525252525252525252525252525252FIdentity%252525252525252525252525252525252525252FAccount%252525252525252525252525252525252525252FLogin%252525252525252525252525252525252525253FReturnUrl%252525252525252525252525252525252525253D%25252525252525252525252525252525252525252F
    

看起來像是要進入登入頁面時因為沒有登入所以沒通過驗證然後又嘗試跳轉到登入頁面,所以網址就變很長 這個簡單,到剛剛加入的自訂登入頁面(Login.cshtml)中加上讓他能夠不需要驗證即可存取的屬性(Attribute)
    
@using Microsoft.AspNetCore.Authorization

@attribute [AllowAnonymous]
    

此時再登入就發現已經正常了,並且修改 Login.cshtml 也會出現在登入頁面上,成功達成自訂登入頁面的目的!

註:若有自訂忘記密碼(ForgotPasswordModel.cshtml)、忘記密碼確認(ForgotPasswordConfirmation.cshtlm)等頁面,在沒有登入時會進不去而無法重置密碼,此時也需要在不需要驗證即可存取的頁面上面都加上 [AllowAnonymous]

留言