2016-09-08 25 views
10

Tôi có API sử dụng IdentityServer4 để xác thực mã thông báo. Tôi muốn đơn vị kiểm tra API này với một TestServer trong bộ nhớ. Tôi muốn lưu trữ IdentityServer trong TestServer trong bộ nhớ.Thử nghiệm tích hợp với IdentityServer

Tôi đã quản lý để tạo mã thông báo từ IdentityServer.

Đây là cách xa tôi đã đến, nhưng tôi nhận được một lỗi "Không thể để có được cấu hình từ http://localhost:54100/.well-known/openid-configuration"

Việc sử dụng Api [Duyệt] -attribute với các chính sách khác nhau. Đây là những gì tôi muốn thử nghiệm.

Điều này có thể được thực hiện hay không và tôi đang làm gì sai? Tôi đã cố gắng xem xét mã nguồn cho IdentityServer4, nhưng đã không đi qua một kịch bản thử nghiệm tích hợp tương tự.

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

Và trong đơn vị thử nghiệm của tôi:

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

Trả lời

3

Tôi nghĩ rằng bạn có thể cần phải thực hiện một thử nghiệm giả đôi cho middleware cho phép bạn tùy thuộc vào bao nhiêu chức năng mà bạn muốn. Vì vậy, về cơ bản bạn muốn có một phần mềm trung gian làm tất cả mọi thứ mà phần mềm trung gian cấp phép làm trừ đi cuộc gọi kênh trở lại đối với tài liệu khám phá.

IdentityServer4.AccessTokenValidation là trình bao bọc xung quanh hai phần giữa. Phần mềm trung gian JwtBearerAuthentication và phần mềm trung gian OAuth2IntrospectionAuthentication. Cả hai đều lấy tài liệu khám phá trên http để sử dụng để xác thực mã thông báo. Đó là một vấn đề nếu bạn muốn làm một thử nghiệm trong bộ nhớ khép kín.

Nếu bạn muốn gặp rắc rối, có thể bạn sẽ cần tạo một phiên bản giả mạo app.UseIdentityServerAuthentication mà không thực hiện cuộc gọi bên ngoài tìm nạp tài liệu khám phá. Nó chỉ điền vào phần tử HttpContext để các chính sách [Authorize] của bạn có thể được kiểm tra.

Kiểm tra xem thịt của IdentityServer4.AccessTokenValidation trông như thế nào here. Và theo dõi bằng cách xem JwtBearer Middleware trông như thế nào here

+0

Thanks a lot @Lutando. Câu trả lời đầu tiên của bạn đã chỉ cho tôi đúng hướng. –

+0

ah ok @emedbo Tôi nghĩ rằng nó có thể là một chút nhiều để làm cho kiểm tra giả gấp đôi. Nhưng nó hoạt động :) – Lutando

+0

Vâng, dự án giả mạo bao gồm khoảng 20 tập tin, do đó, đó là một trách nhiệm pháp lý. Mặc dù sự lộn ngược là khá gọn gàng! Tôi chắc chắn ai đó có nhiều kiến ​​thức hơn tôi có thể làm cho nó ít phức tạp hơn. –

4

Tôi hiểu rằng cần có câu trả lời hoàn chỉnh hơn những gì @ james-fera đã đăng. Tôi đã học được từ câu trả lời của mình và thực hiện một dự án github bao gồm một dự án thử nghiệm và dự án API. Mã nên tự giải thích và không khó hiểu.

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

Các IdentityServerSetup.cs lớp https://github.com/emedbo/identityserver-test-template/blob/master/tests/API.Tests/Config/IdentityServerSetup.cs có thể được trừu tượng đi ví dụ NuGetted đi, để lại các lớp cơ sở IntegrationTestBase.cs

