Zum Inhalt springen

Services, Wiki-Artikel, Blog-Beiträge und Glossar-Einträge durchsuchen

↑↓NavigierenEnterÖffnenESCSchließen
Enterprise SSO implementieren: SAML 2.0, OIDC und Keycloak im Vergleich - Cybersicherheit und digitaler Schutz
Netzwerk- & Endpoint Security

Enterprise SSO implementieren: SAML 2.0, OIDC und Keycloak im Vergleich

Single Sign-On Implementierung für Unternehmen: SAML 2.0 (Ablauf, Assertions, SP-/IdP-initiiert), OpenID Connect (Authorization Code Flow + PKCE, JWT-Tokens), Keycloak als Open-Source-Identity-Provider (Realm-Konfiguration, Client-Setup, Attribute Mapper, Composite Roles), LDAP/Active-Directory-Integration, MFA-Enforcement und SSO-Migration ohne User-Disruption.

Vincent Heinen Vincent Heinen Abteilungsleiter Offensive Services
13 Min. Lesezeit
OSCP+ OSCP OSWP OSWA

TL;DR

Die Implementierung von Enterprise Single Sign-On erfordert eine fundierte Entscheidung zwischen SAML 2.0 und OpenID Connect, wobei jedes Protokoll spezifische Stärken und Anwendungsfälle aufweist. SAML 2.0, entwickelt 2005, eignet sich mit seinem XML-Format und breiter App-Kompatibilität besonders für Legacy-Enterprise-Anwendungen wie SAP oder Salesforce. OpenID Connect (OIDC), seit 2014 auf OAuth 2.0 basierend, nutzt JSON/JWT und ist ideal für moderne Web- und Mobile-Apps sowie APIs. Der Artikel beleuchtet detailliert den SP-initiierten SAML-Ablauf und den empfohlenen OIDC Authorization Code Flow mit PKCE, inklusive der Struktur von JWT-Tokens. Zudem werden kritische Sicherheitsprobleme wie XML-Signature-Wrapping bei SAML und deren Schutzmaßnahmen aufgezeigt.

Diese Zusammenfassung wurde KI-gestützt erstellt (EU AI Act Art. 52).

Inhaltsverzeichnis (7 Abschnitte)

Single Sign-On ist heute Standard in Unternehmensumgebungen - aber die Implementierung birgt zahlreiche Fallstricke. Ob SAML oder OIDC, Keycloak oder Azure AD: jede Entscheidung hat Konsequenzen für Sicherheit, Usability und Wartbarkeit. Dieser Guide zeigt die vollständige SSO-Implementierung.

SAML 2.0 vs. OpenID Connect: Die Wahl

Welches Protokoll für welchen Use Case?

SAML 2.0 (Security Assertion Markup Language):

  • Entwickelt: 2005 (für Browser-basierte Enterprise-Apps)
  • Format: XML (komplex, aber vollständig)
  • Stärken: Enterprise-Standard, breite App-Kompatibilität
  • Schwächen: kein nativer Mobile-Support, XML-Overhead
  • Typische Use Cases: SharePoint, SAP, Salesforce, ServiceNow, Legacy Enterprise Applications, Behörden und stark regulierte Branchen

OpenID Connect (OIDC):

  • Entwickelt: 2014 (über OAuth 2.0 aufgebaut)
  • Format: JSON/JWT (einfach, flexibel)
  • Stärken: Modern, Mobile-freundlich, APIs, SPA
  • Schwächen: Jüngere Apps fehlen teils noch
  • Typische Use Cases: Web-Apps (React, Vue, Angular), Mobile Apps (iOS, Android), APIs und Microservices, Google/GitHub/Microsoft Auth

Entscheidungsmatrix

App-TypSAMLOIDC
Browser-SPAneinja
Mobile Appneinja
Legacy Enterprisejaselten
Microservicesneinja
Office 365beidebeide (OIDC bevorzugt)
SAP/Oraclejanein

SAML 2.0 im Detail

SAML-Ablauf (SP-initiiert - Standard)

Akteure:

  • User: Benutzer (Browser)
  • SP: Service Provider = App (z.B. Salesforce)
  • IdP: Identity Provider = Auth-Server (z.B. Keycloak, AD FS)

