前言
因为正在开发的系统是内部类型,希望只是简单建立server-side的验证码机制就好,所以就不考虑使用Google reCaptcha。网路上.NET Core文章有点乱,版本又杂(微软根本版本之鬼...),顺手整理一下
环境
.NET 版本: .NET Core 5
后端
.NET Core的所有物件皆须已注入的方式使用,而我们这次要将验证码储存在Session
中。
需要先在startup.cs
中增加以下项目:
app.UseSession()
,告诉.NET Core需要使用Sessionservices.AddDistributedMemoryCache()
,注入分散式记忆体快取物件,Session会用到services.AddSession()
,注入Seesionservices.AddHttpContextAccessor()
,后面会说明为什么有这行。startup.cs
public void ConfigureServices(IServiceCollection services){ // 注入分散式记忆体快取 services.AddDistributedMemoryCache(); // 注入Session services.AddSession(options => { options.IdleTimeout = TimeSpan.FromMinutes(10);//You can set Time }); // 注入 HttpContextAccessor services.AddHttpContextAccessor(); services.AddControllers(); services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "TestValidation", Version = "v1" }); });}public void Configure(IApplicationBuilder app, IWebHostEnvironment env){ if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseSwagger(); app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "TestValidation v1")); } app.UseHttpsRedirection(); app.UseRouting(); // 使用Session app.UseSession(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); });}
接下来我们建立一个.NET Core 类别库专案,并建立CodeValidation.cs
,interface
、class
放在同一个档案,方便使用。
这里要注意一点,.NET Core很多东西是要自己额外安装:
IHttpContextAccessor
需要从nuget
安装套件: Microsoft.AspNetCore.Http
public interface ICodeValidator{ /// <summary> /// 验证 /// </summary> /// <param name="code"></param> /// <returns></returns> bool Validate(string code); /// <summary> /// 产生验证码 /// </summary> /// <returns></returns> string Generate();}public class CodeValidator : ICodeValidator{ private const string KEY = "ValidationCode"; private HttpContext _httpContext { get; set; } // 注入 IHttpContextAccessor ,因为我们要使用HttpContext取得Session public CodeValidator(IHttpContextAccessor httpContextAccessor) { _httpContext = httpContextAccessor.HttpContext; } public string Generate() { string code = CreateRandomWord(5); // session只能储存byte[],将字串转为byte[] byte[] codeBytes = Encoding.ASCII.GetBytes(code); _httpContext.Session.Set(KEY, codeBytes); return code; } public bool Validate(string code) { bool isOk = false; byte[] codeBytes = null; if(_httpContext.Session.TryGetValue(KEY,out codeBytes)) { // 从Session取出来的byte[] 转成字串 string serverCode = Encoding.ASCII.GetString(codeBytes); // 忽略大小写比对 if (serverCode.Equals(code, StringComparison.InvariantCultureIgnoreCase)) { isOk = true; } } // 无论成功失败,都清除Session。(依情境,非必要) _httpContext.Session.Remove(KEY); return isOk; } /// <summary> /// 产生随机字串 /// </summary> /// <param name="length"></param> /// <returns></returns> private string CreateRandomWord(int length = 5) { string code = ""; var letters = "ABCDEFGHJKMPQRSTUVWXYZ23456789abcdefghjkmpqrstuvwxyz".ToArray(); Random r = new Random(); for (int i = 0; i < length; i++) { int index = r.Next(0, letters.Length); code += letters[index]; } return code; }}
这就是为什么我们会注入HttpContextAccessor
的原因,因为实际专案基本上都是分层架构,所以要在不同层级取得Session,就必须注入此物件,而不是从Controller传入HttpContext。
接下来我们需要将CodeValidator
注入给Controller使用,所以startup.cs
要增加程式
public void ConfigureServices(IServiceCollection services){ // 注入分散式记忆体快取 services.AddDistributedMemoryCache(); // 注入Session services.AddSession(options => { options.IdleTimeout = TimeSpan.FromMinutes(10);//You can set Time }); // 注入验证物件 services.AddScoped(typeof(ICodeValidator), typeof(CodeValidator)); // 注入 HttpContextAccessor services.AddHttpContextAccessor(); services.AddControllers(); services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "TestValidation", Version = "v1" }); });}
建立CodeController
製作验证码API。
[Route("api/[controller]")][ApiController]public class CodeController : ControllerBase{ private ICodeValidator _codeValidator { get; set; } public CodeController(ICodeValidator codeValidator) { _codeValidator = codeValidator; } [HttpGet] public ActionResult<string> Generate() { string code = _codeValidator.Generate(); return Ok(code); } [HttpGet("{code}")] public ActionResult Validate(string code) { bool isOk = _codeValidator.Validate(code); return isOk ? Ok() : BadRequest(); }}
测试结果
取得验证码,反回CDXPM
输入验证码验证,返回200 OK
输入错误测试
从新取得验证码 psBw9
输入错误的验证码12345
,返回400 BadRequest