使用内存IdentityServer进行集成测试

Esp*_*dbø 21 identityserver4

我有一个使用IdentityServer4进行令牌验证的API.我想用内存中的TestServer对这个API进行单元测试.我想将IdentityServer托管在内存中的TestServer中.

我已经设法从IdentityServer创建一个令牌.

这是我到底有多远,但是我收到错误"无法从http:// localhost:54100/.well-known/openid-configuration获取配置 "

Api使用[授权] - 属性与不同的策略.这是我想测试的.

可以这样做,我做错了什么?我试图查看IdentityServer4的源代码,但没有遇到过类似的集成测试场景.

protected IntegrationTestBase()
{
    var startupAssembly = typeof(Startup).GetTypeInfo().Assembly;

    _contentRoot = SolutionPathUtility.GetProjectPath(@"<my project path>", startupAssembly);
    Configure(_contentRoot);
    var orderApiServerBuilder = new WebHostBuilder()
        .UseContentRoot(_contentRoot)
        .ConfigureServices(InitializeServices)
        .UseStartup<Startup>();
    orderApiServerBuilder.Configure(ConfigureApp);
    OrderApiTestServer = new TestServer(orderApiServerBuilder);

    HttpClient = OrderApiTestServer.CreateClient();
}

private void InitializeServices(IServiceCollection services)
{
    var cert = new X509Certificate2(Path.Combine(_contentRoot, "idsvr3test.pfx"), "idsrv3test");
    services.AddIdentityServer(options =>
        {
            options.IssuerUri = "http://localhost:54100";
        })
        .AddInMemoryClients(Clients.Get())
        .AddInMemoryScopes(Scopes.Get())
        .AddInMemoryUsers(Users.Get())
        .SetSigningCredential(cert);

    services.AddAuthorization(options =>
    {
        options.AddPolicy(OrderApiConstants.StoreIdPolicyName, policy => policy.Requirements.Add(new StoreIdRequirement("storeId")));
    });
    services.AddSingleton<IPersistedGrantStore, InMemoryPersistedGrantStore>();
    services.AddSingleton(_orderManagerMock.Object);
    services.AddMvc();
}

private void ConfigureApp(IApplicationBuilder app)
{
    app.UseIdentityServer();
    JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
    var options = new IdentityServerAuthenticationOptions
    {
        Authority = _appsettings.IdentityServerAddress,
        RequireHttpsMetadata = false,

        ScopeName = _appsettings.IdentityServerScopeName,
        AutomaticAuthenticate = false
    };
    app.UseIdentityServerAuthentication(options);
    app.UseMvc();
}
Run Code Online (Sandbox Code Playgroud)

在我的单元测试中:

private HttpMessageHandler _handler;
const string TokenEndpoint = "http://localhost/connect/token";
public Test()
{
    _handler = OrderApiTestServer.CreateHandler();
}

[Fact]
public async Task LeTest()
{
    var accessToken = await GetToken();
    HttpClient.SetBearerToken(accessToken);

    var httpResponseMessage = await HttpClient.GetAsync("stores/11/orders/asdf"); // Fails on this line

}

private async Task<string> GetToken()
{
    var client = new TokenClient(TokenEndpoint, "client", "secret", innerHttpMessageHandler: _handler);

    var response = await client.RequestClientCredentialsAsync("TheMOON.OrderApi");

    return response.AccessToken;
}
Run Code Online (Sandbox Code Playgroud)

Jam*_*era 21

您在初始问题中发布的代码处于正确的轨道上.

所述IdentityServerAuthenticationOptions对象具有属性覆盖默认HttpMessageHandlers它使用反向信道的通信.

将此与TestServer对象上的CreateHandler()方法结合使用后,您将获得:

    //build identity server here

    var idBuilder = new WebBuilderHost();
    idBuilder.UseStartup<Startup>();
    //...

    TestServer identityTestServer = new TestServer(idBuilder);

    var identityServerClient = identityTestServer.CreateClient();

    var token = //use identityServerClient to get Token from IdentityServer

    //build Api TestServer
    var options = new IdentityServerAuthenticationOptions()
    {
        Authority = "http://localhost:5001",

        // IMPORTANT PART HERE
        JwtBackChannelHandler = identityTestServer.CreateHandler(),
        IntrospectionDiscoveryHandler = identityTestServer.CreateHandler(),
        IntrospectionBackChannelHandler = identityTestServer.CreateHandler()
    };

    var apiBuilder = new WebHostBuilder();

    apiBuilder.ConfigureServices(c => c.AddSingleton(options));
    //build api server here

    var apiClient = new TestServer(apiBuilder).CreateClient();
    apiClient.SetBearerToken(token);

    //proceed with auth testing
Run Code Online (Sandbox Code Playgroud)

这允许Api项目中的AccessTokenValidation中间件直接与内存中的IdentityServer通信,而无需跳过箍.

作为旁注,对于Api项目,我发现使用TryAddSingletonIdentityServerAuthenticationOptions添加到Startup.cs中的服务集合而不是内联创建它是有用的:

    public void ConfigureServices(IServiceCollection services)
    {
        services.TryAddSingleton(new IdentityServerAuthenticationOptions
        {
            Authority = Configuration.IdentityServerAuthority(),
            ScopeName = "api1",
            ScopeSecret = "secret",
            //...,
        });
    }

    public void Configure(IApplicationBuilder app)
    {
        var options = app.ApplicationServices.GetService<IdentityServerAuthenticationOptions>()

        app.UseIdentityServerAuthentication(options);

        //...

    }
Run Code Online (Sandbox Code Playgroud)

这允许您在测试中注册IdentityServerAuthenticationOptions对象,而无需更改Api项目中的代码.


Esp*_*dbø 6

我知道需要一个比@james-fera发布的答案更完整的答案.我从他的回答中学到了一个包含测试项目和API项目的github项目.代码应该是不言自明的,不难理解.

https://github.com/emedbo/identityserver-test-template

https://github.com/emedbo/identityserver-test-template/blob/master/tests/API.Tests/Config/IdentityServerSetup.cs这个IdentityServerSetup.cs类可以被抽象掉,例如NuGetted,离开基类IntegrationTestBase.cs

其实质是可以使测试IdentityServer像普通的IdentityServer一样工作,具有用户,客户端,范围,密码等.我已经使用DELETE方法[Authorize(Role ="admin)]来证明这一点.

我不建议在这里发布代码,而是推荐阅读@james-fera的帖子来获取基础知识然后拉动我的项目并运行测试.

IdentityServer是一个非常棒的工具,并且能够使用TestServer框架,它变得更好.


Lut*_*ndo 4

我认为您可能需要为您的授权中间件制作一个测试双重伪造,具体取决于您想要多少功能。因此,基本上您需要一个中间件来完成授权中间件所做的所有事情,减去对发现文档的反向通道调用。

IdentityServer4.AccessTokenValidation 是两个中间件的包装器。中间件JwtBearerAuthenticationOAuth2IntrospectionAuthentication中间件。这两者都通过 http 获取发现文档以用于令牌验证。如果您想进行内存中的独立测试,这是一个问题。

如果您想解决这个问题,您可能需要制作一个假版本,app.UseIdentityServerAuthentication该版本不会执行获取发现文档的外部调用。它仅填充 HttpContext 主体,以便可以测试您的 [Authorize] 策略。

在这里查看 IdentityServer4.AccessTokenValidation 的主要内容。接下来看看 JwtBearer Middleware 的外观