2016-09-08 5 views
10

トークン検証にIdentityServer4を使用するAPIがあります。 私は、このAPIをメモリ内TestServerでユニットテストしたいと思っています。私はメモリ内のTestServerにIdentityServerをホストしたいと思います。インメモリIdentityServerによる統合テスト

私はIdentityServerからトークンを作成することができました。

これは、私が来てどのくらいですが、私は異なるポリシーと

のAPIの使用[承認] -attributeエラー「http://localhost:54100/.well-known/openid-configurationから設定を取得できません」を取得。これは私がテストしたいものです。

これを行うことができますか、間違っていますか? 私は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(); 
} 

そして、私のユニットテストで:

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

答えて

3

私は、あなたはおそらくあなたが望むどのくらいの機能に応じて、あなたの認可ミドルウェアのテストダブル偽物を作る必要があると思います。したがって、基本的には、認証ミドルウェアがディスカバリー・ドックへのバックチャネル呼び出しを除いたすべてのことを行うミドルウェアが必要です。

IdentityServer4.AccessTokenValidationは2つのミドルウェアのラッパーです。 JwtBearerAuthenticationミドルウェア、およびOAuth2IntrospectionAuthenticationミドルウェアです。これらの両方は、トークンの検証に使用するために、httpを介して検出文書を取得します。これは、メモリ内の自己完結型テストを実行する場合に問題になります。

問題を解決するには、おそらくapp.UseIdentityServerAuthenticationの偽のバージョンを作成して、検出文書を取得する外部呼び出しを行わないといけません。 [Authorize]ポリシーをテストできるように、HttpContextプリンシパルにのみ移入されます。

IdentityServer4.AccessTokenValidationの肉がどのようにhereとなっているかを確認してください。そして、JwtBearer Middlewareがどのように見えるかを見てください。here

+0

おかげでたくさんの@Lutando:私のユニットテストプロジェクトにTestApi BackChannelHandlerにAuthServer.Handlerの割り当て

public class Startup { public static HttpMessageHandler BackChannelHandler { get; set; } public void Configuration(IAppBuilder app) { //accept access tokens from identityserver and require a scope of 'Test' app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions { Authority = "https://localhost", BackchannelHttpHandler = BackChannelHandler, ... }); ... } } 

。あなたの最初の答えは正しい方向に私を指摘した。 –

+0

ah ok @emedbo私は、偽のテストを二重にするのはちょっとかもしれないと思っていました。しかし、それは動作します:) – Lutando

+0

ええ、偽のプロジェクトは、約20ファイルで構成されているので、責任があります。逆さまですがかなりきれいです!私より多くの知識を持つ人が、それほど複雑ではないと確信しています。 –

4

@ james-feraが投稿したものよりも完全な答えが必要であると私は理解しています。私は彼の答えから学び、テストプロジェクトとAPIプロジェクトからなるgithubプロジェクトを作った。コードは自明で理解しにくいはずです。

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

IdentityServerSetup.csクラスhttps://github.com/emedbo/identityserver-test-template/blob/master/tests/API.Tests/Config/IdentityServerSetup.cs例えば離れて抽象化することができますNuGetted、基底クラスを残してIntegrationTestBase.cs

エッセンスは、ユーザー、クライアント、スコープ、パスワードなどで、通常のIdentityServerと同じようにテストIdentityServerを動作させることができます。[Authorize(Role = "これを証明するために管理者)]。代わりに、ここでのコードを投稿の

、私は基本的には、その後の私のプロジェクトを取得し、テストを実行得るためにジェームズ・FERAのポスト@読んでお勧めします。

IdentityServerは、このような素晴らしいツールです、ととTestServerフレームワークを使用する能力はさらに向上します。

+0

あなたがまだそれをしているなら、あなたが言及したプロジェクトを見たいと思います。私は現在あなたが描いているものに似た何かをしようとしています。 –

+0

あなたは@ james-feraを投稿しましたか?そうでない場合は、私のソリューションはもっと多くのコードを必要とするため、最初に試してみます。 –

+0

私はそれを試して、それは動作しませんでした。しかし、私のアイデンティティサーバーの設定で設定エラーが見つかりました。一度固定すると、@ james-feraの提案は完全に機能しました。 –

12

彼はあなたの最初の質問に投稿されたコードを正しく追跡します。

IdentityServerAuthenticationOptions目的は、バックチャネル通信のために使用するデフォルトをHttpMessageHandlersを上書きする特性を有します。あなたはのcreateHandler()あなたのTESTSERVERオブジェクト上方法でこれを組み合わせたら

あなたが得る:

//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 

は、これはあなたのInと直接通信するためにあなたのAPIプロジェクトにAccessTokenValidationミドルウェアを可能にメモリアイデンティティサーバーフープを飛び越える必要はありません。代わりに、インラインそれを作成する

注意点として、APIプロジェクトのために、私はそれが便利TryAddSingletonを使用してStartup.csにサービスコレクションにIdentityServerAuthenticationOptionsを追加するために見つける:

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

     //... 

    } 

これにより、Apiプロジェクトのコードを変更せずに、IdentityServerAuthenticationOptionsオブジェクトをテストに登録することができます。

+0

それはかなりきれいに見える、私はそれを試してみましょう。このソリューションを使用して認証とすべての申し立てを作成できますか?私の現在のソリューションにはもっと多くの作業が必要でしたが、現在はスムーズに実行されており、役割、要求、すべてに対してテストすることができます。 –

+0

これは、完全に機能するメモリ内のIdentityServerとApiサーバーを作成します(私は、同じサーバー内にあった元の例とは異なり、2つのTestServerオブジェクトにそれらを保持します)。これにより、好きなものをテストすることができます。 apiClientを使用してリクエストを発行し、レスポンスをテストします。認可機能(クレーム、ロールなど)はIdentityServerから返されたトークンに格納され、適切に見えるようにそれらの機能を使用するのはApiサーバーまでです。 –

+0

これはきれいです。私は偽のバックチャネルハンドラの注入を許可するフックを探してみましたが、見つけられませんでした。これが追加されたか、常にそこにありましたか? – Lutando

0

テストAPIの起動:

protected TestServer AuthServer { get; set; } 
    protected TestServer MockApiServer { get; set; } 
    protected TestServer TestApiServer { get; set; } 

    [OneTimeSetUp] 
    public void Setup() 
    { 
     ... 
     AuthServer = TestServer.Create<AuthenticationServer.Startup>(); 
     TestApi.Startup.BackChannelHandler = AuthServer.Handler; 
     TestApiServer = TestServer.Create<TestApi.Startup>(); 
    }