使用身份验证托管的 Blazor Wasm 上出现 Azure 500 错误

Posted

技术标签:

【中文标题】使用身份验证托管的 Blazor Wasm 上出现 Azure 500 错误【英文标题】:Azure 500 error on a Blazor Wasm Hosted with Authentication 【发布时间】:2021-04-11 11:49:17 【问题描述】:

我在这里完全不知所措。我有一个运行 .net5 的 Blazor WASM 托管,它已部署到 Azure 应用服务。当没有数据库时,部署到 Azure 没有问题。它按预期加载并运行。我已经安装了 Identity 和 DBContext。一切都在本地构建并使用本地 SQL 实例正常运行。

在 Azure 上,我创建了一个新的 SQLServer 以及一个 SQL 数据库。在 SQL 数据库防火墙设置中,我有“允许 Azure 服务和资源访问此服务器”以及我的客户端 IP(不是 Azure 应用程序的 IP)的规则。

对于配置中的应用服务,我有一个名为 DefaultConnection 的 ConnectionString(与 appsettings.json 中的相同),其连接字符串与 SQLDatabase 提供的连接字符串相同,源 AppConfig,类型 SqlAzure

我正在使用 VS2019 Publish on the Server 项目(启动项目)发布到 Azure。我选择目标是 Azure -> Azure App Services (Windows) 和我的实例名称。配置为 Release,Target Framework net5.0,DeploymentMode Framework-dependent,Target runtime Portable。

Service Dependencies 设置为 AzureSqlDatabase,它使用 ConnectionName DefaultConnection,Username 和 Password 是在 Azure 上创建的 SQL Server 的 Admin UserPassword 设置,SaveConnectionStringValue 是 Azure App Settings。 (这会自动填充上述应用服务配置连接字符串。

当我点击发布时,我在输出中看到所有发布都正确:

Publish Succeeded.
Web App was published successfully http://bbqfriend.azurewebsites.net/
========== Build: 1 succeeded, 0 failed, 6 up-to-date, 0 skipped ==========
========== Publish: 1 succeeded, 0 failed, 0 skipped ==========
Installation of Web App Site extension Microsoft.AspNetCore.AzureAppServices.SiteExtension is in progress...
Restarting the Web App...
Successfully installed Web App extension Microsoft.AspNetCore.AzureAppServices.SiteExtension
Successfully restarted Web App.

但是当页面启动时,它会显示 500 错误。

如果我返回发布并编辑设置 - 数据库 - DefaultConnection 并检查在运行时使用此连接字符串,选择在 ServiceDependencies 中配置的连接字符串以及 EntityFrameworkMigrations DataContext 在发布时应用此迁移。当我发布该配置文件时,它将执行迁移以及我在 DataContext OnModelCreating 覆盖中定义的种子

protected override void OnModelCreating(ModelBuilder modelBuilder)
        
            base.OnModelCreating(modelBuilder);

            #region Identity Seed
            modelBuilder.ApplyConfiguration(new ApplicationUserConfiguration());
            modelBuilder.ApplyConfiguration(new IdentityRoleConfiguration());
            modelBuilder.ApplyConfiguration(new IdentityUserRoleConfiguration());
            #endregion

            //modelBuilder.ApplyConfiguration(new CountryConfiguration());

            
        

所以我知道连接字符串是正确的,并且有一个具有正确模型和种子数据的数据库。为什么我得到 500?!?

这是我在服务器项目中的 appsettings.json


    "ConnectionStrings": 
        "DefaultConnection": "Server=.;Database=DatabaseName;Trusted_Connection=True;MultipleActiveResultSets=true"
    ,
    "IdentityServer": 
        "Clients": 
            "XXXX.Client": 
                "Profile": "IdentityServerSPA"
            
        
    ,
    "Serilog": 
        "Using": [ "Serilog.Sinks.MSSqlServer" ],
        "MinimumLevel": 
            "Default": "Information",
            "Override": 
                "Microsoft": "Warning",
                "Microsoft.AspNetCore": "Warning",
                "Microsoft.AspNetCore.Authorization.DefaultAuthorizationService": "Warning",
                "Microsoft.EntityFrameworkCore": "Warning",
                "System": "Warning",
                "System.Net.Http.HttpClient*": "Warning",
                "IdentityServer4": "Warning",
                "Serilog.AspNetCore": "Warning"
            
        ,
        "WriteTo": [
            
                "Name": "MSSqlServer",
                "Args": 
                    "connectionString": "DefaultConnection",
                    "sinkOptionsSection": 
                        "tableName": "Logs"
                    ,
                    "columnOptionsSection": 
                        "additionalColumns": [
                            
                                "ColumnName": "InstanceId"
                            ,
                            
                                "ColumnName": "Origin"
                            ,
                            
                                "ColumnName": "SourceContext"
                            ,
                            
                                "ColumnName": "UserId"
                            ,
                            
                                "ColumnName": "Username"
                            
                        ],
                        "excludeAdditionalProperties": true
                    
                
            
        ]
    ,
    "AllowedHosts": "*"

这里是服务器项目的 Startup.cs

public class Startup
    
        public Startup(IConfiguration configuration)
        
            Configuration = configuration;
        

        public IConfiguration Configuration  get; 

        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        
            //Register the Datacontext and Connection String
            services.AddDbContext<DataContext>(options =>
                options.UseSqlServer(
                    Configuration.GetConnectionString("DefaultConnection")));

            services.AddDatabaseDeveloperPageExceptionFilter();

            //Sets up the default Asp.net core Identity Screens - Use Identity Scaffolding to override defaults
            services.AddDefaultIdentity<ApplicationUser>( options =>
                    
                        options.SignIn.RequireConfirmedAccount = true;
                        options.Password.RequireDigit = true;
                        options.Password.RequireLowercase = true;
                        options.Password.RequireUppercase = true;
                        options.Password.RequiredUniqueChars = 0;
                        options.Password.RequireNonAlphanumeric = false;
                        options.Password.RequiredLength = 8;
                        options.User.RequireUniqueEmail = true;
                    )
                .AddRoles<IdentityRole>()
                .AddEntityFrameworkStores<DataContext>();

            //Associates the User to Context with Identity
            services.AddIdentityServer()
                .AddApiAuthorization<ApplicationUser, DataContext>( options =>
            
                options.IdentityResources["openid"].UserClaims.Add(JwtClaimTypes.Role);
                options.ApiResources.Single().UserClaims.Add(JwtClaimTypes.Role);
            );
            JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove(JwtClaimTypes.Role);

            //Adds authentication handler
            services.AddAuthentication().AddIdentityServerJwt();

            //Register Repositories for Dependency Injection
            services.AddScoped<ICountryRepository, CountryRepository>();

            services.AddControllersWithViews();
            services.AddRazorPages();
        

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, DataContext dataContext)
        
            if (env.IsDevelopment())
            
                app.UseDeveloperExceptionPage();
                app.UseMigrationsEndPoint();
                app.UseWebAssemblyDebugging();
            
            else
            
                app.UseExceptionHandler("/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            

            //AutoMigrates data
            //dataContext.Database.Migrate();

            app.UseHttpsRedirection();
            app.UseBlazorFrameworkFiles();
            app.UseStaticFiles();

            app.UseSerilogIngestion();
            app.UseSerilogRequestLogging();

            app.UseRouting();

            app.UseIdentityServer();
            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            
                endpoints.MapRazorPages();
                endpoints.MapControllers();
                endpoints.MapFallbackToFile("index.html");
            );
        
    

