我正在使用 FastEndpoints 和 cookie 身份验证来实现我的 REST API。为此,我有一个专门的登录端点:
await HttpContext.SignInAsync(await HttpContext.GetCookieAuthenticationSchemeAsync(), CreatePrincipal(), new AuthenticationProperties());
当从 Swagger 或基于 FastEndpoint 的
AppFixture
的集成测试调用时,效果很好。显然,客户端会自动从端点获取 cookie 并在必要时添加它们。
现在,我想创建一个注销。
与登录并行,我正在使用这种方法:
if (User.Identity?.IsAuthenticated == true)
{
await httpContext.SignOutAsync();
}
这在 Swagger 中有效,但在测试中无效。我想也许 cookie 没有正确发送,所以我添加了
var siteCookies = HttpContext.Request.Cookies;
foreach (var cookie in siteCookies)
{
httpContext.Response.Cookies.Delete(cookie.Key);
// OR
var opts = new CookieOptions();
opts.Expires = DateTimeOffset.MinValue;
httpContext.Response.Cookies.Append(cookie.Key, string.Empty, opts);
}
我可以看到正确的 cookie 被标记为已删除,但
AppFixture
不会识别它。所以我把它改为:
public class MyApiFixture : AppFixture<Program>
{
public void CreateMyClient()
{
var options = new ClientOptions
{
HandleCookies = false, // we handle cookies 🍪
};
options.AddHandlers(new CookieContainerHandler(CookieJar));
Client = CreateClient(options);
}
public CookieContainer CookieJar { get; } = new();
public void UpdateCookies(HttpResponseMessage response)
{
if (!response.Headers.Contains("Set-Cookie")) return;
var uri = new Uri("http://localhost");;
foreach (var cookieHeader in response.Headers.GetValues("Set-Cookie"))
{
CookieJar.SetCookies(uri, cookieHeader);
}
}
}
我将注销端点的结果放入
UpdateCookies()
,但很明显,cookie 是自动应用的(因此根据使用的方法被删除或过期)。尽管如此,最后一个有效的 cookie 仍会再次发送。
如何让 FastEndpoint 的
AppFixture
以与添加 Cookie 相同的方式应用已删除的 Cookie?
我尝试使用简化的端点来复制您的初始问题,如下所示:
sealed class LoginEndpoint : EndpointWithoutRequest<string>
{
public override void Configure()
{
Post("login");
AllowAnonymous();
}
public override async Task HandleAsync(CancellationToken c)
{
await CookieAuth.SignInAsync(_ => { });
await SendAsync("You are signed in!");
}
}
sealed class ProtectedEndpoint : EndpointWithoutRequest
{
public override void Configure()
{
Get("protected");
}
public override async Task HandleAsync(CancellationToken c)
{
await SendAsync("You are authenticated!");
}
}
sealed class LogoutEndpoint : EndpointWithoutRequest
{
public override void Configure()
{
Get("logout");
}
public override async Task HandleAsync(CancellationToken c)
{
await CookieAuth.SignOutAsync();
await SendAsync("You are signed out!");
}
}
和这样的启动配置:
var bld = WebApplication.CreateBuilder(args);
bld.Services
.AddAuthenticationCookie(validFor: TimeSpan.FromMinutes(10))
.AddAuthorization()
.AddFastEndpoints()
.SwaggerDocument(o => o.EnableJWTBearerAuth = false);
var app = bld.Build();
app.UseAuthentication()
.UseAuthorization()
.UseFastEndpoints()
.UseSwaggerGen();
app.Run();
然后我创建了一些像这样的有序测试来验证注销是否有效,并且测试
HttpClient
尊重注销。
public class CookieTests(App App) : TestBase<App>
{
[Fact, Priority(1)]
public async Task Unauthenticated_User_Receives_401_Unauthorized()
{
var (rsp, _) = await App.Client.GETAsync<ProtectedEndpoint, EmptyResponse>();
rsp.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
}
[Fact, Priority(2)]
public async Task Authenticated_User_Can_Access_Protected_Endpoint()
{
var (rsp1, res1) = await App.Client.POSTAsync<LoginEndpoint, string>();
rsp1.IsSuccessStatusCode.Should().BeTrue();
res1.Should().Be("You are signed in!");
var (rsp2, res2) = await App.Client.GETAsync<ProtectedEndpoint, string>();
rsp2.IsSuccessStatusCode.Should().BeTrue();
res2.Should().Be("You are authenticated!");
}
[Fact, Priority(3)]
public async Task Authenticated_User_Receives_401_After_Singing_Out()
{
var (rsp1, res1) = await App.Client.GETAsync<ProtectedEndpoint, string>();
rsp1.IsSuccessStatusCode.Should().BeTrue();
res1.Should().Be("You are authenticated!");
var (rsp2, res2) = await App.Client.GETAsync<LogoutEndpoint, string>();
rsp2.IsSuccessStatusCode.Should().BeTrue();
res2.Should().Be("You are signed out!");
var (rsp3, _) = await App.Client.GETAsync<ProtectedEndpoint, EmptyResponse>();
rsp3.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
}
}
所有测试均顺利通过。您可以通过下载重现项目来亲自查看从这里。
如果您希望我进一步查看此问题,请更新重现项目以突出显示问题并将其上传到某处,以便我进行调查。