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
Utilizzare Tailwind CSS all'interno di React: installazione
Eseguire i worklow di GitHub su runner potenziati
Estrarre dati randomici da una lista di oggetti in C#
Creare un webhook in Azure DevOps
Gestire il colore CSS con HWB
Sostituire la GitHub Action di login su private registry
Supportare lo HierarchyID di Sql Server in Entity Framework 8
Applicare un filtro per recuperare alcune issue di GitHub
Miglioramenti nell'accessibilità con Angular CDK
Sviluppare un'interfaccia utente in React con Tailwind CSS e Preline UI
Eseguire le GitHub Actions offline
Utilizzare i primary constructor di C# per inizializzare le proprietà