这是服务器项目的 Program.cs

public class Program
    
        public static void Main(string[] args)
        
            var configuration = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json")
                .Build();

            Log.Logger = new LoggerConfiguration()
                .ReadFrom.Configuration(configuration)
                .Enrich.WithProperty("InstanceId", Guid.NewGuid())
                .Enrich.WithProperty("Origin", "Server")
                .CreateLogger();

            try
            
                Log.Information("Starting up");
                CreateHostBuilder(args).Build().Run();
            
            catch (Exception ex)
            
                Log.Fatal(ex, "Application start-up failed");
            
            finally
            
                Log.CloseAndFlush();
            

        

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .UseSerilog()
                .ConfigureWebHostDefaults(webBuilder =>
                
                    webBuilder.UseStartup<Startup>();
                );
    

这是客户项目的 Program.cs

public static async Task Main(string[] args)
        
            //Serilog 
            var levelSwitch = new LoggingLevelSwitch();
            Log.Logger = new LoggerConfiguration()
                .MinimumLevel.ControlledBy(levelSwitch)
                .Enrich.WithProperty("InstanceId", Guid.NewGuid())
                .Enrich.FromLogContext()
                .WriteTo.BrowserHttp(controlLevelSwitch: levelSwitch)
                .CreateLogger();            

            Log.ForContext<Program>().Information("Client has started");

            var builder = WebAssemblyHostBuilder.CreateDefault(args);
            builder.RootComponents.Add<App>("#app");

            builder.Services.AddLogging(logging =>
           
               logging.ClearProviders();
               logging.AddSerilog(dispose: true);
           );

            builder.Services.AddHttpClient("XXX.ServerAPI", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
                .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

            // Supply HttpClient instances that include access tokens when making requests to the server project
            builder.Services.AddTransient(sp => 
                sp.GetRequiredService<IHttpClientFactory>()
                .CreateClient("XXXX.ServerAPI"));

            builder.Services.AddApiAuthorization()
                .AddAccountClaimsPrincipalFactory<RolesClaimsPrincipalFactory>();

            //Register Services
            var baseAddress = new Uri($"builder.HostEnvironment.BaseAddressapi/");
            void RegisterTypedClient<TClient, TImplementation>(Uri apiBaseUrl)
                where TClient : class where TImplementation : class, TClient
            
                builder.Services.AddHttpClient<TClient, TImplementation>(client => client.BaseAddress = apiBaseUrl)
                    .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
            

            RegisterTypedClient<ICountryService, CountryService>(baseAddress);


            await builder.Build().RunAsync();
        

我确实配置了 Serilog,它看起来也可以正常工作。这是我在服务器启动期间看到的错误消息

System.InvalidOperationException: Startup assembly Microsoft.ApplicationInsights.StartupBootstrapper failed to execute. See the inner exception for more details.
 ---> System.IO.FileNotFoundException: Could not load file or assembly 'Microsoft.ApplicationInsights.StartupBootstrapper, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.
File name: 'Microsoft.ApplicationInsights.StartupBootstrapper, Culture=neutral, PublicKeyToken=null'
   at System.Reflection.RuntimeAssembly.InternalLoad(ObjectHandleOnStack assemblyName, ObjectHandleOnStack requestingAssembly, StackCrawlMarkHandle stackMark, Boolean throwOnFileNotFound, ObjectHandleOnStack assemblyLoadContext, ObjectHandleOnStack retAssembly)
   at System.Reflection.RuntimeAssembly.InternalLoad(AssemblyName assemblyName, RuntimeAssembly requestingAssembly, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, AssemblyLoadContext assemblyLoadContext)
   at System.Reflection.Assembly.Load(AssemblyName assemblyRef)
   at Microsoft.AspNetCore.Hosting.GenericWebHostBuilder.ExecuteHostingStartups()
   --- End of inner exception stack trace ---

 System.InvalidOperationException: Startup assembly DiagnosticServices.HostingStartup failed to execute. See the inner exception for more details.
 ---> System.IO.FileNotFoundException: Could not load file or assembly 'DiagnosticServices.HostingStartup, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.
File name: 'DiagnosticServices.HostingStartup, Culture=neutral, PublicKeyToken=null'
   at System.Reflection.RuntimeAssembly.InternalLoad(ObjectHandleOnStack assemblyName, ObjectHandleOnStack requestingAssembly, StackCrawlMarkHandle stackMark, Boolean throwOnFileNotFound, ObjectHandleOnStack assemblyLoadContext, ObjectHandleOnStack retAssembly)
   at System.Reflection.RuntimeAssembly.InternalLoad(AssemblyName assemblyName, RuntimeAssembly requestingAssembly, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, AssemblyLoadContext assemblyLoadContext)
   at System.Reflection.Assembly.Load(AssemblyName assemblyRef)
   at Microsoft.AspNetCore.Hosting.GenericWebHostBuilder.ExecuteHostingStartups()
   --- End of inner exception stack trace ---

更新 我能够复制上述错误消息,并通过 Serilog 将它们登录到数据库中。

所以我们可以从 Server Program.cs Main 方法(上图)中看到“正在启动”,下一个条目来自 EntityFramework Model Validation。然后是错误。我可以看到命名空间为 Microsoft.AspNetCore.Hosting.Diagnostics 作为异常的来源。

    我试图添加一个 Nuget 引用,但什么也没做

    我尝试添加对已添加服务的引用。AddApplicationInsightsTelemetry();到服务器 Startup.cs ConfigureServices 和 ApplicationInsights InstrumentationKey 到 appsettings.json(已作为变量存在于 Azure 中),但什么也没做

    我添加了引用并添加了

public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .UseSerilog()
                .ConfigureWebHostDefaults(webBuilder =>
                
                    webBuilder.UseStartup<Startup>().UseAzureAppServices();
                );

根据此处找到的解决方法https://github.com/dotnet/extensions/issues/2566 没有帮助

第 2 天更新

添加更多信息,因为我仍然遇到相同的异常。我很好奇这是否是版本之间的兼容性问题。我的应用程序是 .net5 并使用 .net5 早期访问在 Azure 上运行。

这是我的服务器项目的 Nuget 包

<ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="5.0.1" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="5.0.1" />
    <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="5.0.1" />
    <PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="5.0.1" />
    <PackageReference Include="Microsoft.AspNetCore.ApiAuthorization.IdentityServer" Version="5.0.1" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="5.0.1" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.1">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <PackageReference Include="Microsoft.Extensions.Configuration" Version="5.0.0" />
    <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="5.0.1" />
    <PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />
    <PackageReference Include="Serilog.AspNetCore.Ingestion" Version="1.0.0-dev-00012" />
    <PackageReference Include="Serilog.Settings.Configuration" Version="3.1.0" />
    <PackageReference Include="Serilog.Sinks.MSSqlServer" Version="5.6.0" />
  </ItemGroup>

这里是客户端项目的 Nuget 包

<ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="5.0.1" />
    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="5.0.1" />
    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="5.0.1" />
    <PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
    <PackageReference Include="Serilog.Extensions.Logging" Version="3.0.1" />
    <PackageReference Include="Serilog.Sinks.BrowserHttp" Version="1.0.0-dev-00012" />
    <PackageReference Include="System.Net.Http.Json" Version="5.0.0" />
  </ItemGroup>

我尝试从客户端和服务器项目中删除 Serilog。我还是收到了 500。

删除 Serilog 后,我尝试使用来自 https://github.com/dotnet/extensions/issues/2566 的 .UseAzureAppServices() 也没有运气。

我确实注意到了额外的错误消息

2021-01-06 19:00:38.322 +00:00 [Error] Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware: An unhandled exception has occurred while executing the request.
System.NullReferenceException: Object reference not set to an instance of an object.
   at Microsoft.Extensions.DependencyInjection.IdentityServerBuilderConfigurationExtensions.<>c.<AddSigningCredentials>b__10_2(IServiceProvider sp)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context)
   at ... (removed for post size) 

