50 lines
2.1 KiB
C#
50 lines
2.1 KiB
C#
using System.Net;
|
|
using System.Text.Json.Nodes;
|
|
|
|
namespace Monitor.Backend.Tests;
|
|
|
|
/// <summary>登录限流按来源地址计,会影响同一地址上的其他登录,所以用独立的后端实例。</summary>
|
|
public sealed class LoginRateLimitTests : IClassFixture<StrictLoginFixture>
|
|
{
|
|
private readonly StrictLoginFixture _backend;
|
|
|
|
public LoginRateLimitTests(StrictLoginFixture backend)
|
|
{
|
|
_backend = backend;
|
|
}
|
|
|
|
[Fact]
|
|
public async Task LoginAttempts_AreRateLimited_WithRetryAfter_AndASuccessfulLoginIsAlsoCounted()
|
|
{
|
|
using HttpClient client = _backend.NewClient();
|
|
HttpResponseMessage? limited = null;
|
|
int attempts = 0;
|
|
for (; attempts < 15 && limited is null; attempts++)
|
|
{
|
|
HttpResponseMessage response = await client.PostAsync("/api/admin/auth/login", BackendFixture.Json(new JsonObject { ["username"] = "admin", ["password"] = "wrong" }));
|
|
if (response.StatusCode == HttpStatusCode.TooManyRequests)
|
|
{
|
|
limited = response;
|
|
}
|
|
else
|
|
{
|
|
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
|
|
response.Dispose();
|
|
}
|
|
}
|
|
|
|
Assert.NotNull(limited);
|
|
using (limited)
|
|
{
|
|
// 前一次登录(创建管理员令牌)也占了一次配额:第 10 次之内的都是 401
|
|
Assert.InRange(attempts, 9, 10);
|
|
Assert.Equal("too_many_requests", JsonNode.Parse(await limited.Content.ReadAsStringAsync())!["code"]!.GetValue<string>());
|
|
Assert.True(limited.Headers.RetryAfter!.Delta!.Value.TotalSeconds is >= 1 and <= 60);
|
|
}
|
|
|
|
// 被限流的来源连正确的密码也不行:限流发生在校验密码之前,不能被用来猜密码
|
|
using HttpResponseMessage correct = await client.PostAsync("/api/admin/auth/login", BackendFixture.Json(new JsonObject { ["username"] = BackendFixture.AdminUser, ["password"] = BackendFixture.AdminPassword }));
|
|
Assert.Equal(HttpStatusCode.TooManyRequests, correct.StatusCode);
|
|
}
|
|
}
|