通过托管 IdentityServer 的单独微服务使用 OATH 保护 WebApi 的集成测试

Integration Tests for WebApi that is protected using OATH through a separate microservice that hosts IdentityServer

背景

我们有一个 ASP.NET Core 2.1 解决方案,其微服务包含 WebApi 方法。我们使用 Identity Server 4 进行身份验证。我们有这些服务,每一个都在一个单独的项目中。

  1. crm。包含各种 WebAPI 方法。
  2. 欺诈。包含各种 WebAPI 方法。
  3. 通知。包含各种 WebAPI 方法。
  4. 身份验证。包含 IdentityServer 实现。

身份验证服务没有控制器或 WebAPI 方法,它只是配置和实现 IdentityServer4 的地方。资源、客户端、范围等在这里定义,在 Startup 中它也有初始化:

        // Identity Server
        services.AddIdentityServer()
            .AddSigningCredential(Configuration.GetValue<string>("Certificates:TokenCertificate"), System.Security.Cryptography.X509Certificates.StoreLocation.LocalMachine, NameType.Thumbprint)
            .AddInMemoryApiResources(Config.GetApiResources())
            .AddInMemoryClients(Config.GetClients(Configuration))
            .AddResourceOwnerValidator<ResourceOwnerPasswordValidator>()
            .AddProfileService<ProfileService>();

当我们使用 Postman 进行测试时,我们的身份验证工作正常,我们必须首先连接到身份验证服务的令牌端点并请求 Bearer 令牌。然后我们可以在请求的授权 header 中传递它,它工作正常。

现在是更新我们的集成测试的时候了,因为自从我们实施身份验证以来它们都失败了,它们 return 状态未授权,这是可以预料的。

问题

我们如何从集成测试中调用身份验证服务,因为它是一个单独的微服务?

我们遵循标准做法为我们的微服务创建集成测试。每个微服务都有一个测试项目,它有一个固定装置 class 使用该微服务的 Startup 创建一个 webHostBuilder,以及一个将由集成测试查询的 testServer HttpClient。

        var webHostBuilder = new WebHostBuilder()
               .UseEnvironment("Testing")
               .UseStartup<Startup>()
                .ConfigureTestServices(s =>
                {
                    addServices?.Invoke(s);
                })
               .ConfigureAppConfiguration((builderContext, config) =>
               {
                   Configuration = config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true).AddEnvironmentVariables().Build();
                   this.SignatureCertificate = CertificateHelper.FindCertificateByThumbprint(Configuration.GetValue<string>("Certificates:SignatureThumbprint"), StoreLocation.LocalMachine, StoreName.My);
                   this.EncryptionCertificate = CertificateHelper.FindCertificateByThumbprint(Configuration.GetValue<string>("Certificates:EncryptionThumbprint"), StoreLocation.LocalMachine, StoreName.My);
                   this.DecryptionCertificate = CertificateHelper.FindCertificateByThumbprint(Configuration.GetValue<string>("Certificates:DecryptionThumbprint"), StoreLocation.LocalMachine, StoreName.My);
                   this.ReadSignedCertificate = CertificateHelper.FindCertificateByThumbprint(Configuration.GetValue<string>("Certificates:ReadSignedThumbprint"), StoreLocation.LocalMachine, StoreName.My);
               });
        var testServer = new TestServer(webHostBuilder);

        this.Context = testServer.Host.Services.GetService(typeof(CrmContext)) as CrmContext;
        this.Client = testServer.CreateClient();

但是现在我们的集成测试必须首先从令牌端点请求令牌。但端点尚未由 webHostBuilder 启动,因为我们的 ID4 集成在单独的服务中。

我们是否需要从使用身份验证 ASP.NET 核心服务启动的第二个 WebHostBuilder 创建第二个 TestServer?

非常感谢任何帮助。

我们必须创建一个集合夹具 class,它使用我们的身份验证服务启动一个单独的 WebHostBuilder,并且通过将其注入构造函数,使该夹具可用于所有集成测试。

但这还不是全部,当我们调用认证方法时,web API 测试服务器无法访问认证测试服务器。使用 OATH 保护网站 api 时,此网站 api 必须访问此 url

http:///.well-known/openid-configuration

对此的解决方案是在 IdentityServer 的配置中使用 IdentityServer 选项的 属性 JwtBackChannelHandler。这是我们网站的启动 api:

        //Authentication
        services.AddAuthentication("Bearer")
            .AddIdentityServerAuthentication(options =>
            {
                options.Authority = Configuration.GetValue<string>("Authentication:BaseUrl");
                options.RequireHttpsMetadata = false;
                options.ApiName = "Crm";

                if (CurrentEnvironment.IsEnvironment("Testing"))
                {
                    options.JwtBackChannelHandler = BackChannelHandler;
                }

            });

BackChannelHandler 是我们的 web api 控制器的一个静态 属性,然后上面提到的身份验证收集夹具可以使用这个静态 属性 来指定可以使用的处理程序访问 openid 配置端点。

    public AuthenticationFixture()
    {
        //start the authentication service
        var authWebHostBuilder = new WebHostBuilder()
                .UseEnvironment("Testing")
                .UseStartup<Adv.Authentication.Api.Startup>()
                .ConfigureTestServices(s =>
                {

                    var userAccountWebServiceMock = new Mock<IUserAccountWebservice>();
                    userAccountWebServiceMock
                        .Setup(o => o.LogInAsync(It.IsAny<LogInCommand>()))
                        .Returns(Task.FromResult((ActionResult<LogInDto>)(new OkObjectResult(new LogInDto() { IsAuthenticated = true, UserId = 1 }))));

                    s.AddSingleton(userAccountWebServiceMock.Object);

                })
               .ConfigureAppConfiguration((builderContext, config) =>
               {
                   Configuration = config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true).AddEnvironmentVariables().Build();
               });

        var testServer = new TestServer(authWebHostBuilder);

        **Startup.BackChannelHandler = testServer.CreateHandler();**
        this.Client = testServer.CreateClient();

        GetAccessTokensAsync().Wait();
    }