SP-initiierter Flow:

  1. User → Salesforce.com/protected
  2. Salesforce: kein gültiges SAML-Session → redirect zu IdP
  3. Redirect-URL enthält SAMLRequest (base64-encoded XML): https://sso.company.com/saml/...?SAMLRequest=...&RelayState=...
  4. IdP: zeigt Login-Seite (wenn kein Session)
  5. User: Username + Passwort + MFA eingeben
  6. IdP erstellt SAML Assertion (signiert mit Private Key):
<saml:Assertion>
  <saml:AttributeStatement>
    <saml:Attribute Name="email">
      <saml:AttributeValue>user@company.com</saml:AttributeValue>
    </saml:Attribute>
    <saml:Attribute Name="groups">
      <saml:AttributeValue>Salesforce-Admins</saml:AttributeValue>
    </saml:Attribute>
  </saml:AttributeStatement>
  <saml:AuthnStatement SessionIndex="_abc123">
    <saml:AuthnContext>
      <saml:AuthnContextClassRef>
        urn:oasis:names:tc:SAML:2.0:ac:classes:TimeSyncToken
      </saml:AuthnContextClassRef>
    </saml:AuthnContext>
  </saml:AuthnStatement>
</saml:Assertion>
  1. IdP: POST zur ACS-URL des SP mit SAMLResponse
  2. SP: validiert Signatur mit IdP-Zertifikat
  3. SP: erstellt lokale Session für User → Zugang gewährt!

Wichtige SAML-Parameter

ParameterBeschreibung
NameID FormatemailAddress (empfohlen), persistent, transient
ACS-URLAssertion Consumer Service URL des SP
Entity IDEindeutige SP-Identifikation
RelayStateUrsprüngliche URL nach Auth
NotBefore/AfterZeitfenster der Assertion (max. 5 Min.!)

SAML-Sicherheitsprobleme

1. XML-Signature-Wrapping (XSW):

  • Angreifer wickelt bösartige Assertion um legitime
  • SP validiert legitime, aber nutzt bösartige!
  • Schutz: xpath-basierte Signatur-Validierung + Zertifikat-Pinning

2. Assertion-Replay:

  • Gültige Assertion = nochmal verwendbar
  • Schutz: InResponseTo + Assertion-ID-Cache (1h)

OpenID Connect (OIDC) und OAuth 2.0

Authorization Code Flow mit PKCE (Empfehlung 2024)

Akteure:

  • User: Endnutzer
  • Client: Web-App / Mobile App
  • Authorization Server: IdP (Keycloak, Auth0, etc.)
  • Resource Server: API (mit Access Token geschützt)
# Client generiert code_verifier:
# code_verifier = random(43-128 Zeichen URL-safe)
# code_challenge = BASE64URL(SHA256(code_verifier))

# Schritt 1: Authorization Request:
GET https://sso.company.com/auth/realms/company/protocol/openid-connect/auth
  ?response_type=code
  &client_id=my-app
  &redirect_uri=https://app.company.com/callback
  &scope=openid profile email groups
  &state=random_state_value          # CSRF-Schutz
  &code_challenge=abc123...          # PKCE
  &code_challenge_method=S256

# Schritt 2: User authentifiziert sich am IdP
# IdP leitet zurück zu:
# https://app.company.com/callback?code=AUTH_CODE&state=random_state_value

# Schritt 3: Token Exchange (Server-zu-Server!):
POST https://sso.company.com/.../token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=AUTH_CODE
&redirect_uri=https://app.company.com/callback
&client_id=my-app
&client_secret=...
&code_verifier=original_verifier    # PKCE-Verifikation

# Antwort:
{
  "access_token": "eyJ...",    # JWT, gültig 5 Min.
  "id_token": "eyJ...",        # User-Info, signiert
  "refresh_token": "...",      # Für Token-Erneuerung
  "expires_in": 300
}

JWT-Aufbau (Access Token)

// Header:
{"alg": "RS256", "typ": "JWT", "kid": "key-id-1"}

// Payload:
{
  "iss": "https://sso.company.com/realms/company",
  "sub": "user-uuid-123",
  "aud": ["my-app", "account"],
  "exp": 1709550000,
  "iat": 1709549700,
  "email": "user@company.com",
  "groups": ["admins", "developers"],
  "preferred_username": "john.doe"
}
// Signatur: RS256 mit IdP-Private-Key

