1. 程式人生 > 實用技巧 >Dotnet Core Public API的安全實踐

Dotnet Core Public API的安全實踐

公開API的安全,其實更重要。

一、API的安全

作為一個Dotnet Core的老司機,寫API時,能兼顧到API的安全,這是一種優雅。

通常,我們會用認證來保證API的安全,無敵的Authorize能解決我們很多的問題。

但是,總有一些場合,我們沒辦法用Authorize,而只能用匿名或不加驗證的方式來訪問。比方電商中查詢SKU的列表並在前端展示,通常這個無關使用者和許可權,在完成API的時候,我們也不會加入認證Authorize

這種情況下,如果直接寫,不加入安全級別,這樣的體系結構是有可能成為可供利用的安全漏洞的。

Dotnet Core框架已經提供了一些常見漏洞的解決方法,包括:

  • 跨站點指令碼
  • SQL注入
  • 跨站點請求偽造(CSRF)
  • 重定向

等等。

但是,我們還需要更進一步,還需要照顧到以下常見的攻擊:

  • 拒絕服務(DOS)
  • 分散式拒絕服務(DDOS)
  • 批量API呼叫
  • 探測響應
  • 資料抓取

這部分內容,需要我們自己實現。當然,這部分內容的實現,也可以從Web Server上進行設定。

本文討論的,是程式碼的實現。

為了防止不提供原網址的轉載,特在這裡加上原文連結:https://www.cnblogs.com/tiger-wang/p/13471718.html

二、相關程式碼

今天偷個懶,不講原理,以分享程式碼為主。

2.1 基於IP的客戶端請求限制

通過限制客戶端在指定的時間範圍內的請求數量,防止惡意bot攻擊。

程式碼中,我建立了一個基於IP的請求限制過濾器。

注意:有多個客戶端位於同一個IP地址的情況,這個情況在這個程式碼中沒有考慮。如果您希望實現這一點,可以把幾種方式結合起來使用。

以下是程式碼:

[AttributeUsage(AttributeTargets.Method)]
publicclassRequestLimitAttribute:ActionFilterAttribute
{
publicstringName{get;}
publicintNoOfRequest{get;set;}
publicintSeconds{get;set;}

privatestaticMemoryCacheCache{get;}=new
MemoryCache(newMemoryCacheOptions());

publicRequestLimitAttribute(stringname,intnoOfRequest=5,intseconds=10)
{
Name=name;
NoOfRequest=noOfRequest;
Seconds=seconds;
}
publicoverridevoidOnActionExecuting(ActionExecutingContextcontext)
{
varipAddress=context.HttpContext.Request.HttpContext.Connection.RemoteIpAddress;
varmemoryCacheKey=$"{Name}-{ipAddress}";

Cache.TryGetValue(memoryCacheKey,outintprevReqCount);
if(prevReqCount>=NoOfRequest)
{
context.Result=newContentResult
{
Content=$"Requestlimitisexceeded.Tryagainin{Seconds}seconds.",
};
context.HttpContext.Response.StatusCode=(int)HttpStatusCode.TooManyRequests;
}
else
{
varcacheEntryOptions=newMemoryCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromSeconds(Seconds));
Cache.Set(memoryCacheKey,(prevReqCount+1),cacheEntryOptions);
}
}
}

使用時,只要在需要的API前加屬性即可:

[HttpGet]
[RequestLimit("DataGet",5,30)]
publicIEnumerable<WeatherForecast>Get()
{
...
}

2.2 引用頭檢查

對API請求的請求引用頭進行檢查,可以防止API濫用,以及跨站點請求偽造(CSRF)攻擊。

同樣,也是採用自定義屬性的方式。

publicclassValidateReferrerAttribute:ActionFilterAttribute
{
privateIConfiguration_configuration;

publicoverridevoidOnActionExecuting(ActionExecutingContextcontext)
{
_configuration=(IConfiguration)context.HttpContext.RequestServices.GetService(typeof(IConfiguration));

base.OnActionExecuting(context);

if(!IsValidRequest(context.HttpContext.Request))
{
context.Result=newContentResult
{
Content=$"Invalidrefererheader",
};
context.HttpContext.Response.StatusCode=(int)HttpStatusCode.ExpectationFailed;
}
}
privateboolIsValidRequest(HttpRequestrequest)
{
stringreferrerURL="";

if(request.Headers.ContainsKey("Referer"))
{
referrerURL=request.Headers["Referer"];
}
if(string.IsNullOrWhiteSpace(referrerURL))returntrue;

varallowedUrls=_configuration.GetSection("CorsOrigin").Get<string[]>()?.Select(url=>newUri(url).Authority).ToList();

boolisValidClient=allowedUrls.Contains(newUri(referrerURL).Authority);

returnisValidClient;
}
}

