How can I set my IdentityServer4 BackChannelHandler from within an xUnit integration test using WebApplicationFactory?

烈酒焚心 提交于 2021-01-29 06:12:16

问题


Update: After correcting the certificate issue, I'm now getting a 500 response form the test, with this message:

InvalidOperationException: IDX20803: Unable to obtain configuration from: 'http://localhost/.well-known/openid-configuration'.

That appears to be similar to this issue: https://github.com/IdentityServer/IdentityServer4/issues/685; however, I can't come up with a way to set the backchannel client or handler from my test -- it seems like a chicken and egg situation.


This issue was fixed by using a real certificate/.pfx file. That lead to the above issue.

I'm using WebApplicationFactory to do integration tests over my API, and I think I've covered all the bases with getting the http clients configured correctly. I'm getting an error when calling an action in my API.

This is the error when executing a get against the api with a token:

WWW-Authenticate: Bearer error="invalid_token", error_description="The signature key was not found"

Here's a simple test class that demonstrates this problem:

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);
    }
}

Snip from Startup.cs in the API project:

services.AddIdentityServer()
            .AddSigningCredential(myCertificate)
            .AddSigningCredential()
            .AddClientStore<CustomClientStore>()
            .AddInMemoryIdentityResources(IdentityServerConfig.IdentityResources)
            .AddInMemoryApiResources(IdentityServerConfig.Apis);
  • I am hosting the IdentityServer4 and the API in the same project.
  • When I manually perform integration tests, through a browser, it works just fine
  • I have found this which seems very similar

Is there something I need to account for when running in the context of an xunit test that I am not?


回答1:


The only solution I've come up with, is to setup a static Handler on the Startup class of the API project and then override it from within each unit test.

Specifically:

// 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/";
      }
  });

Complete listing from IdSvrAndApi project:

 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/";
                }
            });
    }

A fully working sample of this can be seen here: https://github.com/fuzzzerd/IdentityServerAndApi.

I have not received a definitive answer on if this is the recommended way to acheive this, but I did open an issue on the project here: https://github.com/IdentityServer/IdentityServer4/issues/2877



来源:https://stackoverflow.com/questions/52953732/how-can-i-set-my-identityserver4-backchannelhandler-from-within-an-xunit-integra

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!