Razor Pages .NET Core 2.1集成测试身份验证后

时间:2018-10-23 17:28:12

标签: authentication asp.net-core .net-core asp.net-core-2.1 razor-pages

我正在寻找一些指导...

我目前正在尝试为.net core 2.1中的Razor Pages应用编写一些集成测试,我想测试的页面是后期身份验证,但是我不确定实现该方法的最佳方法。该文档似乎建议创建一个CustomWebApplicationFactory,但除此之外,我还有些迷茫,因为我无法使用基于Cookie的基本身份验证来伪造/模拟已身份验证的用户/请求。

我已经看到针对Microsoft文档有一个开放的GitHub issue(这里是实际的GitHub issue),有一个提到的使用IdentityServer4的解决方案,但我只是在寻找如何执行此操作仅使用基于cookie的身份验证。

有人得到了他们可能会建议的指导吗?

预先感谢

到目前为止,我的代码是:

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
        {
            services.AddDbContext<ApplicationDbContext>(options =>
            {
                options.UseMySql(connectionString);
                options.EnableSensitiveDataLogging();
            });

            services.AddLogging(builder =>
            {
                builder.AddSeq();
            });

            services.ConfigureAuthentication();
            services.ConfigureRouting();
    }
}

ConfigureAuthentication.cs

  namespace MyCarparks.Configuration.Startup
  {
      public static partial class ConfigurationExtensions
      {
          public static IServiceCollection ConfigureAuthentication(this IServiceCollection services)
          {
              services.AddIdentity<MyCarparksUser, IdentityRole>(cfg =>
              {
                  //cfg.SignIn.RequireConfirmedEmail = true;
              })
              .AddDefaultUI()
              .AddDefaultTokenProviders()
              .AddEntityFrameworkStores<ApplicationDbContext>();

              services.ConfigureApplicationCookie(options =>
              {
                  options.LoginPath = $"/Identity/Account/Login";
                  options.LogoutPath = $"/Identity/Account/Logout";
                  options.AccessDeniedPath = $"/Identity/Account/AccessDenied";
              });

              services.AddMvc()
                  .SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
                  .AddRazorPagesOptions(options =>
              {
                    options.AllowAreas = true;
                    options.Conventions.AuthorizeAreaFolder("Identity", "/Account/Manage");
                    options.Conventions.AuthorizeAreaPage("Identity", "/Account/Logout");

                    options.Conventions.AuthorizeFolder("/Sites");
                });

            return services;
        }
    }
}

集成测试

PageTests.cs

namespace MyCarparks.Web.IntegrationTests
{
    public class PageTests : IClassFixture<CustomWebApplicationFactory<Startup>>
    {
        private readonly CustomWebApplicationFactory<Startup> factory;

        public PageTests(CustomWebApplicationFactory<Startup> webApplicationFactory)
        {
            factory = webApplicationFactory;
        }


    [Fact]
    public async Task SitesReturnsSuccessAndCorrectContentTypeAndSummary()
    {
        var siteId = Guid.NewGuid();
        var site = new Site { Id = siteId, Address = "Test site address" };
        var mockSite = new Mock<ISitesRepository>();
        mockSite.Setup(s => s.GetSiteById(It.IsAny<Guid>())).ReturnsAsync(site);

        // Arrange
        var client = factory.CreateClient();

        // Act
        var response = await client.GetAsync("http://localhost:44318/sites/sitedetails?siteId=" + siteId);

        // Assert
        response.EnsureSuccessStatusCode();

        response.Content.Headers.ContentType.ToString()
            .Should().Be("text/html; charset=utf-8");

        var responseString = await response.Content.ReadAsStringAsync();
        responseString.Should().Contain("Site Details - MyCarparks");
    }

    public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<Startup>
    {
        protected override void ConfigureWebHost(IWebHostBuilder builder)
        {
            builder.UseStartup<Startup>();
        }
    }
}

2 个答案:

答案 0 :(得分:0)

要实现您的要求,您可以尝试下面的代码,该代码使用身份验证cookie创建客户端。