Các tinh hoa có thể làm cho thử nghiệm IdentityServer hoạt động giống như một IdentityServer bình thường, với người dùng, khách hàng, phạm vi, mật khẩu, vv Tôi đã thực hiện phương pháp DELETE [Authorize (Role = ") admin)] để chứng minh điều này.

Thay vì đăng mã ở đây, tôi khuyên bạn nên đọc @ bài james-fera của để có được những điều cơ bản sau đó kéo dự án của tôi và chạy thử nghiệm.

IdentityServer là một công cụ tuyệt vời như vậy, và với khả năng sử dụng khung công tác TestServer thậm chí còn tốt hơn.

+0

Nếu bạn vẫn còn cho nó, muốn xem dự án bạn đề cập đến. Tôi hiện đang cố gắng làm một cái gì đó tương tự như những gì bạn mô tả. –

+0

Bạn đã thử những gì @ james-fera đăng trên? Nếu không, tôi sẽ thử nó trước, vì giải pháp của tôi đòi hỏi nhiều mã hơn. –

+0

Tôi đã thử nó và nó không hoạt động. Nhưng sau đó tôi tìm thấy một lỗi cấu hình trong thiết lập máy chủ nhận dạng của tôi. Sau khi cố định, đề xuất của @ james-fera đã hoạt động hoàn hảo. –

12

Bạn đã sử dụng anh ta theo dõi đúng với mã được đăng trong câu hỏi ban đầu của bạn.

Các IdentityServerAuthenticationOptions đối tượng có thuộc tính để ghi đè mặc định HttpMessageHandlers nó sử dụng để liên lạc kênh trở lại.

Khi bạn kết hợp này với CreateHandler() phương pháp trên Tuyên chiến đối tượng của bạn, bạn nhận được:

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

Điều này cho phép AccessTokenValidation middleware trong dự án Api của bạn để trao đổi trực tiếp với In- của bạn Bộ nhớ IdentityServer mà không cần phải nhảy qua hoops.

Như một mặt lưu ý, đối với một dự án Api, tôi thấy nó hữu ích để thêm IdentityServerAuthenticationOptions đến việc thu dịch vụ trong Startup.cs sử dụng TryAddSingleton thay vì tạo ra nó inline:

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

     //... 

    } 

Điều này cho phép bạn đăng ký đối tượng IdentityServerAuthenticationOptions trong các thử nghiệm của mình mà không phải thay đổi mã trong dự án Api.

+0

Trông khá gọn gàng, tôi sẽ thử điều đó. Bạn có thể tạo auth và tất cả các tuyên bố với giải pháp này? Giải pháp hiện tại của tôi liên quan đến nhiều công việc hơn, nhưng nó hiện đang chạy trơn tru và Im có thể thử nghiệm chống lại vai trò, tuyên bố, mọi thứ. –

+0

Điều này tạo ra đầy đủ chức năng trong bộ nhớ IdentityServer và máy chủ Api (Tôi giữ chúng trong hai đối tượng TestServer riêng biệt không giống như ví dụ ban đầu của bạn mà có cả hai trong cùng một máy chủ). Điều này cho phép bạn kiểm tra bất cứ điều gì bạn thích. Chỉ cần đưa ra yêu cầu bằng cách sử dụng apiClient và kiểm tra câu trả lời. Các tính năng ủy quyền (các xác nhận quyền sở hữu, vai trò, vv) được lưu trữ trong mã thông báo được trả về từ IdentityServer và nó phụ thuộc vào máy chủ Api để sử dụng các tính năng đó khi nó thấy phù hợp. –

+0

Đây là gọn gàng. Tôi đã cố gắng để tìm một cái móc để cho phép tiêm của một xử lý kênh giả trở lại nhưng tôi không tìm thấy một :) đã được thêm vào hoặc luôn luôn ở đó? – Lutando

0

API kiểm tra khởi động:

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

     ... 
    } 
} 

Gán AuthServer.Handler để TestApi BackChannelHandler trong dự án thử nghiệm đơn vị của tôi:

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>(); 
    } 
Các vấn đề liên quan