JWT-Validierung (Server-Side)

# Python (PyJWT):
import jwt
from cryptography.hazmat.primitives import serialization

# JWKS-Endpoint: public keys des IdP
jwks_uri = "https://sso.company.com/realms/company/protocol/openid-connect/certs"
jwks_client = jwt.PyJWKClient(jwks_uri)
signing_key = jwks_client.get_signing_key_from_jwt(token)

payload = jwt.decode(
    token,
    signing_key.key,
    algorithms=["RS256"],
    audience="my-app",
    issuer="https://sso.company.com/realms/company"
)
# → Automatisch: exp, iat, iss, aud geprüft!

Keycloak als Open-Source Identity Provider

Installation (Docker)

# docker-compose.yml:
services:
  keycloak:
    image: quay.io/keycloak/keycloak:24.0.2
    command: start-dev --import-realm
    environment:
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: ${KC_ADMIN_PASSWORD}
      KC_DB: postgres
      KC_DB_URL: jdbc:postgresql://postgres:5432/keycloak
      KC_DB_USERNAME: keycloak
      KC_DB_PASSWORD: ${KC_DB_PASSWORD}
      KC_HOSTNAME: sso.company.com
      KC_PROXY: edge  # Hinter Nginx/Traefik!
    volumes:
      - ./realm-export.json:/opt/keycloak/data/import/realm.json

Realm-Konzept

  • Realm “company”: für alle Unternehmensanwendungen
  • Realm “customers”: für Kunden-SSO (separater Bereich!)
  • Kein Realm “master” für Anwendungen verwenden! (nur Admin)

Client-Konfiguration (OIDC-Web-App)

Client ID:           my-app
Client Protocol:     openid-connect
Access Type:         confidential (mit Client Secret)
Valid Redirect URIs: https://app.company.com/*
Web Origins:         https://app.company.com
Standard Flow:       ON (Authorization Code)
Direct Access:       OFF (kein Username+Password direkt!)

# Client Secret anzeigen:
Clients → my-app → Credentials → Client secret

Rollen und Gruppen

# Realm-Rollen (für alle Clients):
admin, user, developer, viewer

# Client-Rollen (app-spezifisch):
my-app: admin, user, read-only

# Composite Roles (Rollengruppen):
developer = user + deployer + read-only

# Gruppen → Rollen mapping:
Gruppe "Engineering" → hat Realm-Rolle "developer"
→ Alle Gruppen-Mitglieder erhalten automatisch developer-Rechte

Attribute Mapper (Custom Claims in Token)

# Mapper: Gruppen im Token:
Name:            groups
Mapper Type:     Group Membership
Token Claim:     groups
Full group path: ON (z.B. /Engineering/Backend)

# Mapper: Abteilung aus User-Attribut:
Name:            department
Mapper Type:     User Attribute
User Attribute:  department  (custom Feld in User-Profil)
Token Claim:     department

Active Directory Integration

LDAP/AD-Integration in Keycloak

User Federation → Add Provider → ldap

LDAP-Konfiguration:
  Vendor:              Active Directory
  Connection URL:      ldaps://dc01.company.local:636
  Bind DN:             CN=keycloak-svc,OU=ServiceAccounts,DC=company,DC=local
  Bind Credential:     [Service-Account-Passwort]
  User DN:             OU=Users,DC=company,DC=local
  User Object Classes: person, organizationalPerson, user
  Import Users:        ON (oder Read-Only Sync)
# LDAP-Filter (nur aktive Accounts):
Custom User LDAP Filter: (&(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))

Gruppen-Sync

Mapper → group-ldap-mapper:
  LDAP Groups DN:              OU=SecurityGroups,DC=company,DC=local
  Group Object Classes:        group
  Membership LDAP Attribute:   member
  Group Name LDAP Attribute:   cn

# AD-Gruppe "SSO-Admins" → Keycloak-Rolle "admin"
Group Roles Mapper: SSO-Admins → admin

Sync-Strategie

# Sync-Intervalle:
# Initial Sync: alle 4h (Full Sync)
# Changed Users Sync: alle 15 Min. (Changed Sync)

# Via Admin CLI:
kcadm.sh trigger-ldap-full-sync --realm company \
  --federation-id ldap-provider-id