当我将我的网站部署为 Blazor WebAssembly 托管解决方案时,我已经能够将其隔离为一个问题。我能够获得一个升级到 .net5 PRE 的网站版本,从 Blazor WebAssembly 切换到 Blazor WebAssembly Hosted。 .net5 版本能够毫无问题地部署到 Azure。部署 WebAssembly Hosted 版本时,出现 500 错误。因此,这与将 Blazor WebAssembly 托管解决方案部署到 Azure 有关。

我还通过创建一个没有身份验证的开箱即用 Blazor WebAssembly 托管解决方案进行了实验,并将其部署到 Azure。这没有问题。但是,当我创建一个开箱即用的 Blazor WebAssembly Hosted WITH Authentication(存储在应用程序中的单个用户帐户)并将其部署到 Azure 时,它​​会失败并显示 500!

【问题讨论】:

我实际上能够复制上面列出的错误。我相信这是我的问题。 您可以通过 Azure 门户、iirc WebApp > 开发工具 > 高级访问日志。 Henk 我按照描述进入了高级,在调试控制台中的 eventlog.xml 我有一个 6700 行文件。老实说,我不知道该怎么做。这远远超出了我的知识范围 滚动到底部并解码那里的异常。 XML 应该是“人类可读的”(-: 仅供参考,我也有同样的问题 【参考方案1】:

使其工作的最简单方法:

将环境变量ASPNETCORE_ENVIRONMENT添加/设置为Development

...您的 Hosted Blazor WASM with Identity 最终将在 Azure 应用服务中运行


如果您不想要上述简单方法,请改为:

    按照本文生成自签名证书:

(在生成自签名证书部分) https://gavilan.blog/2020/08/18/blazor-using-a-self-signed-certificate-for-identityserver4-in-azure-app-service/

    记住您用于生成证书的密码。 将证书放入您的项目中(例如服务器项目中) 将这些附加到appsettings.json 文件中:

    再次发布应用。

【讨论】:

所以我厌倦了简单的方法,这对我不起作用。我仍然有同样的问题 @JoeyD 可能发生了一些变化,但以前可以。添加环境变量后可以再次发布/重新部署应用程序吗?或者只是选择第二个选项,它真的很容易遵循。

以上是关于使用身份验证托管的 Blazor Wasm 上出现 Azure 500 错误的主要内容,如果未能解决你的问题,请参考以下文章

IdentityServerBuilderConfigurationExtension 中的 Blazor WASM 托管身份验证空引用异常

升级到 .net 6 时托管的 Blazor WASM 身份验证中断

Blazor Wasm 身份验证

使用 Blazor wasm Bearer Token 进行 SignalR 身份验证

使用通过 Blazor WASM 使用 Windows 身份验证的 WebAPI

如何在 Blazor WASM 中对当前经过身份验证的用户帐户信息执行 CRUD 操作?