使用 WebApplicationFactory 时如何使用 JWT Bearer Scheme

Posted

技术标签:

【中文标题】使用 WebApplicationFactory 时如何使用 JWT Bearer Scheme【英文标题】:How use JWT Bearer Scheme when working with WebApplicationFactory 【发布时间】:2021-05-11 00:11:04 【问题描述】:

我最近更新了一个可以使用的 Web API,以使用 JWT 身份验证,虽然它在我正常运行时可以正常工作,但我似乎无法让我的集成测试正常工作。

我想开始为我的集成测试集成令牌生成选项,但我什至无法让他们抛出 401。

当我在项目中运行任何没有 JWT 的现有集成测试时,我预计会收到 401,因为我没有任何身份验证信息,但实际上我收到了 System.InvalidOperationException : Scheme already exists: Bearer 错误。

我认为这是因为 WebApplicationFactory 的工作方式是在 Startup 类的 ConfigureServices 方法之后运行其 ConfigureWebHost 方法,并且当我在我的 jwt 服务上设置断点时,确实如此被击中两次,但鉴于这是WebApplicationFactory 的构建方式,我不确定这里推荐的选项是什么。值得注意的是,即使我删除了其中一项服务,我仍然会收到错误消息:

var serviceDescriptor = services.FirstOrDefault(descriptor => descriptor.ServiceType == typeof(JwtBearerHandler));
services.Remove(serviceDescriptor);

我的WebApplicationFactory是基于eshopwebapi工厂的:


    public class CustomWebApplicationFactory : WebApplicationFactory<StartupTesting>
    
        // checkpoint for respawning to clear the database when spinning up each time
        private static Checkpoint checkpoint = new Checkpoint
        
            
        ;

        protected override void ConfigureWebHost(IWebHostBuilder builder)
        
            builder.UseEnvironment("Testing");

            builder.ConfigureServices(async services =>
            
                // Create a new service provider.
                var provider = services
                    .AddEntityFrameworkInMemoryDatabase()
                    .BuildServiceProvider();

                // Add a database context (LabDbContext) using an in-memory 
                // database for testing.
                services.AddDbContext<LabDbContext>(options =>
                
                    options.UseInMemoryDatabase("InMemoryDbForTesting");
                    options.UseInternalServiceProvider(provider);
                );

                // Build the service provider.
                var sp = services.BuildServiceProvider();

                // Create a scope to obtain a reference to the database
                // context (ApplicationDbContext).
                using (var scope = sp.CreateScope())
                
                    var scopedServices = scope.ServiceProvider;
                    var db = scopedServices.GetRequiredService<LabDbContext>();

                    // Ensure the database is created.
                    db.Database.EnsureCreated();

                    try
                    
                        await checkpoint.Reset(db.Database.GetDbConnection());
                    
                    catch
                    
                    
                
            ).UseStartup<StartupTesting>();
        

        public HttpClient GetAnonymousClient()
        
            return CreateClient();
        
    

这是我的服务注册:

    public static class ServiceRegistration
    
        public static void AddIdentityInfrastructure(this IServiceCollection services, IConfiguration configuration)
        
            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options =>
                
                    options.Authority = configuration["JwtSettings:Authority"];
                    options.Audience = configuration["JwtSettings:Audience"];
                );
            
            services.AddAuthorization(options =>
            
                options.AddPolicy("CanReadPatients", 
                    policy => policy.RequireClaim("scope", "patients.read"));
                options.AddPolicy("CanAddPatients", 
                    policy => policy.RequireClaim("scope", "patients.add"));
                options.AddPolicy("CanDeletePatients", 
                    policy => policy.RequireClaim("scope", "patients.delete"));
                options.AddPolicy("CanUpdatePatients", 
                    policy => policy.RequireClaim("scope", "patients.update"));
            );
        
    

这是我的集成测试(我希望目前会抛出 401):

public class GetPatientIntegrationTests : IClassFixture<CustomWebApplicationFactory>
     
        private readonly CustomWebApplicationFactory _factory;

        public GetPatientIntegrationTests(CustomWebApplicationFactory factory)
        
            _factory = factory;
        

        
        [Fact]
        public async Task GetPatients_ReturnsSuccessCodeAndResourceWithAccurateFields()
        
            var fakePatientOne = new FakePatient  .Generate();
            var fakePatientTwo = new FakePatient  .Generate();

            var appFactory = _factory;
            using (var scope = appFactory.Services.CreateScope())
            
                var context = scope.ServiceProvider.GetRequiredService<LabDbContext>();
                context.Database.EnsureCreated();

                context.Patients.AddRange(fakePatientOne, fakePatientTwo);
                context.SaveChanges();
            

            var client = appFactory.CreateClient(new WebApplicationFactoryClientOptions
            
                AllowAutoRedirect = false
            );

            var result = await client.GetAsync("api/Patients")
                .ConfigureAwait(false);
            var responseContent = await result.Content.ReadAsStringAsync()
                .ConfigureAwait(false);
            var response = JsonConvert.DeserializeObject<Response<IEnumerable<PatientDto>>>(responseContent).Data;

            // Assert
            result.StatusCode.Should().Be(200);
            response.Should().ContainEquivalentOf(fakePatientOne, options =>
                options.ExcludingMissingMembers());
            response.Should().ContainEquivalentOf(fakePatientTwo, options =>
                options.ExcludingMissingMembers());
        
     

【问题讨论】:

【参考方案1】:

嘿,当我在寻找相同的答案时,我看到了你的帖子。我通过将以下代码放入 WebApplicationFactory 的 ConfigureWebHost 方法中解决了这个问题:

    protected override void ConfigureWebHost(
        IWebHostBuilder builder)
    
        builder.ConfigureServices(serviceCollection =>
        

        );
   
        // Overwrite registrations from Startup.cs
        builder.ConfigureTestServices(serviceCollection =>
        
            var authenticationBuilder = serviceCollection.AddAuthentication();
            authenticationBuilder.Services.Configure<AuthenticationOptions>(o =>
            
                o.SchemeMap.Clear();
                ((IList<AuthenticationSchemeBuilder>) o.Schemes).Clear();
            );
        );
    

我知道我迟到了四个月,但我希望你仍然可以使用它。

【讨论】:

我也使用这个库进行虚假身份验证:github.com/webmotions/fake-authentication-jwtbearer。也许对你也有一些用处。 我和@weretiger做了同样的事情

以上是关于使用 WebApplicationFactory 时如何使用 JWT Bearer Scheme的主要内容,如果未能解决你的问题,请参考以下文章

使用 WebApplicationFactory 时如何使用 JWT Bearer Scheme

学习 ASP.NET Core 2.1:集成测试中使用 WebApplicationFactory

如何在 .net6 中使用 WebApplicationFactory(没有可说的入口点)

AspNetCore 集成测试多个 WebApplicationFactory 实例?

在 ASP.NET Core 集成测试中从 WebApplicationFactory<T> 获取服务

ASP.Net 6 自定义 WebApplicationFactory 抛出异常