How do I integration test a ASP 5/Core Web API wit

2019-06-27 02:12发布

I currently have an ASP 5/ASP Core Web API that I need to integration test with the OWIN Test Server.

The problem is that I use IdentityServer as the authorization server in production and I do not want to include the authorization as part of my integration testing.

This is the Startup.cs of the API:

public Startup(IHostingEnvironment env)
{
    // Set up configuration sources.
    IConfigurationBuilder builder = new ConfigurationBuilder()
        .AddJsonFile("appsettings.json")
        .AddJsonFile($"appsettings.{env.EnvironmentName}.json", true);

    if (env.IsEnvironment("Development"))
    {
        // This will push telemetry data through Application Insights pipeline faster, allowing you to view results immediately.
        builder.AddApplicationInsightsSettings(developerMode: true);
    }

    builder.AddEnvironmentVariables();
    Configuration = builder.Build().ReloadOnChanged("appsettings.json");
}

public IConfigurationRoot Configuration { get; set; }

// This method gets called by the runtime. Use this method to add services to the container
public IServiceProvider ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddApplicationInsightsTelemetry(Configuration);

    ConfigureEntityFrameworkDatabase(services, Configuration);

    services.AddIdentity<IdentityUser, IdentityRole>()
        .AddEntityFrameworkStores<HoehenSuchtIdentityDbContext>()
        .AddDefaultTokenProviders();

    ConfigureMvc(services);

    // register autofac as dependency resolver
    ContainerBuilder containerBuilder = new ContainerBuilder();

    // register all required autofac modules
    RegisterAutofacModules(containerBuilder);

    // register all automapper mappings as di services so there dependencies can be resolved
    ConfigureAutomapper(containerBuilder);

    ConfigureSwagger(services);

    // copy all asp core dependency injection registrations to autofac
    containerBuilder.Populate(services);
    IContainer container = containerBuilder.Build();

    return container.Resolve<IServiceProvider>();
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

    if (env.IsDevelopment())
    {
        app.UseBrowserLink();
        app.UseDeveloperExceptionPage();
        app.UseDatabaseErrorPage();
    }

    // make sure the database was created and all migrations applied
    MigrateDatabase(app);
    app.ApplicationServices.GetService<HoehenSuchtDbContext>().EnsureSeedData(env);

    app.UseIISPlatformHandler();

    app.UseApplicationInsightsRequestTelemetry();
    app.UseApplicationInsightsExceptionTelemetry();

    ConfigureIdentityServer(app, Configuration);

    app.UseStaticFiles();

    app.UseMvc();

    //app.UseSwaggerGen(/*routeTemplate: "docs/{apiVersion}/swagger.json"*/);
    //app.UseSwaggerUi(/*baseRoute: "docs", swaggerUrl: "docs/v1/swagger.json"*/);
}

public static Action<IServiceCollection, IConfigurationRoot> ConfigureEntityFrameworkDatabase = (services, config) =>
{
    services.AddEntityFramework()
        .AddSqlServer()
        .AddDbContext<HoehenSuchtDbContext>(builder =>
            builder.UseSqlServer(config["Data:DefaultConnection:ConnectionString"]))
        .AddDbContext<HoehenSuchtIdentityDbContext>(builder =>
            builder.UseSqlServer(config["Data:IdentityConnection:ConnectionString"]));
};

public static Action<IServiceCollection> ConfigureMvc = services =>
{
    services.AddMvc().AddControllersAsServices(new List<Assembly> { typeof(Startup).GetTypeInfo().Assembly });
};

I already tried registering a special test middleware that in theory should authenticate and set a claims principal. But somewhere down the OWIN pipeline the authentication is denied and I get a 401 error code.

This is how I setup the OWIN Test Server:

Startup.MigrateDatabase = app =>
{
    app.ApplicationServices.GetService<HoehenSuchtDbContext>().Database.EnsureCreated();
};
Startup.ConfigureEntityFrameworkDatabase = ApiTestServer.ConfigureInMemoryDatabase;
Startup.ConfigureIdentityServer = (app, config) =>
{
    app.ApplicationServices.GetService<HoehenSuchtDbContext>().EnsureSeedData(new HostingEnvironment {EnvironmentName = "development" });

    app.UseMiddleware<AuthenticatedTestRequestMiddleware>();
};
Server = new TestServer(TestServer.CreateBuilder().UseStartup<Startup>());

And this is my custom AuthenticatedTestRequestMiddleware:

public class AuthenticatedTestRequestMiddleware
{
    public const string TestingCookieAuthentication = "TestCookieAuthentication";
    public const string TestingHeader = "X-Integration-Testing";
    public const string TestingHeaderValue = "78EAAA45-E68B-43C7-9D12-3A5F1E646BD5";

    private readonly RequestDelegate _next;

    public AuthenticatedTestRequestMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        if (context.Request.Headers.Keys.Contains(TestingHeader) && context.Request.Headers[TestingHeader].First().Equals(TestingHeaderValue))
        {
            // fake authenticated the user
            ClaimsIdentity claimsIdentity = new ClaimsIdentity();
            claimsIdentity.AddClaims(new List<Claim>
            {
                new Claim(ClaimTypes.Name, "admin"),
                new Claim(ClaimTypes.NameIdentifier, UserSeedData.AdminUserId)
            });
            ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
            context.User = claimsPrincipal;
        }

        await _next(context);
    }
}

The principal is set and exists in the database with the given ID, but after I call next(context) I get an 401 Unauthorized result.

How can I successfully fake authenticate the user and bypass the [Authorize] while also setting the current User for the HttpRequest?

UPDATE: If I register my own CookieAuthentication handler like that:

app.UseCookieAuthentication(options =>
{
    options.AuthenticationScheme = AuthenticatedTestRequestMiddleware.TestingCookieAuthentication;
    options.AutomaticAuthenticate = true;
    options.AutomaticChallenge = true;
});

I get the 302 Redirect to the login page. The signin however is working correctly when I use this inside the TestMiddleware await context.Authentication.SignInAsync(TestingCookieAuthentication, claimsPrincipal)

1条回答
Emotional °昔
2楼-- · 2019-06-27 02:42

Ok so I found out why it does not work :)

When creating the ClaimsPrincipal the AuthenticationProvider must be included in the constructor of the principal. If the authentication type is not provided the SignInAsync() function will fail and not authenticated the user.

Instead of doing this:

ClaimsIdentity claimsIdentity = new ClaimsIdentity(new List<Claim>
{
    new Claim(ClaimTypes.Name, "admin"),
    new Claim(ClaimTypes.NameIdentifier, UserSeedData.AdminUserId)
});

You must specify the AuthenticationHandler like this:

ClaimsIdentity claimsIdentity = new ClaimsIdentity(new List<Claim>
{
    new Claim(ClaimTypes.Name, "admin"),
    new Claim(ClaimTypes.NameIdentifier, UserSeedData.AdminUserId)
}, TestingCookieAuthentication);
查看更多
登录 后发表回答