如何使用WebApplicationFactory在xUnit集成测试中设置IdentityServer4 BackChannelHandler?

时间:2018-10-23 16:23:57

标签: c# .net oauth identityserver4 openid-connect

更新:更正了证书问题之后,我现在从测试中获得500响应,并显示以下消息:

  

InvalidOperationException:IDX20803:无法从“ http://localhost/.well-known/openid-configuration”获得配置。

这似乎与以下问题类似:https://github.com/IdentityServer/IdentityServer4/issues/685;但是,我无法从测试中提出一种设置反向通道客户端或处理程序的方法-好像是鸡和蛋的情况。


此问题已通过使用真实证书/.pfx文件修复。导致上述问题。

我正在使用WebApplicationFactory对我的API进行集成测试,我认为我已经涵盖了正确配置http客户端的所有基础。在我的API中调用操作时出现错误。

这是对带有令牌的api执行get时的错误:

  

WWW-Authenticate:承载错误=“ invalid_token”,error_description =“未找到签名密钥”

这是一个演示此问题的简单测试类:

public class EntityControllerShould : IClassFixture<WebApplicationFactory<Startup>>
{
    private readonly WebApplicationFactory<Startup> _factory;

    public EntityControllerShould(WebApplicationFactory<Startup> factory)
    {
        _factory = factory;
    }

    [Fact]
    public async Task ReturnListOfEntities_ForGet()
    {
        // arrange
        _factory.CreateClient();

        var handler = _factory.Server.CreateHandler();

        var client = new HttpClient(handler) { BaseAddress = new System.Uri("http://localhost/") };

        // discover endpoints from metadata
        var dc = new DiscoveryClient(client.BaseAddress.ToString(), handler);
        var disco = await dc.GetAsync();
        if (disco.IsError)
        {
            Assert.True(false);
        }
        // request token
        var tokenClient = new TokenClient(disco.TokenEndpoint, "api_client", "secret", handler);
        var tokenResponse = await tokenClient.RequestClientCredentialsAsync("api1");

        if (tokenResponse.IsError)
        {
            Assert.True(false);
        }

        client.SetBearerToken(tokenResponse.AccessToken);

        // act
        var response = await client.GetAsync("api/entity/?id=123");
        // response code is 401 with the above quoted error in the header
        response.EnsureSuccessStatusCode();

        var responseString = await response.Content.ReadAsStringAsync();

        // assert
        Assert.NotNull(responseString);
    }
}

API项目中Startup.cs的片段:

services.AddIdentityServer()
            .AddSigningCredential(myCertificate)
            .AddSigningCredential()
            .AddClientStore<CustomClientStore>()
            .AddInMemoryIdentityResources(IdentityServerConfig.IdentityResources)
            .AddInMemoryApiResources(IdentityServerConfig.Apis);
  • 我在同一项目中托管IdentityServer4和API。
  • 当我通过浏览器手动执行集成测试时,它工作正常
  • 我找到了this which seems very similar

在不是我进行的xunit测试的上下文中运行时,是否需要解决一些问题?

1 个答案:

答案 0 :(得分:0)

我想出的唯一解决方案是在API项目的Startup类上设置一个静态Handler,然后在每个单元测试中覆盖它。

特别是:

// startup.cs
public static HttpMessageHandler Handler { get; set; }

// snip from configureservices
.AddJwtBearer(jwt =>
 {
      // defaults as they were
      jwt.Authority = "http://localhost:5000/";
      jwt.RequireHttpsMetadata = false;
      jwt.Audience = "api1";
      // if static handler is null don't change anything, otherwise assume integration test.
      if(Handler == null)
      {
          jwt.BackchannelHttpHandler = Handler;
          jwt.Authority = "http://localhost/";
      }
  });

IdSvrAndApi项目的完整列表:

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

     public IConfiguration Configuration { get; }
     public static HttpMessageHandler Handler { get; set; }

     public void ConfigureServices(IServiceCollection services)
     {
         services.AddMvc();

         services.AddIdentityServer()
             .AddDeveloperSigningCredential()
             .AddInMemoryIdentityResources(Config.IdentityResources)
             .AddInMemoryClients(Config.Clients)
             .AddInMemoryApiResources(Config.Apis)
             .AddTestUsers(TestUsers.Users);

        services.AddAuthentication()
           .AddJwtBearer(jwt =>
           {
               jwt.BackchannelHttpHandler = Handler;
               jwt.Authority = "http://localhost/";
               jwt.RequireHttpsMetadata = false;
               jwt.Audience = "api1";
           });
            .AddJwtBearer(jwt =>
            {
                // defaults as they were
                jwt.Authority = "http://localhost:5000/";
                jwt.RequireHttpsMetadata = false;
                jwt.Audience = "api1";
                // if static handler is null don't change anything, otherwise assume integration test.
                if(Handler == null)
                {
                    jwt.BackchannelHttpHandler = Handler;
                    jwt.Authority = "http://localhost/";
                }
            });
    }

在这里可以找到完整的工作示例:https://github.com/fuzzzerd/IdentityServerAndApi

对于实现此目标的推荐方法,我尚未收到明确的答案,但是我确实在此处打开了该项目的问题:https://github.com/IdentityServer/IdentityServer4/issues/2877