公開API的安全,其實更重要。html
做爲一個Dotnet Core的老司機,寫API時,能兼顧到API的安全,這是一種優雅。前端
一般,咱們會用認證來保證API的安全,無敵的Authorize
能解決咱們不少的問題。git
可是,總有一些場合,咱們沒辦法用Authorize
,而只能用匿名或不加驗證的方式來訪問。比方電商中查詢SKU的列表並在前端展現,一般這個無關用戶和權限,在完成API的時候,咱們也不會加入認證Authorize
。github
這種狀況下,若是直接寫,不加入安全級別,這樣的體系結構是有可能成爲可供利用的安全漏洞的。web
Dotnet Core框架已經提供了一些常見漏洞的解決方法,包括:json
等等。c#
可是,咱們還須要更進一步,還須要照顧到如下常見的攻擊:api
這部份內容,須要咱們本身實現。固然,這部份內容的實現,也能夠從Web Server上進行設置。安全
本文討論的,是代碼的實現。微信
爲了防止不提供原網址的轉載,特在這裏加上原文連接:http://www.javashuo.com/article/p-zdgwvixb-np.html
今天偷個懶,不講原理,以分享代碼爲主。
經過限制客戶端在指定的時間範圍內的請求數量,防止惡意bot攻擊。
代碼中,我創建了一個基於IP的請求限制過濾器。
注意:有多個客戶端位於同一個IP地址的狀況,這個狀況在這個代碼中沒有考慮。若是您但願實現這一點,能夠把幾種方式結合起來使用。
如下是代碼:
[AttributeUsage(AttributeTargets.Method)]
public class RequestLimitAttribute : ActionFilterAttribute
{
public string Name { get; }
public int NoOfRequest { get; set; }
public int Seconds { get; set; }
private static MemoryCache Cache { get; } = new MemoryCache(new MemoryCacheOptions());
public RequestLimitAttribute(string name, int noOfRequest = 5, int seconds = 10)
{
Name = name;
NoOfRequest = noOfRequest;
Seconds = seconds;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
var ipAddress = context.HttpContext.Request.HttpContext.Connection.RemoteIpAddress;
var memoryCacheKey = $"{Name}-{ipAddress}";
Cache.TryGetValue(memoryCacheKey, out int prevReqCount);
if (prevReqCount >= NoOfRequest)
{
context.Result = new ContentResult
{
Content = $"Request limit is exceeded. Try again in {Seconds} seconds.",
};
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.TooManyRequests;
}
else
{
var cacheEntryOptions = new MemoryCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromSeconds(Seconds));
Cache.Set(memoryCacheKey, (prevReqCount + 1), cacheEntryOptions);
}
}
}
使用時,只要在須要的API前加屬性便可:
[HttpGet]
[RequestLimit("DataGet", 5, 30)]
public IEnumerable<WeatherForecast> Get()
{
...
}
對API請求的請求引用頭進行檢查,能夠防止API濫用,以及跨站點請求僞造(CSRF)攻擊。
一樣,也是採用自定義屬性的方式。
public class ValidateReferrerAttribute : ActionFilterAttribute
{
private IConfiguration _configuration;
public override void OnActionExecuting(ActionExecutingContext context)
{
_configuration = (IConfiguration)context.HttpContext.RequestServices.GetService(typeof(IConfiguration));
base.OnActionExecuting(context);
if (!IsValidRequest(context.HttpContext.Request))
{
context.Result = new ContentResult
{
Content = $"Invalid referer header",
};
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.ExpectationFailed;
}
}
private bool IsValidRequest(HttpRequest request)
{
string referrerURL = "";
if (request.Headers.ContainsKey("Referer"))
{
referrerURL = request.Headers["Referer"];
}
if (string.IsNullOrWhiteSpace(referrerURL)) return true;
var allowedUrls = _configuration.GetSection("CorsOrigin").Get<string[]>()?.Select(url => new Uri(url).Authority).ToList();
bool isValidClient = allowedUrls.Contains(new Uri(referrerURL).Authority);
return isValidClient;
}
}
這裏我用了一個配置,在appsetting.json
中:
{
"CorsOrigin": ["https://test.com", "http://test1.cn:8080"]
}
CorsOrigin
參數中加入容許引用的來源域名:端口列表。
使用時,在須要的API前加屬性:
[HttpGet]
[ValidateReferrer]
public IEnumerable<WeatherForecast> Get()
{
...
}
DDOS攻擊在網上很常見,這種攻擊簡單有效,可讓一個網站瞬間開始並長時間沒法響應。一般來講,網站能夠經過多種節流方法來避免這種狀況。
下面咱們換一種方式,用中間件MiddleWare
來限制特定客戶端IP的請求數量。
public class DosAttackMiddleware
{
private static Dictionary<string, short> _IpAdresses = new Dictionary<string, short>();
private static Stack<string> _Banned = new Stack<string>();
private static Timer _Timer = CreateTimer();
private static Timer _BannedTimer = CreateBanningTimer();
private const int BANNED_REQUESTS = 10;
private const int REDUCTION_INTERVAL = 1000; // 1 second
private const int RELEASE_INTERVAL = 5 * 60 * 1000; // 5 minutes
private RequestDelegate _next;
public DosAttackMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext httpContext)
{
string ip = httpContext.Connection.RemoteIpAddress.ToString();
if (_Banned.Contains(ip))
{
httpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
CheckIpAddress(ip);
await _next(httpContext);
}
private static void CheckIpAddress(string ip)
{
if (!_IpAdresses.ContainsKey(ip))
{
_IpAdresses[ip] = 1;
}
else if (_IpAdresses[ip] == BANNED_REQUESTS)
{
_Banned.Push(ip);
_IpAdresses.Remove(ip);
}
else
{
_IpAdresses[ip]++;
}
}
private static Timer CreateTimer()
{
Timer timer = GetTimer(REDUCTION_INTERVAL);
timer.Elapsed += new ElapsedEventHandler(TimerElapsed);
return timer;
}
private static Timer CreateBanningTimer()
{
Timer timer = GetTimer(RELEASE_INTERVAL);
timer.Elapsed += delegate {
if (_Banned.Any()) _Banned.Pop();
};
return timer;
}
private static Timer GetTimer(int interval)
{
Timer timer = new Timer();
timer.Interval = interval;
timer.Start();
return timer;
}
private static void TimerElapsed(object sender, ElapsedEventArgs e)
{
foreach (string key in _IpAdresses.Keys.ToList())
{
_IpAdresses[key]--;
if (_IpAdresses[key] == 0) _IpAdresses.Remove(key);
}
}
}
代碼中設置:1秒(1000ms)中有超過10次訪問時,對應的IP會被禁用5分鐘。
使用時,在Startup.cs
中直接加載中間件:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseMiddleware<DosAttackMiddleware>();
...
}
以上代碼僅爲拋磚引玉之用。
公開的API,未經驗證的API,在生產環境會由於種種緣由被攻擊。這幾天公司的系統就由於這個出了大事。
因此,寫API的時候,要充分考慮到這些網絡攻擊的可能性,經過正確的處理,來防止來自網絡的攻擊。
這是一份責任,也是一個理念。
與你們共勉!
(全文完)
本文的代碼,我已經傳到Github上,位置在:https://github.com/humornif/Demo-Code/tree/master/0021/demo
![]() |
微信公衆號:老王Plus 掃描二維碼,關注我的公衆號,能夠第一時間獲得最新的我的文章和內容推送 本文版權歸做者全部,轉載請保留此聲明和原文連接 |