iT邦幫忙

0

.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

參考


尚未有邦友留言

立即登入留言