登录验证码接入

This commit is contained in:
biss committed 2026-09-11 22:05:07 +08:00
1 parent ae075bda6d
commit f489a3553c
15 files changed
+471 -19

No files matched your search

@@ -20,6 +20,7 @@ public sealed class AuthController(
UserManager<ApplicationUser> userManager,
IAuthSessionService authSessionService,
ITwoFactorLoginTicketService twoFactorTickets,
ILoginCaptchaService loginCaptcha,
IAppCache cache) : ControllerBase
{
[AllowAnonymous]
@@ -135,6 +136,35 @@ public sealed class AuthController(
});
}
[AllowAnonymous]
[EnableRateLimiting("public-auth")]
[HttpPost("login/captcha")]
public async Task<ActionResult<LoginCaptchaChallenge>> CreateLoginCaptcha(
LoginCaptchaCreateRequest request,
CancellationToken cancellationToken) =>
Ok(await loginCaptcha.CreateAsync(request.DeviceId, HttpContext, cancellationToken));
[AllowAnonymous]
[EnableRateLimiting("public-auth")]
[HttpPost("login/captcha/verify")]
public async Task<ActionResult<LoginCaptchaProofResponse>> VerifyLoginCaptcha(
LoginCaptchaVerifyRequest request,
CancellationToken cancellationToken)
{
var proof = await loginCaptcha.VerifyAsync(
new LoginCaptchaVerification(
request.ChallengeId,
request.DeviceId,
request.Text,
request.SliderX,
request.Clicks),
HttpContext,
cancellationToken);
return proof is null
? Unauthorized(LoginCaptchaProblem())
: Ok(new LoginCaptchaProofResponse(proof));
}
[AllowAnonymous]
[EnableRateLimiting("public-auth")]
[HttpPost("login")]
@@ -142,6 +172,12 @@ public sealed class AuthController(
LoginRequest request,
CancellationToken cancellationToken)
{
if (!await loginCaptcha.ConsumeProofAsync(
request.CaptchaTicket,
request.DeviceId,
HttpContext,
cancellationToken))
return Unauthorized(LoginCaptchaProblem());
var user = await userManager.FindByNameAsync(request.UserName);
if (user is null || !user.IsEnabled)
{
@@ -290,6 +326,12 @@ public sealed class AuthController(
Status = StatusCodes.Status401Unauthorized
};
internal static ProblemDetails LoginCaptchaProblem() => new()
{
Title = "安全验证失败", Detail = "验证码无效、已过期或与当前设备不匹配,请重新验证。",
Status = StatusCodes.Status401Unauthorized
};
internal static LoginResponse CreateLoginResponse(AuthSessionResult session) =>
new(
session.AccessToken,
@@ -308,8 +350,21 @@ public sealed class AuthController(
public sealed record LoginRequest(
[Required, MaxLength(100)] string UserName,
[Required, MaxLength(100)] string Password,
[Required, MinLength(32), MaxLength(128)] string CaptchaTicket,
[MaxLength(128)] string? DeviceId = null,
bool IsNativeApp = false);
public sealed record LoginCaptchaCreateRequest([MaxLength(128)] string? DeviceId = null);
public sealed record LoginCaptchaVerifyRequest(
[Required, MinLength(32), MaxLength(64)] string ChallengeId,
[MaxLength(128)] string? DeviceId,
[MaxLength(16)] string? Text,
[Range(0, 240)] int? SliderX,
[MaxLength(2)] IReadOnlyList<CaptchaPoint>? Clicks);
public sealed record LoginCaptchaProofResponse(string CaptchaTicket);
public sealed record TotpLoginRequest(
[Required, MinLength(20), MaxLength(2048)] string TwoFactorTicket,
[Required, MinLength(6), MaxLength(12)] string Code);