روشی برای محدود کردن API ها که هر درخواست با یک Key جدید و منحصر به فرد قابل فراخوانی باشد ( Time-based One-time Password )
نویسنده: زمانی فرهاد
تاریخ: ۱۳۹۹/۰۶/۱۱ ۰:۳۵
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
{"DateTimeUtcTicks":637345812872371593} g/ibfD2M3uE1RhEGxt8/jKcmpW2zhU1kKjVRC7CyrHiCHkdaAmLOwziBATFnHyJ3
var dateTimeNow = DateTime.UtcNow; var expireTimeFrom = dateTimeNow.AddMinutes(-1).Ticks; var expireTimeTo = dateTimeNow.Ticks; string clientSecret = httpContext.Request.Headers["ClientSecret"].ToString(); string decryptedRequestHeader = AesProvider.Decrypt(requestKeyHeader, clientSecret); var requestKeyData = System.Text.Json.JsonSerializer.Deserialize<ApiLimiterDto>(decryptedRequestHeader); if (requestKeyData.DateTimeUtcTicks >= expireTimeFrom && requestKeyData.DateTimeUtcTicks <= expireTimeTo)
public class ApiLimiterDto
{
public long DateTimeUtcTicks { get; set; }
} public class ApiLimiterMiddleware
{
private readonly RequestDelegate _next;
private readonly IDistributedCache _cache;
public ApiLimiterMiddleware(RequestDelegate next, IDistributedCache cache)
{
_next = next;
_cache = cache;
}
private const string requestKey = "Request-Key";
private const string clientSecretHeader = "ClientSecret";
public async Task InvokeAsync(HttpContext httpContext)
{
if (!httpContext.Request.Headers.ContainsKey(requestKey) || !httpContext.Request.Headers.ContainsKey(clientSecretHeader))
{
await WriteToReponseAsync();
return;
}
var requestKeyHeader = httpContext.Request.Headers[requestKey].ToString();
string clientSecret = httpContext.Request.Headers[clientSecretHeader].ToString();
if (string.IsNullOrEmpty(requestKeyHeader) || string.IsNullOrEmpty(clientSecret))
{
await WriteToReponseAsync();
return;
}
//اگر کلید در کش موجود بود یعنی کاربر از کلید تکراری استفاده کرده است
if (_cache.GetString(requestKeyHeader) != null)
{
await WriteToReponseAsync();
return;
}
var dateTimeNow = DateTime.UtcNow;
var expireTimeFrom = dateTimeNow.AddMinutes(-1).Ticks;
var expireTimeTo = dateTimeNow.Ticks;
string decryptedRequestHeader = AesProvider.Decrypt(requestKeyHeader, clientSecret);
var requestKeyData = System.Text.Json.JsonSerializer.Deserialize<ApiLimiterDto>(decryptedRequestHeader);
if (requestKeyData.DateTimeUtcTicks >= expireTimeFrom && requestKeyData.DateTimeUtcTicks <= expireTimeTo)
{
//ذخیره کلید درخواست در کش برای جلوگیری از استفاده مجدد از کلید
await _cache.SetAsync(requestKeyHeader, Encoding.UTF8.GetBytes("KeyExist"), new DistributedCacheEntryOptions
{
AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(2)
});
await _next(httpContext);
}
else
{
await WriteToReponseAsync();
return;
}
async Task WriteToReponseAsync()
{
httpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
await httpContext.Response.WriteAsync("Forbidden: You don't have permission to call this api");
}
}
} public static class AesProvider
{
private static byte[] GetIV()
{
//این کد ثابتی است که باید در سمت سرور و موبایل موجود باشد
return encoding.GetBytes("ThisIsASecretKey");
}
public static string Encrypt(string plainText, string key)
{
try
{
var aes = GetRijndael(key);
ICryptoTransform AESEncrypt = aes.CreateEncryptor(aes.Key, aes.IV);
byte[] buffer = encoding.GetBytes(plainText);
string encryptedText = Convert.ToBase64String(AESEncrypt.TransformFinalBlock(buffer, 0, buffer.Length));
return encryptedText;
}
catch (Exception)
{
throw new Exception("an error occurred when encrypting");
}
}
private static RijndaelManaged GetRijndael(string key)
{
return new RijndaelManaged
{
KeySize = 128,
BlockSize = 128,
Padding = PaddingMode.PKCS7,
Mode = CipherMode.CBC,
Key = encoding.GetBytes(key),
IV = GetIV()
};
}
private static readonly Encoding encoding = Encoding.UTF8;
public static string Decrypt(string plainText, string key)
{
try
{
var aes = GetRijndael(key);
ICryptoTransform AESDecrypt = aes.CreateDecryptor(aes.Key, aes.IV);
byte[] buffer = Convert.FromBase64String(plainText);
return encoding.GetString(AESDecrypt.TransformFinalBlock(buffer, 0, buffer.Length));
}
catch (Exception)
{
throw new Exception("an error occurred when decrypting");
}
}
} public class ApiLimiterPipeline
{
public void Configure(IApplicationBuilder app)
{
app.UseMiddleware<ApiLimiterMiddleware>();
}
} نحوه استفاده از میان افزار برای یک اکشن خاص :
[Route("api/[controller]/[action]")]
[ApiController]
public class ValuesController : ControllerBase
{
[MiddlewareFilter(typeof(ApiLimiterPipeline))]
public async Task<IActionResult> Get()
{
return Ok("Hi");
}
}