.NET Core API 产生 server-side 验证码

前言

因为正在开发的系统是内部类型,希望只是简单建立server-side的验证码机制就好,所以就不考虑使用Google reCaptcha。网路上.NET Core文章有点乱,版本又杂(微软根本版本之鬼...),顺手整理一下

环境

.NET 版本: .NET Core 5

後端

.NET Core的所有物件皆须已注入的方式使用,而我们这次要将验证码储存在Session 中。

需要先在startup.cs 中增加以下项目:

  • app.UseSession() ,告诉.NET Core需要使用Session
  • services.AddDistributedMemoryCache() ,注入分散式记忆体快取物件,Session会用到
  • services.AddSession() ,注入Seesion
  • services.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.csinterfaceclass 放在同一个档案,方便使用。

这里要注意一点,.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

https://ithelp.ithome.com.tw/upload/images/20210619/20138428F87AKxz1qY.png

输入验证码验证,返回200 OK

https://ithelp.ithome.com.tw/upload/images/20210619/20138428ZDgDGBdSPn.png

输入错误测试

从新取得验证码 psBw9

https://ithelp.ithome.com.tw/upload/images/20210619/20138428St1x7kldNk.png

输入错误的验证码12345,返回400 BadRequest

https://ithelp.ithome.com.tw/upload/images/20210619/20138428na87hVQvFr.png

参考


<<:  Day 17 (Ps)

>>:  javascript 防疫自学日记 day 1

iPhone 永久删除的照片如何复原?

对於大多数 iPhone 用户来说,管理 iPhone 存储空间往往是一件令人头疼的事情。存储空间太...

Day12 - audio tag 帮我设定背景音乐

透过 audio tag 设定背景音乐 class BGM { constructor() { th...

[NestJS 带你飞!] DAY04 - Controller (下)

主体资料 (Body) 在传输资料时经常会使用到主体资料,比如说:POST、PUT、PATCH等操作...

Day 27 - State Monad II

在上一章,我们提到了如何用一般方法实作 PRNG 乱数生成器,本章将介绍 State Monad 以...

Day 26:扩充性

谈到扩充性,JUCE 以 Modules 为基础,开发者可提供自制 Module,供其他人使用。如下...