In uno scenario in cui dobbiamo effettuare chiamate server-to-server in modalità sicura, una possibile alternativa a OAuth2 è l'utilizzo di certificati X509. Con questo sistema, il client allegherà alla richiesta un particolare certificato, la cui thumbprint verrà poi verificata dal server.
Ci sono vari vantaggi nell'adozione di un sistema di questo tipo: se pensiamo per esempio a un'architettura basata su decine di micro servizi, tutti questi componenti possono condividere il medesimo certificato memorizzato su Azure KeyVault, così da semplificare scenari di rotazione delle chiavi.
Come implementare un sistema del genere in ASP.NET Core?
Per prima cosa abbiamo bisogno di generare due certificati di test:
1) una Certificate Authority
2) un certificato di test vero e proprio, generato a partire dalla CA precedente
Entrambi questi task possono essere eseguiti tramite il codice PowerShell in basso:
# Generiamo il certificato per la CA $rootCert = New-SelfSignedCertificate -DnsName "localhost", "localhost" ` -CertStoreLocation "cert:\CurrentUser\My" ` -NotAfter (Get-Date).AddYears(20) ` -FriendlyName "CertDemoRoot" ` -KeyUsageProperty All ` -KeyUsage CertSign, CRLSign, DigitalSignature $rootThumbprint = $rootCert.Thumbprint $mypwd = ConvertTo-SecureString -String "0000" -Force -AsPlainText Get-ChildItem -Path "cert:\CurrentUser\My\$rootThumbprint" ` | Export-PfxCertificate -FilePath .\CertDemoRoot.pfx -Password $mypwd > $null # Installiamo il CA Cert nei TrustedRoot Import-PfxCertificate -FilePath .\CertDemoRoot.pfx ` -CertStoreLocation "cert:\CurrentUser\Root" ` -Password $mypwd > $null # Creiamo il client certificate e installiamo su My $clientCert = New-SelfSignedCertificate ` -certstorelocation cert:\CurrentUser\my ` -dnsname "localhost" ` -Signer $rootCert ` -NotAfter (Get-Date).AddYears(20) ` -FriendlyName "CertDemoClient" $clientThumbprint = $clientCert.Thumbprint Get-ChildItem -Path "cert:\CurrentUser\my\$clientThumbprint" ` | Export-PfxCertificate -FilePath .\CertDemoClient.pfx -Password $mypwd > $null # Rimuoviamo il CA Cert da My Get-ChildItem -Path "cert:\CurrentUser\My\$rootThumbprint" | Remove-Item > $null Write-Host "Thumbprint: $clientThumbprint"
Grazie a questo script, avremo nel nostro store personale due nuovi certificati pronti all'uso. Ora non dobbiamo far altro che creare un progetto ASP.NET Core Web API, e aggiungere una reference al package:
Microsoft.AspNetCore.Authentication.Certificate
A questo punto, possiamo iniziare a modificare il nostro metodo Main come segue:
public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); // .. altro codice qui .. builder.Services.Configure<KestrelServerOptions>(options => { options.ConfigureHttpsDefaults(options => { options.ClientCertificateMode = ClientCertificateMode.AllowCertificate; }); }); var cert = new X509Certificate2(@"C:\[folder]\CertDemoClient.pfx", "0000"); builder.Services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme) .AddCertificate(options => { options.AllowedCertificateTypes = CertificateTypes.SelfSigned; options.Events = new CertificateAuthenticationEvents() { OnCertificateValidated = ctx => { if (ctx.ClientCertificate.Thumbprint == cert.Thumbprint) { Console.WriteLine("Certs match!"); ctx.Success(); } else { Console.WriteLine("certs don't match"); ctx.Fail("wrong cert"); } return Task.CompletedTask; } }; }); builder.Services.AddAuthorization(); // .. altro codice qui .. app.UseAuthentication(); app.UseAuthorization(); // .. altro codice qui .. }
Il codice in alto è un estratto del metodo Main, dove per prima cosa andiamo a configurare Kestrel per accettare un certificato dal client attivando la modalità AllowCertificate. Questa è solo una delle due opzioni utili (l'altra è RequireCertificate) e, nello specifico, è utile se vogliamo far sì che quella basata su certificate non sia l'unica autenticazione disponibile, per esempio vogliamo supportare anche JWT o avere degli endpoint pubblici. Se utilizzassimo RequireCertificate, invece, la richiesta in assenza di certificato verrebbe rigettata a livello di web server, prima cioè che raggiunga la nostra applicazione.
Successivamente, carichiamo in memoria il certificato a partire dal file che abbiamo generato per recuperarne la Thumbprint, e aggiungiamo uno schema di autenticazione all'interno del quale abbiamo inserito un controllo sulla Thumbprint ricevuta, per verificare che sia la stessa che ci aspettiamo.
Come ultimo passo, ovviamente, dovremo anche aggiungere i servizi di Authorization, così da poter impostare i controller come protetti, oltre che i relativi middleware.
Se abbiamo effettuato tutti i passaggi correttamente, non ci resta che marcare il nostro WeatherForecastController con l'attributo Authorize, e provare a eseguire l'applicazione.
[ApiController] [Authorize] [Route("[controller]")] public class WeatherForecastController : ControllerBase { ... }
All'apertura del browser, ci verrà richiesto di selezionare un certificate da utilizzare per il client:
![](https://www.aspitalia.com/script/images/1424.jpg)
Come possiamo facilmente verificare, solo utilizzando quello denominato CertDemoClient avremo effettivamente accesso all'endpoint.
Per rimuovere i certificati generati e tornare a una situazione "pulita" sul nostro sistema, possiamo utilizzare ancora PowerShell:
Get-ChildItem -Path "cert:\CurrentUser\Root\$rootThumbprint" | Remove-Item Get-ChildItem -Path "cert:\CurrentUser\My\$clientThumbprint" | Remove-Item
Un'ultima nota riguarda il fatto che questo codice è da intendersi per sole finalità di test e sviluppo, ma non è adatto per uno scenario di produzione: tipicamente, in questi casi, non utilizzeremo un Self Signed certificate, ma uno creato da una Trusted Authority, che installeremo verosimilmente in uno storage sicuro come KeyVault. Pertanto il codice per recuperarne la thumbprint e per configurare l'autenticazione sarà leggermente diverso. Ce ne occuperemo in un prossimo script.
Commenti
Per inserire un commento, devi avere un account.
Fai il login e torna a questa pagina, oppure registrati alla nostra community.
Approfondimenti
Migrare una service connection a workload identity federation in Azure DevOps
Aggiungere interattività lato server in Blazor 8
Utilizzare i primary constructor di C# per inizializzare le proprietà
Generare token per autenicarsi sulle API di GitHub
Eseguire attività pianificate con Azure Container Jobs
Eseguire query manipolando le liste contenute in un oggetto mappato verso una colonna JSON
Eseguire attività con Azure Container Jobs
Utilizzare la versione generica di EntityTypeConfiguration in Entity Framework Core
Verificare la provenienza di un commit tramite le GitHub Actions
Eseguire operazioni sui blob con Azure Storage Actions
Utilizzare Azure Cosmos DB con i vettori
Assegnare un valore di default a un parametro di una lambda in C#
I più letti di oggi
- ecco tutte le novità pubblicate sui nostri siti questa settimana: https://aspit.co/wkly buon week-end!
- Windows Server 2003 SP1 Italiano
- Creare un modulo e un controller con AngularJS
- Build 2014: tutte le novità per gli sviluppatori in diretta da San Francisco
- Rilasciata la versione RTM di Windows Vista SP1