In uno script di qualche settimana fa (https://www.aspitalia.com/script/1296/Costruire-Client-HTTP-Tipizzato-Refit-IHttpClientFactory-ASP.NET-Core-2.1.aspx), abbiamo introdotto il package Refit, che permette di creare dei client tipizzati per servizi REST. Cerchiamo di capire come utilizzarlo per realizzare una funzionalità di blocco territoriale, per esempio per consentire l'accesso a una action solo se la richiesta proviene da una certa area geografica.
Allo scopo possiamo sfruttare un servizio denominato IpStack (https://ipstack.com/product) che possiede anche un profilo gratuito che include 10.000 richieste al mese. Questo servizio espone una REST API che, dato un IP, restituisce una serie di informazioni:
{ "ip": "89.32.122.191", "type": "ipv4", "continent_code": "EU", "continent_name": "Europe", "country_code": "GB", "country_name": "United Kingdom", "region_code": "ENG", "region_name": "England", .. "location": { .. "is_eu": true } }
Per integrarlo, ci basta realizzare un'interfaccia come la seguente, tramite cui passare l'indirizzo IP e la chiave di accesso:
public interface IIpClient { [Get("/{ip}")] Task<IpData> Get(string ip, [AliasAs("access_key")] string key); }
Come sappiamo, poi, il passo successivo è quello di registrare il client all'interno della classe Startup:
public void ConfigureServices(IServiceCollection services) { // .. altro codice qui .. services.AddHttpClient("ip", x => { x.BaseAddress = new Uri("http://api.ipstack.com"); }).AddTypedClient(c => RestService.For<IIpClient>(c)); services.AddSingleton<EuResourceFilter>(); // .. altro codice qui .. }
Nel metodo ConfigureServices abbiamo registrato anche una classe denominata EuResourceFilter, che implementa la nostra logica di blocco territoriale. Si tratta di un IAsyncResourceFilter, ossia un filtro che viene eseguito subito dopo l'eventuale autorizzazione della richiesta, ma prima dell'esecuzione della action.
All'interno di questo filtro, effettuiamo la chiamata a IpStack per determinare la provenienza geografica della richiesta:
public class EuResourceFilter : IAsyncResourceFilter { private string _key; private IIpClient _client; public EuResourceFilter(IIpClient client, IConfiguration configuration) { _key = configuration.GetValue<string>("ipKey"); _client = client; } public async Task OnResourceExecutionAsync( ResourceExecutingContext context, ResourceExecutionDelegate next) { var sourceIp = context.HttpContext.Connection.RemoteIpAddress.ToString(); var ipData = await _client.Get(sourceIp, _key); if (!ipData.Location.IsEu) { context.Result = new StatusCodeResult(403); return; } await next(); } }
Il cuore del nostro filtro è rappresentato dal metodo OnResourceExecutionAsync, che riceve il contesto di esecuzione da cui recuperiamo l'IP del chiamante. A questo punto, tramite IIpClient, effettuiamo la chiamata a IpStack e, se la location non è in europa, restituiamo uno status code 403 - Forbidden.
In caso contrario, invece, ci limitiamo a invocare il delegate next per proseguire con l'esecuzione della richiesta.
Ovviamente, questo filtro non è direttamente traducibile in un attribute, perchè necessita di un paio di risorse in input dall'IoC container di ASP.NET Core. Come abbiamo visto nello scorso script, ci è sufficiente creare un attribute che implementi IFilterFactory:
public class EuOnlyAttribute : Attribute, IFilterFactory { public bool IsReusable => true; public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) { return serviceProvider.GetService<EuResourceFilter>(); } }
A questo punto non ci resta che decorare le action o i controller che vogliamo proteggere con l'attributo EuOnly:
[EuOnly] public class ValuesController : ControllerBase { .. }
Per semplicità, in questo script abbiamo trascurato alcuni aspetti fondamentali per uno scenario di produzione. Innanzi tutto dobbiamo tenere a mente che questo filtro verrà invocato per ogni richiesta che riceviamo, e pertanto è opportuno mantenere una cache durevole, magari anche persistente, degli IP risolti, così da non rallentare le prestazioni del nostro sito web.
Inoltre, se la chiamata a IP Stack dovesse fallire, fallirebbe anche la richiesta verso il nostro sito, provocando un disservizio agli utenti. Pertanto, può essere sicuramente consigliabile introdurre una logica di circuit breaker, come quella introdotta in questo script (https://www.aspitalia.com/script/1256/Implementare-Pattern-Circuit-Breaker-ASP.NET-Core-MVC.aspx), così da bypassare il servizio esterno nel caso non sia disponibile.
Commenti
Per inserire un commento, devi avere un account.
Fai il login e torna a questa pagina, oppure registrati alla nostra community.
Approfondimenti
Gestire undefined e partial nelle reactive forms di Angular
Gestire errori funzionali tramite exception in ASP.NET Core Web API
Short-circuiting della Pipeline in ASP.NET Core
Eseguire le GitHub Actions offline
Load test di ASP.NET Core con k6
Effettuare il deploy di immagini solo da container registry approvati in Kubernetes
Implementare il throttling in ASP.NET Core
Configurare dependabot per aggiornare le dipendenze di terze parti con GitHub Actions
Sfruttare lo streaming di una chiamata Http da Blazor
.NET Conference Italia 2023
Come EF 8 ha ottimizzato le query che usano il metodo Contains
I più letti di oggi
- Miglioramenti nelle performance di Angular 16
- Ottimizzare le performance delle collection con le classi FrozenSet e FrozenDictionary
- HTML5 con CSS e JavaScript
- ecco tutte le novità pubblicate sui nostri siti questa settimana: https://aspit.co/wkly buon week-end!
- Ottimizzazione dei block template in Angular 17
- Disabilitare automaticamente un workflow di GitHub (parte 2)