This commit is contained in:
biss committed 2026-09-12 10:46:36 +08:00
1 parent 9cc95215ff
commit 69059b2e64
5 files changed
+48 -27

No files matched your search

@@ -5,7 +5,7 @@
<Nullable>enable</Nullable>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<PackageId>CaptchaKit.AspNetCore</PackageId>
<Version>0.1.0-preview.2</Version>
<Version>0.1.0-preview.3</Version>
<Authors>CaptchaKit Contributors</Authors>
<Description>ASP.NET Core and distributed-cache integration for CaptchaKit.</Description>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
+44 -24
View File
@@ -13,7 +13,7 @@ public sealed class CaptchaKitOptions
public TimeSpan Lifetime { get; set; } = TimeSpan.FromMinutes(2);
public string CacheKeyPrefix { get; set; } = "captchakit:";
public bool RequireBrowserProof { get; set; }
public bool EnableOneClickChallenge { get; set; } = true;
public bool EnableMathChallenge { get; set; } = true;
public bool EnableSelectionChallenge { get; set; } = true;
}
@@ -85,33 +85,41 @@ public sealed class DistributedCaptchaChallengeStore(
public sealed class RandomCaptchaGenerator(CaptchaKitOptions options) : ICaptchaGenerator
{
private readonly SkiaTextCaptchaGenerator _text = new();
private const string SelectionAlphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
public GeneratedCaptcha Generate(CaptchaGenerationRequest request)
{
var roll = RandomNumberGenerator.GetInt32(100);
if (options.EnableOneClickChallenge && roll < 15)
if (options.EnableMathChallenge && roll < 25)
{
return new GeneratedCaptcha(
new CaptchaChallenge(request.Id, CaptchaChallengeType.OneClick, request.ExpiresAt,
"请点击确认按钮继续登录"),
"CONFIRM");
var left = RandomNumberGenerator.GetInt32(2, 10);
var right = RandomNumberGenerator.GetInt32(1, 10);
var isAddition = RandomNumberGenerator.GetInt32(2) == 0;
if (!isAddition && right > left)
(left, right) = (right, left);
var expression = $"{left} {(isAddition ? '+' : '-')} {right} = ?";
var answer = (isAddition ? left + right : left - right).ToString();
return _text.CreateVisualChallenge(
request, expression, answer, CaptchaChallengeType.MathImage, "请计算图片中的算式");
}
if (options.EnableSelectionChallenge && roll < 45)
if (options.EnableSelectionChallenge && roll < 55)
{
var correct = RandomNumberGenerator.GetInt32(2, 10).ToString();
var candidates = Enumerable.Range(1, 9)
.Where(x => x.ToString() != correct)
var target = SelectionAlphabet[RandomNumberGenerator.GetInt32(SelectionAlphabet.Length)].ToString();
var candidates = SelectionAlphabet
.Select(x => x.ToString())
.Where(x => x != target)
.OrderBy(_ => RandomNumberGenerator.GetInt32(int.MaxValue))
.Take(3)
.Append(int.Parse(correct))
.Append(target)
.OrderBy(_ => RandomNumberGenerator.GetInt32(int.MaxValue))
.Select(x => new CaptchaChoice($"choice-{x}", x.ToString()))
.Select(x => new CaptchaChoice($"choice-{x}", x))
.ToList();
var answer = candidates.Single(x => x.Label == correct).Id;
var answer = candidates.Single(x => x.Label == target).Id;
var visual = _text.CreateVisualChallenge(
request, target, answer, CaptchaChallengeType.Selection, "请选择与图片中字符相同的一项");
return new GeneratedCaptcha(
new CaptchaChallenge(request.Id, CaptchaChallengeType.Selection, request.ExpiresAt,
$"请选择数字 {correct}", candidates),
answer);
visual.Challenge with { Choices = candidates },
visual.Answer);
}
return _text.Generate(request);
}
@@ -125,27 +133,39 @@ public sealed class SkiaTextCaptchaGenerator : ICaptchaGenerator
{
var answer = string.Concat(Enumerable.Range(0, request.CodeLength)
.Select(_ => Alphabet[RandomNumberGenerator.GetInt32(Alphabet.Length)]));
using var bitmap = new SKBitmap(160, 54);
return CreateVisualChallenge(request, answer, answer, CaptchaChallengeType.TextImage, "请输入图中的字符");
}
public GeneratedCaptcha CreateVisualChallenge(
CaptchaGenerationRequest request,
string displayText,
string answer,
CaptchaChallengeType type,
string prompt)
{
var width = Math.Max(160, 20 + displayText.Length * 27);
using var bitmap = new SKBitmap(width, 54);
using var canvas = new SKCanvas(bitmap);
canvas.Clear(new SKColor(244, 248, 252));
DrawNoise(canvas, 160, 54, 13);
DrawNoise(canvas, width, 54, 16);
using var paint = new SKPaint { IsAntialias = true };
using var font = new SKFont(SKTypeface.FromFamilyName("Arial", SKFontStyle.Bold), 30);
for (var index = 0; index < answer.Length; index++)
var spacing = Math.Max(22, (width - 24) / displayText.Length);
for (var index = 0; index < displayText.Length; index++)
{
var x = 14 + index * 29 + RandomNumberGenerator.GetInt32(-2, 3);
var x = 12 + index * spacing + RandomNumberGenerator.GetInt32(-2, 3);
var y = 38 + RandomNumberGenerator.GetInt32(-4, 5);
paint.Color = RandomColor(35, 145);
canvas.Save();
canvas.RotateDegrees(RandomNumberGenerator.GetInt32(-20, 21), x, y);
canvas.DrawText(answer[index].ToString(), x, y, SKTextAlign.Left, font, paint);
canvas.DrawText(displayText[index].ToString(), x, y, SKTextAlign.Left, font, paint);
canvas.Restore();
}
DrawNoise(canvas, 160, 54, 8);
DrawNoise(canvas, width, 54, 10);
using var image = SKImage.FromBitmap(bitmap);
using var data = image.Encode(SKEncodedImageFormat.Png, 100);
var challenge = new CaptchaChallenge(request.Id, CaptchaChallengeType.TextImage, request.ExpiresAt,
"请输入图中的字符", null, "image/png", data.ToArray());
var challenge = new CaptchaChallenge(request.Id, type, request.ExpiresAt,
prompt, null, "image/png", data.ToArray());
return new GeneratedCaptcha(challenge, answer);
}