1- # Edi.Captcha.AspNetCore
1+ # Edi.Captcha.AspNetCore
22The Captcha module used in my blog
33
44[ ![ .NET] ( https://github.com/EdiWang/Edi.Captcha.AspNetCore/actions/workflows/dotnet.yml/badge.svg )] ( https://github.com/EdiWang/Edi.Captcha.AspNetCore/actions/workflows/dotnet.yml )
@@ -8,9 +8,7 @@ The Captcha module used in my blog
88[ main-nuget ] : https://www.nuget.org/packages/Edi.Captcha/
99[ main-nuget-badge ] : https://img.shields.io/nuget/v/Edi.Captcha.svg?style=flat-square&label=nuget
1010
11- ## Usage
12-
13- ### 0. Install from NuGet
11+ ## Install
1412
1513NuGet Package Manager
1614```
@@ -23,6 +21,8 @@ or .NET CLI
2321dotnet add package Edi.Captcha
2422```
2523
24+ ## Session-Based Captcha (Traditional Approach)
25+
2626### 1. Register in DI
2727
2828``` csharp
@@ -186,18 +186,184 @@ public async Task<IActionResult> SomeAction(YourModelWithCaptchaCode model)
186186 }
187187```
188188
189- Refer to https://edi.wang/post/2018/10/13/generate-captcha-code-aspnet-core
190189
191- ## 免责申明
190+ ## Stateless Captcha (Recommended for Scalable Applications)
191+
192+ ** Advantages of Stateless Captcha:**
193+ - ✅ Works in clustered/load-balanced environments
194+ - ✅ No server-side session storage required
195+ - ✅ Built-in expiration through encryption
196+ - ✅ Secure token-based validation
197+ - ✅ Better scalability
198+ - ✅ Single API call for both token and image
199+
200+ ### 1. Register in DI
201+
202+ ``` csharp
203+ services .AddStatelessCaptcha ();
204+ ```
205+
206+ or with custom options:
207+
208+ ``` csharp
209+ services .AddStatelessCaptcha (options =>
210+ {
211+ options .Letters = " 2346789ABCDGHKMNPRUVWXYZ" ;
212+ options .CodeLength = 4 ;
213+ options .TokenExpiration = TimeSpan .FromMinutes (5 );
214+ });
215+ ```
216+
217+ ### 2. Create Model with Token Support
192218
193- 此项目(Edi.Captcha.AspNetCore)及其配套组件均为免费开源的产品,仅用于学习交流,并且不直接向中国提供服务,中国访客请于下载后立即删除。
219+ ``` csharp
220+ public class StatelessHomeModel
221+ {
222+ [Required ]
223+ [StringLength (4 )]
224+ public string CaptchaCode { get ; set ; }
225+
226+ public string CaptchaToken { get ; set ; }
227+ }
228+ ```
194229
195- 任何中国境内的组织及个人不得使用此项目(Edi.Captcha.AspNetCore)及其配套组件构建任何形式的面向中国境内访客的网站或服务。
230+ ### 3. Example Controller
196231
197- 不可用于任何违反中华人民共和国(含台湾省)或使用者所在地区法律法规的用途。
232+ ``` csharp
233+ using Edi .Captcha .SampleApp .Models ;
234+ using Microsoft .AspNetCore .Mvc ;
235+ using System ;
236+ using System .Diagnostics ;
198237
199- 因为作者即本人仅完成代码的开发和开源活动(开源即任何人都可以下载使用),从未参与访客的任何运营和盈利活动。
238+ namespace Edi . Captcha . SampleApp . Controllers ;
200239
201- 且不知晓访客后续将程序源代码用于何种用途,故访客使用过程中所带来的任何法律责任即由访客自己承担。
240+ public class StatelessController (IStatelessCaptcha captcha ) : Controller
241+ {
242+ public IActionResult Index ()
243+ {
244+ return View (new StatelessHomeModel ());
245+ }
202246
203- [ 《开源软件有漏洞,作者需要负责吗?是的!》] ( https://go.edi.wang/aka/os251 )
247+ [HttpPost ]
248+ public IActionResult Index (StatelessHomeModel model )
249+ {
250+ if (ModelState .IsValid )
251+ {
252+ bool isValidCaptcha = captcha .Validate (model .CaptchaCode , model .CaptchaToken );
253+ return Content (isValidCaptcha ? " Success - Stateless captcha validated!" : " Invalid captcha code" );
254+ }
255+
256+ return BadRequest ();
257+ }
258+
259+ [Route (" get-stateless-captcha" )]
260+ public IActionResult GetStatelessCaptcha ()
261+ {
262+ var result = captcha .GenerateCaptcha (100 , 36 );
263+
264+ return Json (new {
265+ token = result .Token ,
266+ imageBase64 = Convert .ToBase64String (result .ImageBytes )
267+ });
268+ }
269+
270+ [ResponseCache (Duration = 0 , Location = ResponseCacheLocation .None , NoStore = true )]
271+ public IActionResult Error ()
272+ {
273+ return View (new ErrorViewModel { RequestId = Activity .Current ? .Id ?? HttpContext .TraceIdentifier });
274+ }
275+ }
276+ ```
277+
278+ ### 4. Example View
279+
280+ ``` razor
281+ @model StatelessHomeModel
282+ @{
283+ ViewData["Title"] = "Stateless Captcha Example";
284+ }
285+
286+ <div class="text-center">
287+ <h1 class="display-4">Stateless Captcha Example</h1>
288+ <p>This example shows how to use stateless captcha that works in clustered environments.</p>
289+ </div>
290+
291+ <div class="row">
292+ <div class="col-md-6 offset-md-3">
293+ <div class="card">
294+ <div class="card-header">
295+ <h5>Stateless Captcha Form</h5>
296+ </div>
297+ <div class="card-body">
298+ <form asp-action="Index" method="post" id="stateless-form">
299+ <div class="form-group mb-3">
300+ <label>Captcha Image:</label>
301+ <div class="d-flex align-items-center">
302+ <img id="captcha-image" src="" alt="Captcha" class="me-2" style="border: 1px solid #ccc;" />
303+ <button type="button" class="btn btn-sm btn-outline-secondary" onclick="refreshCaptcha()">
304+ 🔄 Refresh
305+ </button>
306+ </div>
307+ <small class="form-text text-muted">Click refresh to get a new captcha</small>
308+ </div>
309+
310+ <div class="form-group mb-3">
311+ <label asp-for="CaptchaCode">Enter Captcha Code:</label>
312+ <input asp-for="CaptchaCode" class="form-control" placeholder="Enter the code from image" autocomplete="off" />
313+ <span asp-validation-for="CaptchaCode" class="text-danger"></span>
314+ </div>
315+
316+ <input type="hidden" asp-for="CaptchaToken" id="captcha-token" />
317+
318+ <div class="form-group">
319+ <button type="submit" class="btn btn-primary">Submit</button>
320+ <a asp-controller="Home" asp-action="Index" class="btn btn-secondary">Session-based Example</a>
321+ </div>
322+ </form>
323+
324+ <div class="mt-4">
325+ <h6>Advantages of Stateless Captcha:</h6>
326+ <ul class="small">
327+ <li>✅ Works in clustered/load-balanced environments</li>
328+ <li>✅ No server-side session storage required</li>
329+ <li>✅ Built-in expiration through encryption</li>
330+ <li>✅ Secure token-based validation</li>
331+ <li>✅ Better scalability</li>
332+ <li>✅ Single API call for both token and image</li>
333+ </ul>
334+ </div>
335+ </div>
336+ </div>
337+ </div>
338+ </div>
339+
340+ <script>
341+ async function refreshCaptcha() {
342+ try {
343+ const response = await fetch('/get-stateless-captcha');
344+ const data = await response.json();
345+
346+ // Set the token for validation
347+ document.getElementById('captcha-token').value = data.token;
348+
349+ // Set the image source using base64 data
350+ document.getElementById('captcha-image').src = `data:image/png;base64,${data.imageBase64}`;
351+
352+ // Clear the input
353+ document.getElementById('CaptchaCode').value = '';
354+ } catch (error) {
355+ console.error('Error refreshing captcha:', error);
356+ alert('Failed to load captcha. Please try again.');
357+ }
358+ }
359+
360+ // Initialize captcha on page load
361+ document.addEventListener('DOMContentLoaded', function() {
362+ refreshCaptcha();
363+ });
364+ </script>
365+
366+ @section Scripts {
367+ @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
368+ }
369+ ```
0 commit comments