    public class CustomWebApplicationFactory<TEntryPoint> : WebApplicationFactory<TEntryPoint> where TEntryPoint : class
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureServices(services =>
        {

        });
        base.ConfigureWebHost(builder);
    }
    public new HttpClient CreateClient()
    {
        var cookieContainer = new CookieContainer();
        var uri = new Uri("https://localhost:44344/Identity/Account/Login");
        var httpClientHandler = new HttpClientHandler
        {
            CookieContainer = cookieContainer
        };
        HttpClient httpClient = new HttpClient(httpClientHandler);
        var verificationToken = GetVerificationToken(httpClient, "https://localhost:44344/Identity/Account/Login");
        var contentToSend = new FormUrlEncodedContent(new[]
                {
                            new KeyValuePair<string, string>("Email", "test@outlook.com"),
                            new KeyValuePair<string, string>("Password", "1qaz@WSX"),
                            new KeyValuePair<string, string>("__RequestVerificationToken", verificationToken),
                        });
        var response = httpClient.PostAsync("https://localhost:44344/Identity/Account/Login", contentToSend).Result;
        var cookies = cookieContainer.GetCookies(new Uri("https://localhost:44344/Identity/Account/Login"));
        cookieContainer.Add(cookies);
        var client = new HttpClient(httpClientHandler);
        return client;
    }
    private string GetVerificationToken(HttpClient client, string url)
    {
        HttpResponseMessage response = client.GetAsync(url).Result;
        var verificationToken =response.Content.ReadAsStringAsync().Result;
        if (verificationToken != null && verificationToken.Length > 0)
        {
            verificationToken = verificationToken.Substring(verificationToken.IndexOf("__RequestVerificationToken"));
            verificationToken = verificationToken.Substring(verificationToken.IndexOf("value=\"") + 7);
            verificationToken = verificationToken.Substring(0, verificationToken.IndexOf("\""));
        }
        return verificationToken;
    }
}

答案 1 :(得分:0)

遵循Chris Pratt的建议,但对于Razor Pages .NET Core 3.1,我使用先前的请求针对登录端点(这也是另一个razor页面)进行身份验证,并从响应中获取cookie。然后,我将相同的cookie作为http请求的一部分添加,瞧,这是经过身份验证的请求。

这是一段使用HttpClient and AngleSharp(作为正式的Microsoft文档)来测试剃须刀页面的代码。因此,我重新使用它来从响应中获取Cookie。

private async Task<string> GetAuthenticationCookie()
{
    var formName = nameof(LoginModel.LoginForm); //this is the bounded model for the login page
    var dto =
        new Dictionary<string, string>
        {
            [$"{formName}.Username"] = "foo",
            [$"{formName}.Password"] = "bar",
        };

    var page = HttpClient.GetAsync("/login").GetAwaiter().GetResult();
    var content = HtmlHelpers.GetDocumentAsync(page).GetAwaiter().GetResult();

    //this is the AndleSharp
    var authResult =
        await HttpClient
            .SendAsync(
                (IHtmlFormElement)content.QuerySelector("form[id='login-form']"),
                (IHtmlButtonElement)content.QuerySelector("form[id='login-form']")
                    .QuerySelector("button"),
                dto);
    _ = authResult.Headers.TryGetValues("Set-Cookie", out var values);
    return values.First();
}

然后可以重复使用该值,并将其与新的http请求一起传递。

//in my test, the cookie is a given (from the given-when-then approach) pre-requirement
protected void Given()
{
    var cookie = GetAuthenticationCookie().GetAwaiter().GetResult();
    //The http client is a property that comes from the TestServer, when creating a client http for tests as usual. Only this time I set the auth cookie to it
    HttpClient.DefaultRequestHeaders.Add("Set-Cookie", cookie);
    var page = await HttpClient.GetAsync($"/admin/protectedPage");
    //this will be a 200 OK because it's an authenticated request with whatever claims and identity the /login page applied
}