Scenarij 9.3: SSO integracija

Kategorija: Autentifikacija
Složenost: ⭐⭐⭐⭐ (Visoka)
Preduvjeti: Identity Provider, klijentski certifikati
Procijenjeno vrijeme: 30-45 minuta


Opis

Ovaj scenarij opisuje integraciju autentifikacije temeljene na certifikatima u Single Sign-On (SSO) sustave. Certifikati se mogu koristiti kao primarni ili dodatni faktor autentifikacije.

Podržani protokoli:

  • SAML 2.0 - X.509 Certificate Holder Assertion
  • OIDC - mTLS Client Credentials
  • WS-Federation - X.509 Token
  • Kerberos PKINIT - Certificate-to-Kerberos

Tijek rada: OIDC s mTLS

sequenceDiagram participant User as Korisnik participant App as Aplikacija participant IdP as Identity Provider participant API User->>App: Pristup App->>IdP: Authorization Request IdP->>User: Zahtjev za klijentski certifikat User->>IdP: Prezentacija certifikata IdP->>IdP: Validacija certifikata IdP->>App: ID Token + Access Token App->>API: Zahtjev + Access Token API->>App: Odgovor


Primjer koda: OIDC s autentifikacijom certifikatom

using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authentication.Certificate;
 
var builder = WebApplication.CreateBuilder(args);
 
// Autentifikacija certifikatom za IdP komunikaciju
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";  // Ili klijentski certifikat
 
    // mTLS za Token Endpoint
    options.BackchannelHttpHandler = CreateMtlsHandler();
 
    options.ResponseType = "code";
    options.SaveTokens = true;
 
    // Mapiranje claims certifikata
    options.ClaimActions.MapJsonKey("certificate_thumbprint", "x5t");
    options.ClaimActions.MapJsonKey("certificate_subject", "x5t#S256");
 
    options.Events = new OpenIdConnectEvents
    {
        OnTokenValidated = context =>
        {
            // Dodatna validacija
            var cert = context.HttpContext.Connection.ClientCertificate;
            if (cert != null)
            {
                // Povezivanje certifikata s tokenom
                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;
}

Primjer koda: SAML s X.509 Holder-of-Key

using ComponentSpace.Saml2;
 
public class SamlCertificateAuthService
{
    private readonly ISamlServiceProvider _samlServiceProvider;
 
    public async Task<ClaimsPrincipal> AuthenticateWithCertificate(
        HttpContext httpContext,
        X509Certificate2 clientCert)
    {
        // Certifikat kao Holder-of-Key Subject Confirmation
        var samlResponse = await _samlServiceProvider.ReceiveSSOAsync();
 
        // Provjera Subject Confirmation Method
        if (samlResponse.SubjectConfirmationMethod != "urn:oasis:names:tc:SAML:2.0:cm:holder-of-key")
        {
            throw new SecurityException("Potrebna je Holder-of-Key Confirmation");
        }
 
        // Usporedba certifikata iz SAML assertion s prezentiranim
        var assertionCert = ExtractCertificateFromAssertion(samlResponse);
        if (assertionCert.Thumbprint != clientCert.Thumbprint)
        {
            throw new SecurityException("Certifikat ne odgovara assertion");
        }
 
        return samlResponse.ClaimsPrincipal;
    }
 
    private X509Certificate2 ExtractCertificateFromAssertion(SamlResponse response)
    {
        // Ekstrakcija X.509 Certificate iz 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;  // npr. "LDAP://dc.example.com"
    }
 
    public DirectoryEntry FindUserByCertificate(X509Certificate2 cert)
    {
        using var searcher = new DirectorySearcher(new DirectoryEntry(_ldapPath));
 
        // Opcija 1: UPN iz SAN
        var upn = GetUpnFromCertificate(cert);
        if (upn != null)
        {
            searcher.Filter = $"(userPrincipalName={upn})";
            return searcher.FindOne()?.GetDirectoryEntry();
        }
 
        // Opcija 2: altSecurityIdentities (Explicit Mapping)
        var issuerSerial = $"X509:<I>{cert.Issuer}<S>{cert.SerialNumber}";
        searcher.Filter = $"(altSecurityIdentities={issuerSerial})";
        var result = searcher.FindOne();
        if (result != null) return result.GetDirectoryEntry();
 
        // Opcija 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("Nepoznati tip mapiranja")
        };
 
        var altSecIds = user.Properties["altSecurityIdentities"];
        altSecIds.Add(mapping);
        user.CommitChanges();
 
        Console.WriteLine($"Mapiranje kreirano: {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 za UPN...
        return null;
    }
}
 
public enum CertMappingType
{
    IssuerSerial,
    SubjectIssuer,
    SubjectOnly,
    IssuerSubjectHash
}

Azure AD Certificate-Based Auth

// Azure AD App Registration za 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)  // Klijentski certifikat umjesto 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 s autentifikacijom certifikatom
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"
  }
}

SSO scenariji specifični za industriju

Industrija IdP Protokol Zahtjev za certifikat
Enterprise Azure AD / Okta OIDC UPN u SAN
Government ADFS / PIV SAML HoK PIV Auth Cert
Healthcare gematik IdP OIDC HBA/SMC-B
Financije Vlastita PKI SAML Kvalificirani

Povezani scenariji

Povezanost Scenarij Opis
Preduvjet 9.1 mTLS Client-Auth TLS razina
Povezano 9.2 Smartcard prijava Hardverska autentifikacija
Povezano 3.2 Klijentski certifikat Kreiranje certifikata

« ← 9.2 Smartcard prijava | ↑ Pregled autentifikacije | → Svi scenariji »


Wolfgang van der Stille @ EMSR DATA d.o.o. - Post-Quantum Cryptography Professional

Zuletzt geändert: 30.01.2026. u 00:18