Kerberos/SPNEGO (Seamless SSO für Windows-Clients):

  • Keycloak mit Kerberos konfigurieren
  • Windows-Nutzer im Firmennetz: kein Login nötig!
  • Browser sendet automatisch Kerberos-Ticket an Keycloak
  • Funktioniert: Chrome, Edge (Firefox braucht Konfiguration)

MFA und Adaptive Authentication

TOTP einrichten

Authentication → Policies → OTP Policy:
  OTP Type:       TOTP
  OTP Hash Algo:  SHA1 (Compat) oder SHA256 (sicher)
  Period:         30 Sekunden
  Initial Counter: 0

# Flow: Browser-MFA erzwingen:
Authentication → Flows → Browser:
  ✓ Username Password Form (REQUIRED)
  ✓ OTP Form (REQUIRED) ← hinzufügen!

FIDO2/WebAuthn

# Keycloak 21+: WebAuthn nativer Support!
Authentication → Policies → WebAuthn Policy:
  Relying Party Entity Name:       company.com
  Attestation Conveyance:          direct
  Authenticator Attachment:        platform (nur Gerät) | cross-platform (externe Keys)
  User Verification Requirement:   required

Adaptive Authentication (Risikobasiert)

Beispiel-Regeln:

  • Neues Gerät + neuer Standort → MFA erzwingen
  • VPN-IP-Range → kein MFA nötig (trusted network)
  • Fehlgeschlagene Logins > 3 → CAPTCHA
  • Außerhalb Geschäftszeiten → MFA + Alert
# Conditional Flow (Keycloak eingebaut):
Condition - User Configured:
  → Prüft ob User TOTP hat
  → Wenn ja: TOTP anfordern
  → Wenn nein: TOTP einrichten (enrollment flow)

SSO-Migration ohne User-Disruption

Phasen-Migration (Zero-Downtime)

Phase 1: IdP-Deployment (keine User-Auswirkung)

  • Keycloak installieren und konfigurieren
  • AD/LDAP-Sync einrichten
  • Test-Apps konfigurieren
  • Pilot-Gruppe: IT-Team (~10 User)

Phase 2: Neue Apps direkt mit SSO (kein Migration nötig)

  • Alle neuen Anwendungen ab jetzt → SSO-only
  • Kein Login-Formular mehr im Code!

Phase 3: Bestehende Apps migrieren

Just-In-Time Provisioning:

  • User loggt sich das erste Mal via SSO ein
  • App prüft: Konto mit dieser E-Mail vorhanden?
  • Wenn ja: bestehenden Account mit SSO verknüpfen
  • Wenn nein: neues Konto anlegen
  • User merkt (fast) nichts!
def handle_oidc_callback(user_info):
    email = user_info['email']
    user = User.find_by_email(email)
    if not user:
        user = User.create(email=email, name=user_info['name'])
    # SSO-Sub-ID speichern für zukünftige Logins:
    user.sso_sub = user_info['sub']
    user.save()
    session['user_id'] = user.id

Phase 4: Passwort-Logins deaktivieren

  • Nach 90 Tagen: lokale Passwörter deaktivieren
  • Exception-Prozess für Notfälle (Service-Accounts etc.)

Rollback-Plan

  • Feature-Flag: SSO_ENABLED = false → lokaler Login als Fallback
  • Nach erfolgreicher Migration: Feature-Flag entfernen

Nächster Schritt

Unsere zertifizierten Sicherheitsexperten beraten Sie zu den Themen aus diesem Artikel — unverbindlich und kostenlos.

Kostenlos · 30 Minuten · Unverbindlich

Artikel teilen

Über den Autor

Vincent Heinen
Vincent Heinen

Abteilungsleiter Offensive Services

M.Sc. IT-Sicherheit mit über 5 Jahren Erfahrung in offensiver Sicherheitsanalyse. Leitet die Durchführung von Penetrationstests mit Spezialisierung auf Web-Applikationen, Netzwerk-Infrastruktur, Reverse Engineering und Hardware-Sicherheit. Verantwortlich für mehrere Responsible Disclosures.

OSCP+ OSCP OSWP OSWA
Zertifiziert ISO 27001ISO 9001AZAVBSI

Cookielose Analyse via Matomo (selbst gehostet, kein Tracking-Cookie). Datenschutzerklärung