Scenario 9.3: Integrazione SSO
Categoria: Autenticazione
Complessita: ⭐⭐⭐⭐ (Alta)
Prerequisiti: Identity Provider, Certificati client
Tempo stimato: 30-45 minuti
Descrizione
Questo scenario descrive l'integrazione dell'autenticazione basata su certificati nei sistemi Single Sign-On (SSO). I certificati possono essere utilizzati come fattore di autenticazione primario o aggiuntivo.
Protocolli supportati:
- SAML 2.0 - X.509 Certificate Holder Assertion
- OIDC - mTLS Client Credentials
- WS-Federation - X.509 Token
- Kerberos PKINIT - Certificate-to-Kerberos
Workflow: OIDC con mTLS
sequenceDiagram
participant User
participant App
participant IdP as Identity Provider
participant API
User->>App: Accesso
App->>IdP: Authorization Request
IdP->>User: Richiedere certificato client
User->>IdP: Presentare certificato
IdP->>IdP: Validare certificato
IdP->>App: ID Token + Access Token
App->>API: Request + Access Token
API->>App: Response
Esempio di codice: OIDC con autenticazione certificato
using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.AspNetCore.Authentication.Certificate; var builder = WebApplication.CreateBuilder(args); // Autenticazione certificato per comunicazione IdP builder.Services.AddAuthentication(options => { options.DefaultScheme = "Cookies"; options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; }) .AddCookie("Cookies") .AddOpenIdConnect(options => { options.Authority = "https://idp.example.com"; options.ClientId = "my-app"; options.ClientSecret = "secret"; // Oppure certificato client // mTLS per Token Endpoint options.BackchannelHttpHandler = CreateMtlsHandler(); options.ResponseType = "code"; options.SaveTokens = true; // Mapping claim certificato options.ClaimActions.MapJsonKey("certificate_thumbprint", "x5t"); options.ClaimActions.MapJsonKey("certificate_subject", "x5t#S256"); options.Events = new OpenIdConnectEvents { OnTokenValidated = context => { // Validazione aggiuntiva var cert = context.HttpContext.Connection.ClientCertificate; if (cert != null) { // Collegare certificato al token var identity = context.Principal.Identity as ClaimsIdentity; identity?.AddClaim(new Claim("client_cert_subject", cert.Subject)); } return Task.CompletedTask; } }; }); static HttpClientHandler CreateMtlsHandler() { var handler = new HttpClientHandler(); handler.ClientCertificates.Add( new X509Certificate2("client.pfx", "Password!") ); return handler; }
Esempio di codice: SAML con X.509 Holder-of-Key
using ComponentSpace.Saml2; public class SamlCertificateAuthService { private readonly ISamlServiceProvider _samlServiceProvider; public async Task<ClaimsPrincipal> AuthenticateWithCertificate( HttpContext httpContext, X509Certificate2 clientCert) { // Certificato come Holder-of-Key Subject Confirmation var samlResponse = await _samlServiceProvider.ReceiveSSOAsync(); // Verificare Subject Confirmation Method if (samlResponse.SubjectConfirmationMethod != "urn:oasis:names:tc:SAML:2.0:cm:holder-of-key") { throw new SecurityException("Richiesta Holder-of-Key Confirmation"); } // Confrontare certificato dall'asserzione SAML con quello presentato var assertionCert = ExtractCertificateFromAssertion(samlResponse); if (assertionCert.Thumbprint != clientCert.Thumbprint) { throw new SecurityException("Il certificato non corrisponde all'asserzione"); } return samlResponse.ClaimsPrincipal; } private X509Certificate2 ExtractCertificateFromAssertion(SamlResponse response) { // Estrarre X.509 Certificate da SubjectConfirmationData var keyInfoXml = response.SubjectConfirmationDataKeyInfo; // Parse X509Data from KeyInfo // ... return null; } }
Active Directory Certificate Mapping
public class AdCertificateMappingService { private readonly string _ldapPath; public AdCertificateMappingService(string ldapPath) { _ldapPath = ldapPath; // es. "LDAP://dc.example.com" } public DirectoryEntry FindUserByCertificate(X509Certificate2 cert) { using var searcher = new DirectorySearcher(new DirectoryEntry(_ldapPath)); // Opzione 1: UPN da SAN var upn = GetUpnFromCertificate(cert); if (upn != null) { searcher.Filter = $"(userPrincipalName={upn})"; return searcher.FindOne()?.GetDirectoryEntry(); } // Opzione 2: altSecurityIdentities (Mapping esplicito) var issuerSerial = $"X509:<I>{cert.Issuer}<S>{cert.SerialNumber}"; searcher.Filter = $"(altSecurityIdentities={issuerSerial})"; var result = searcher.FindOne(); if (result != null) return result.GetDirectoryEntry(); // Opzione 3: Subject + Issuer var subjectIssuer = $"X509:<S>{cert.Subject}<I>{cert.Issuer}"; searcher.Filter = $"(altSecurityIdentities={subjectIssuer})"; return searcher.FindOne()?.GetDirectoryEntry(); } public void MapCertificateToUser(string userDn, X509Certificate2 cert, CertMappingType type) { using var user = new DirectoryEntry($"{_ldapPath}/{userDn}"); string mapping = type switch { CertMappingType.IssuerSerial => $"X509:<I>{cert.Issuer}<SR>{cert.SerialNumber}", CertMappingType.SubjectIssuer => $"X509:<S>{cert.Subject}<I>{cert.Issuer}", CertMappingType.SubjectOnly => $"X509:<S>{cert.Subject}", CertMappingType.IssuerSubjectHash => $"X509:<SHA1-PUKEY>{cert.GetCertHashString()}", _ => throw new ArgumentException("Tipo di mapping sconosciuto") }; var altSecIds = user.Properties["altSecurityIdentities"]; altSecIds.Add(mapping); user.CommitChanges(); Console.WriteLine($"Mapping creato: {mapping}"); } private string GetUpnFromCertificate(X509Certificate2 cert) { // UPN OID: 1.3.6.1.4.1.311.20.2.3 var sanExt = cert.Extensions["2.5.29.17"]; if (sanExt == null) return null; // Parse SAN per UPN... return null; } } public enum CertMappingType { IssuerSerial, SubjectIssuer, SubjectOnly, IssuerSubjectHash }
Azure AD Certificate-Based Auth
// Azure AD App Registration per CBA public class AzureAdCbaService { private readonly IConfidentialClientApplication _app; public AzureAdCbaService(string tenantId, string clientId, X509Certificate2 clientCert) { _app = ConfidentialClientApplicationBuilder .Create(clientId) .WithAuthority($"https://login.microsoftonline.com/{tenantId}") .WithCertificate(clientCert) // Certificato client invece di Secret .Build(); } public async Task<AuthenticationResult> AcquireTokenForClient(string[] scopes) { return await _app.AcquireTokenForClient(scopes).ExecuteAsync(); } public async Task<AuthenticationResult> AcquireTokenOnBehalfOf( string[] scopes, string userToken) { var userAssertion = new UserAssertion(userToken); return await _app.AcquireTokenOnBehalfOf(scopes, userAssertion).ExecuteAsync(); } } // Azure AD B2C con autenticazione certificato builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApi(builder.Configuration) .EnableTokenAcquisitionToCallDownstreamApi() .AddInMemoryTokenCaches();
Keycloak X.509 Authenticator
{
"alias": "x509-auth",
"providerId": "auth-x509-client-username-form",
"enabled": true,
"config": {
"x509-cert-auth.mapping-source-selection": "Subject's Common Name",
"x509-cert-auth.regular-expression": "CN=(.*?)(?:,|$)",
"x509-cert-auth.user-mapping-method": "Username or Email",
"x509-cert-auth.crl-checking-enabled": "true",
"x509-cert-auth.ocsp-checking-enabled": "true",
"x509-cert-auth.ocsp-responder-uri": "http://ocsp.example.com",
"x509-cert-auth.timestamp-validation-enabled": "true",
"x509-cert-auth.keyusage": "digitalSignature",
"x509-cert-auth.extendedkeyusage": "id-kp-clientAuth"
}
}
Scenari SSO specifici per settore
| Settore | IdP | Protocollo | Requisiti certificato |
|---|---|---|---|
| Enterprise | Azure AD / Okta | OIDC | UPN nel SAN |
| Government | ADFS / PIV | SAML HoK | PIV Auth Cert |
| Healthcare | gematik IdP | OIDC | HBA/SMC-B |
| Finanza | PKI propria | SAML | Qualificato |
Scenari correlati
| Relazione | Scenario | Descrizione |
|---|---|---|
| Prerequisito | 9.1 mTLS Client-Auth | Livello TLS |
| Correlato | 9.2 Smartcard Login | Auth hardware |
| Correlato | 3.2 Certificato client | Creare cert |
« ← 9.2 Smartcard Login | ↑ Panoramica autenticazione | → Tutti gli scenari »
Wolfgang van der Stille @ EMSR DATA d.o.o. - Post-Quantum Cryptography Professional
Zuletzt geändert: il 30/01/2026 alle 00:18