這裡我用了一個配置,在appsetting.json中:

{
"CorsOrigin":["https://test.com","http://test1.cn:8080"]
}

CorsOrigin引數中加入允許引用的來源域名:埠列表。

使用時,在需要的API前加屬性:

[HttpGet]
[ValidateReferrer]
publicIEnumerable<WeatherForecast>Get()
{
...
}

2.3 DDOS攻擊檢查

DDOS攻擊在網上很常見,這種攻擊簡單有效,可以讓一個網站瞬間開始並長時間無法響應。通常來說,網站可以通過多種節流方法來避免這種情況。

下面我們換一種方式,用中介軟體MiddleWare來限制特定客戶端IP的請求數量。

publicclassDosAttackMiddleware
{

privatestaticDictionary<string,short>_IpAdresses=newDictionary<string,short>();
privatestaticStack<string>_Banned=newStack<string>();
privatestaticTimer_Timer=CreateTimer();
privatestaticTimer_BannedTimer=CreateBanningTimer();

privateconstintBANNED_REQUESTS=10;
privateconstintREDUCTION_INTERVAL=1000;//1second
privateconstintRELEASE_INTERVAL=5*60*1000;//5minutes
privateRequestDelegate_next;

publicDosAttackMiddleware(RequestDelegatenext)
{
_next=next;
}
publicasyncTaskInvokeAsync(HttpContexthttpContext)
{
stringip=httpContext.Connection.RemoteIpAddress.ToString();

if(_Banned.Contains(ip))
{
httpContext.Response.StatusCode=(int)HttpStatusCode.Forbidden;
}

CheckIpAddress(ip);

await_next(httpContext);
}

privatestaticvoidCheckIpAddress(stringip)
{
if(!_IpAdresses.ContainsKey(ip))
{
_IpAdresses[ip]=1;
}
elseif(_IpAdresses[ip]==BANNED_REQUESTS)
{
_Banned.Push(ip);
_IpAdresses.Remove(ip);
}
else
{
_IpAdresses[ip]++;
}
}


privatestaticTimerCreateTimer()
{
Timertimer=GetTimer(REDUCTION_INTERVAL);
timer.Elapsed+=newElapsedEventHandler(TimerElapsed);
returntimer;
}

privatestaticTimerCreateBanningTimer()
{
Timertimer=GetTimer(RELEASE_INTERVAL);
timer.Elapsed+=delegate{
if(_Banned.Any())_Banned.Pop();
};
returntimer;
}

privatestaticTimerGetTimer(intinterval)
{
Timertimer=newTimer();
timer.Interval=interval;
timer.Start();
returntimer;
}

privatestaticvoidTimerElapsed(objectsender,ElapsedEventArgse)
{
foreach(stringkeyin_IpAdresses.Keys.ToList())
{
_IpAdresses[key]--;
if(_IpAdresses[key]==0)_IpAdresses.Remove(key);
}
}
}

程式碼中設定:1秒(1000ms)中有超過10次訪問時,對應的IP會被禁用5分鐘。

使用時,在Startup.cs中直接載入中介軟體:

publicvoidConfigure(IApplicationBuilderapp,IWebHostEnvironmentenv)
{
...
app.UseMiddleware<DosAttackMiddleware>();
...
}

三、結尾的話

以上程式碼僅為拋磚引玉之用。

公開的API,未經驗證的API,在生產環境會因為種種原因被攻擊。這幾天公司的系統就因為這個出了大事。

所以,寫API的時候,要充分考慮到這些網路攻擊的可能性,通過正確的處理,來防止來自網路的攻擊。

這是一份責任,也是一個理念。

與大家共勉!

(全文完)

本文的程式碼,我已經傳到Github上,位置在:https://github.com/humornif/Demo-Code/tree/master/0021/demo


微信公眾號:老王Plus

掃描二維碼,關注個人公眾號,可以第一時間得到最新的個人文章和內容推送

本文版權歸作者所有,轉載請保留此宣告和原文連結