MSDN Magazin > Home > Ausgaben > 2007 > September >  Security Briefs: Anspruchsbasierte Identitä...
Security Briefs
Anspruchsbasierte Identität
Keith Brown

Codedownload verfügbar unter: SecurityBriefs2007_09.exe (206 KB)
Browse the Code Online
Die meisten Unternehmensanwendungen müssen über einige grundlegende Benutzersicherheitsfeatures verfügen. Eine Mindestvoraussetzung ist die Authentifizierung der Benutzer und möglicherweise auch die Autorisierung des Zugriffs auf bestimmte Features, sodass sie nur von berechtigten Benutzern verwendet werden können. Einige Anwendungen müssen über erweiterte Sicherheitsfeatures verfügen und die Aktivitäten der Benutzer überwachen. Bei Windows® sind diese Features Bestandteil des Betriebssystems und lassen sich in der Regel recht einfach in eine Anwendung integrieren. Bei Nutzung der integrierten Windows-Authentifizierung müssen Sie kein eigenes Authentifizierungsprotokoll erfinden oder eine Benutzerdatenbank verwalten. Mithilfe von Zugriffssteuerungslisten (Access Control Lists, ACLs), Identitätswechsel und Features wie z. B. Gruppen können Sie die Autorisierung mit sehr geringem Programmieraufwand implementieren. Tatsächlich gilt diese Empfehlung unabhängig von dem Betriebssystem, das Sie verwenden. Die enge Integration in die Sicherheitsfeatures in Ihrem Betriebssystem ist fast immer besser, als diese Features selbst neu zu erfinden.
Was geschieht jedoch, wenn Sie die Reichweite auf Benutzer ausdehnen möchten, die über keine Windows-Konten verfügen? Wie verhält es sich mit Benutzern, die überhaupt kein Windows-Betriebssystem verwenden? Mehr und mehr Anwendungen erfordern eine derartige Reichweite, was im Widerspruch zu den traditionellen Empfehlungen zu stehen scheint. In diesem Artikel stelle ich Ihnen das neue Identitätsmodell in Microsoft® .NET Framework 3.0 vor, das zur Lösung dieser und anderer Probleme entwickelt wurde.

Finden von Gemeinsamkeiten
Unter Windows ist die Anmeldeinformation, die am häufigsten für den Zugriff auf eine Unternehmensanwendung verwendet wird, einfach das Domänenkonto des Benutzers. Eine Anwendung mit integrierter Windows-Authentifizierung empfängt zur Repräsentation eines Clients ein Kerberos-Ticket. Eine Anwendung, bei der SSL verwendet wird, könnte stattdessen ein X.509-Zertifikat für den Client erhalten. Ein Kerberos-Ticket und ein X.509-Zertifikat sind zwei völlig verschiedene Dinge, und der Code im Betriebssystem zur Analyse, Überprüfung und letztendlichen Darstellung der Daten unterscheidet sich grundlegend. Wenn Sie jedoch einen Schritt zurückgehen und überlegen, was sie wirklich darstellen, werden Sie feststellen, dass diese beiden Anmeldeinformationen viele Gemeinsamkeiten aufweisen.
Stellen wir uns Folgendes vor, um diese Erläuterung konkreter zu gestalten und einige Begriffe einzuführen, die möglicherweise für einige Leser neu sind: Alice ist eine Benutzerin, die mithilfe ihres Windows-Domänenkontos auf einen Einkaufsdienst zugreifen möchte. Ihr Domänencontroller authentifiziert sie und erstellt ein Kerberos-Ticket, das eine Reihe von Sicherheits-IDs (SIDs) enthält. Diese SIDs repräsentieren das Benutzerkonto von Alice sowie die Domänengruppen, deren Mitglied sie ist, und sie werden im Ticket zusammen mit einer Signatur vom Domänencontroller eingebettet. Im Fachjargon ausgedrückt hat ein Aussteller (der Domänencontroller) einem Antragsteller (Alice) ein Sicherheitstoken übergeben, mit dem sie ihre Identität nachweisen kann.
Dasselbe Prinzip gilt, wenn Alice stattdessen ein Zertifikat verwendet. Ein Zertifikat ist nur eine andere Art von Sicherheitstoken. Der Aussteller ist in diesem Fall eine Zertifizierungsstelle (Certificate Authority, CA), und Alice ist die Antragstellerin. Sowohl das Kerberos-Ticket als auch das Zertifikat sind im Grunde signierte Erklärungen eines Ausstellers zu einem Antragsteller. Es handelt sich hierbei nur um zwei verschiedene Möglichkeiten, wie sich eine vertrauenswürdige Autorität für einen Antragsteller verbürgen kann. Jede signierte Erklärung kann als Sammlung von Ansprüchen (claims) betrachtet werden. Sie sollten sich dies folgendermaßen vorstellen: Der Domänencontroller gibt so genannte Ansprüche zur Identität von Alice ab, wenn er die Liste der SIDs in ihrem Ticket signiert. Jede SID wird zu einem Anspruch. Die Zertifizierungsstelle gibt Ansprüche zur Identität von Alice ab, wenn sie ihren Namen und öffentlichen Schlüssel signiert. Der Name und der öffentliche Schlüssel sind Beispiele für Ansprüche im Zertifikat.
Haben Sie Nachsicht mit mir, wenn die von mir verwendeten Begriffe ein wenig akademisch klingen, denn diese Abstraktionen erweisen sich in der Praxis als äußerst nützlich. Tatsächlich besteht das ganze Ziel dieses neuen Identitätsmodells darin, die Identität so zu abstrahieren, dass die Abhängigkeit von bestimmten Arten von Anmeldeinformationen verringert wird, ohne die Sicherheit Ihrer Anwendung zu beeinträchtigen. Durch Programmierung entsprechend dem Identitätsmodell von .NET Framework 3.0 können Sie nicht nur Kerberos-Tickets und Zertifikate, sondern auch SAML-Tokens (Security Assertion Markup Language) verarbeiten, wodurch einige wirklich interessante Identitätsarchitekturen ermöglicht werden, einschließlich Verbundidentität.

Traditionelle Darstellung der Clientidentität
Aber genug der abstrakten Rede, betrachten wir nun, wie diese Prinzipien in der Praxis funktionieren. Ich beginne mit einem einfachen WCF-Dienst (Windows Communication Foundation), der Windows-Anmeldeinformationen oder X.509-Zertifikate von Clients akzeptiert. Er macht eine einzelne Methode verfügbar, die keine Argumente annimmt, und wenn er von einem Client aufgerufen wird, gibt er Details zur Identität des Clients aus. Abbildung 1 zeigt diese Methode mit dem Namen „Hello“. In dieser Version wird die in .NET Framework 1.0 eingeführte traditionelle IIdentity-Schnittstelle zum Prüfen der Identität des Clients verwendet.
public void Hello() {
    ServiceSecurityContext sctx = ServiceSecurityContext.Current;
    if (null == sctx) {
        Console.WriteLine("Security context is null."+
                          "User must not be authenticated.");
        return;
    }
    IIdentity id = sctx.PrimaryIdentity;
    Console.WriteLine("Primary identity type: {0}", id.GetType().Name);
    Console.WriteLine("AuthenticationType: {0}", id.AuthenticationType);
    Console.WriteLine("IsAuthenticated: {0}", id.IsAuthenticated);
    Console.WriteLine("Name: {0}", id.Name);
}

Der erste Schritt besteht darin, ServiceSecurityContext für den Aufruf abzurufen. Auf diese Weise übermittelt WCF Details zur Identität des Clients an einen Dienst. Ich achte darauf, auf Nullwerte zu prüfen, die auf einen anonymen Client hinweisen würden. Dann rufe ich die PrimaryIdentity-Eigenschaft ab und gebe ihren Inhalt aus. Wenn ich das Beispiel für die Verwendung von Windows-Anmeldeinformationen konfiguriere, sieht die Ausgabe folgendermaßen aus:
Primary identity type: WindowsIdentity
AuthenticationType: NTLM
IsAuthenticated: True
Name: GROMIT\Alice
Der konkrete Typ ist in diesem Fall WindowsIdentity, der viel mehr Informationen zur Verfügung stellt als IIdentity. Durch Typumwandlung in WindowsIdentity könnte ich eine Liste der Gruppen, deren Mitglied Alice ist, sowie ihre Benutzer-SID abrufen und diese Informationen zum Nachschlagen ihres Benutzerdatensatzes in Active Directory® verwenden. Ich könnte sogar unter bestimmten Umständen ihre Identität annehmen.
Führen wir nun dasselbe Beispiel mit einer anderen WCF-Konfiguration aus. Diesmal lasse ich den Client ein X.509-Zertifikat senden.
Primary identity type: X509Identity
AuthenticationType: X509
IsAuthenticated: True
Name: CN=SampleClient; 33BB8518E4B7...
Die Name-Eigenschaft ist in diesem Fall eine Kombination aus dem allgemeinen Namen im Clientzertifikat und seinem Fingerabdruck. Wenn Sie weitere Informationen wünschen, beispielsweise Aussteller, Ablaufdatum usw., könnten Sie eine Typumwandlung in X509Identity versuchen, würden jedoch schnell feststellen, dass diese Klasse als intern markiert ist. Selbst wenn Sie ungeachtet dieser Einschränkung eine Typumwandlung durchführen könnten, hätten Sie es mit einem ganz anderen Programmiermodell als dem in WindowsIdentity zu tun. Dieses Problem wird durch das neue Identitätsmodell gelöst: Sie erhalten unabhängig von der Art der übermittelten Clientanmeldeinformationen ein einziges Programmiermodell. Im nächsten Abschnitt habe ich das Hello-Beispiel so umgeschrieben, dass Ansprüche unterstützt werden. Nachstehend erfahren Sie, wie die Identität aus der anspruchsbasierten Perspektive aussieht.

ClaimSets und Ansprüche
In Abbildung 2 sehen Sie eine neue Version von Hello, die mit Klassen in System.IdentityModel umgeschrieben wurde – dem neuen Identitätsmodell, das mit .NET Framework 3.0 eingeführt wurde. Bei diesem Modell wird ein Schritt zurück getan und die Identität mithilfe der Abstraktionen von Antragsteller, Aussteller und Anspruch behandelt. Aussteller geben Ansprüche zu den Antragstellern ab und signieren diese Ansprüche, um Sicherheitstokens zu erstellen. Jede Erklärung von einem bestimmten Aussteller wird als ClaimSet modelliert, bei dem es sich um eine Auflistung von Claim-Objekten handelt, die an eine Eigenschaft namens „Issuer“ gekoppelt sind, die einfach ein weiteres ClaimSet ist.
public void Hello() {
    ServiceSecurityContext sctx = ServiceSecurityContext.Current;
    if (null == sctx) {
        Console.WriteLine("Security context is null." +
                          "User must not be authenticated.");
        return;
    }
    AuthorizationContext actx = sctx.AuthorizationContext;

    Console.WriteLine();
    Console.WriteLine("HelloService.Hello sees {0} claim set(s):", 
        actx.ClaimSets.Count);
    Console.WriteLine();

    foreach (ClaimSet cs in actx.ClaimSets) {
        Console.WriteLine("------------------------------");
        displayClaimSet(cs);
    }
}

const string CLAIM_FORMAT_STRING = "{0, -5} {1, -15} {2}";

private void displayClaimSet(ClaimSet cs) {
    Console.WriteLine();
    Console.WriteLine(CLAIM_FORMAT_STRING, 
        "RIGHT", "CLAIM_TYPE", "RESOURCE");

    // display claims for subject of this claimset
    foreach (Claim c in cs) {
        displayClaim(c);
    }
    
    Console.WriteLine();
    Console.Write("ISSUED BY: ");

    // display claims for issuer of this claimset
    if (null == cs.Issuer) {
        Console.WriteLine("null");
    }
    else if (object.ReferenceEquals(cs, cs.Issuer)) {
        // self-asserted claims (issuer is the same as subject)
        Console.WriteLine("self");
    }
    else {
        displayClaimSet(cs.Issuer);
    }
}

// print a compact display of the supplied claim
private void displayClaim(Claim c) {
    string right;
    if (c.Right.Equals(Rights.Identity)) {
        right = "ID";
    }
    else if (c.Right.Equals(Rights.PossessProperty)) {
        right = "PP";
    }
    else right = c.Right;

    // make things a little more readable
    string claimType = c.ClaimType.Replace(
        "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/", 
        ".../");
    string value = stringizeResource(c.Resource);

    Console.WriteLine(CLAIM_FORMAT_STRING, right, claimType, value);
}

private string stringizeResource(object resource) {
    // sometimes the claim's value will simply be a string
    string stringResource = resource as string;
    if (null != stringResource) return stringResource + " (string)";

    SecurityIdentifier sid = resource as SecurityIdentifier;
    if (null != sid) return getAccountName(sid) + " (SecurityIdentifier)";

    X500DistinguishedName dn = resource as X500DistinguishedName;
    if (null != dn) return dn.Name + " (X500DistinguishedName)";

    RSACryptoServiceProvider key = resource as RSACryptoServiceProvider;
    if (null != key) return string.Format(
        "{0} bit RSA public key (RSACryptoServiceProvider)", 
        key.KeySize);

    // for anything else, just display the type name
    return string.Format("({0})", resource.GetType().Name);
}

private string getAccountName(SecurityIdentifier sid) {
    try {
        return sid.Translate(typeof(NTAccount)).ToString();
    }
    catch (IdentityNotMappedException) {
        return sid.Value;
    }
}

Bevor ich den Code ausführlich erläutere, möchte ich Ihnen einige Ausgaben zeigen, damit Sie ein Gefühl dafür bekommen, was ein ClaimSet darstellt. Sehen Sie sich Abbildung 3 an, die die Ausgabe der neuen Hello-Methode zeigt, wenn der Client Windows-Anmeldeinformationen verwendet. Die displayClaimSet-Hilfsmethode in Abbildung 2 gibt die Liste der Ansprüche im ClaimSet in Standardschreibweise aus und durchläuft dann rekursiv die Kette der Aussteller von unten nach oben und gibt der Reihe nach ihre ClaimSets aus. Es überrascht nicht, dass bei Windows-Anmeldeinformationen das ClaimSet hauptsächlich aus SIDs besteht. Wenn wir diese Ausgabe ein wenig genauer betrachten, können wir sehen, wie System.IdentityModel Ansprüche darstellt.
HelloService.Hello sees 1 claim set(s):

------------------------------

RIGHT CLAIM_TYPE RESOURCE
ID    .../sid    GROMIT\Alice (SecurityIdentifier)
PP    .../sid    GROMIT\Alice (SecurityIdentifier)
PP    .../name   GROMIT\Alice (string)
PP    .../sid    GROMIT\None (SecurityIdentifier)
PP    .../sid    Everyone (SecurityIdentifier)
PP    .../sid    BUILTIN\Users (SecurityIdentifier)
PP    .../sid    NT AUTHORITY\INTERACTIVE (SecurityIdentifier)
PP    .../sid    NT AUTHORITY\Authenticated Users (SecurityIdentifier)
PP    .../sid    NT AUTHORITY\This Organization (SecurityIdentifier)
PP    .../sid    LOCAL (SecurityIdentifier)
PP    .../sid    NT AUTHORITY\NTLM Authentication (SecurityIdentifier)

ISSUED BY:
RIGHT CLAIM_TYPE RESOURCE
ID    .../sid    S-1-5 (SecurityIdentifier)
PP    .../sid    S-1-5 (SecurityIdentifier)

ISSUED BY: self

Die Claim-Klasse hat drei Haupteigenschaften: ClaimType, Right und Resource. In Abbildung 3 sehen Sie zwei Arten von Ansprüchen: eine SID und einen Namen. Die ClaimTypes (Anspruchstypen) für diese sind URIs. Ich habe sie jedoch in der displayClaim-Hilfsmethode durch Entfernen des ersten Teils des URI verkürzt, da ansonsten die Ausgabe kaum lesbar wäre.
Es gibt keine zentrale Organisation, die alle möglichen Arten von Ansprüchen definiert, aber das ist auch nicht wirklich nötig. Es ist lediglich erforderlich, dass der Aussteller Anspruchstypen verwendet, die der Consumer versteht (in diesem Fall unsere Hello-Methode). In der Praxis werden Sie verschiedene Anspruchstypen sehen, die für die Verwendung in verschiedenen Kontexten definiert wurden. Wenn Sie beispielsweise das Informationskartenprofil lesen, werden Sie rund ein Dutzend Anspruchstypen sehen, die für die Verwendung mit Informationskarten definiert wurden.
Neben ClaimType macht die Claim-Klasse auch die Right-Eigenschaft verfügbar, die Ihnen oft beim Auffinden eines benötigten Anspruchs helfen kann. Schauen Sie sich erneut Abbildung 3 an, und achten Sie darauf, dass von allen angezeigten SIDs nur die erste SID die Identität des Antragstellers (Alice) darstellt. Die übrigen stellen Gruppen dar. Die RIGHT-Spalte in der Ausgabe zeigt den Wert von Claim.Right, bei dem es sich technisch gesehen um einen URI handelt, der in der displayClaim-Routine ebenfalls abgekürzt ist. System.IdentityModel definiert derzeit nur zwei Rechte: Identity und PossessProperty. Sie können sehen, dass WCF davon ausgeht, dass die SID des Benutzers in diesem Beispiel als Identitätsanspruch verwendet werden sollte. Von allen vorgelegten Ansprüchen ist dies der Anspruch, den Sie zur Identifizierung des Benutzers verwenden sollten. Alle anderen Ansprüche sind PossessProperty-Ansprüche.
Claim.Resource ist der Wert des Anspruchs. Mein Anspruchsausgabecode in Abbildung 2 weist eine Hilfsmethode auf – stringizeResource – die diese Werte zusammen mit ihren Typnamen in Standardschreibweise ausgibt. Wie Sie sehen, besitzt die Resource-Eigenschaft den Typ „object“ und kann in Abhängigkeit von der Art des Anspruchs nahezu beliebige Elemente enthalten. Die SID-Ansprüche in Abbildung 3 werden beispielsweise durch Instanzen der SecurityIdentifier-Klasse dargestellt, die in .NET Framework 2.0 eingeführt wurde.
Kurz gesagt, teilt Ihnen die Claim-Klasse den Typ und den Wert des Anspruchs mit und gibt Ihnen oft einen Hinweis darauf, ob der Anspruch zur eindeutigen Identifizierung eines Antragstellers oder Ausstellers verwendet werden kann. Das ClaimSet ist eine Auflistung dieser Claim-Objekte, die an einen Verweis auf das ClaimSet des Ausstellers gekoppelt ist. Beachten Sie in Abbildung 3, dass der Aussteller durch die SID „S-1-5“ dargestellt wird. Diese SID stellt die „NT-Autorität“ dar, was im Grunde das Betriebssystem bedeutet. Alice verwendet in diesem Beispiel ein lokales Konto, ansonsten würde der Aussteller durch die SID der Domäne von Alice identifiziert werden.
Wenn Sie die Kette der Aussteller von unten nach oben durchlaufen, finden Sie schließlich einen Aussteller, der selbstreferenzierend ist, d. h. er verweist auf sein eigenes ClaimSet. Die displayClaimSet-Methode in Abbildung 2 zeigt, wie Sie mit Object.ReferenceEquals darauf prüfen können. Dies geschieht, wenn Sie eine Stammautorität wie einen Domänencontroller oder eine Zertifizierungsstelle erreichen. Es ist wichtig, darauf zu achten. Andernfalls könnten Sie, wenn Sie die Ausstellerkette von unten nach oben durchlaufen, in einer unendlichen Schleife landen, wenn Sie den Stammaussteller erreichen.
Werfen Sie jetzt einen Blick auf Abbildung 4, die die Ausgabe von Hello zeigt, wenn Alice ein Zertifikat sendet. In diesem Fall verwendet WCF Claim.Right, um anzudeuten, dass der Fingerabdruck des Zertifikats zum Identifizieren des Clients verwendet werden kann. Da ich durch Verwendung selbstsignierter Zertifikate für meinen Beispielcode mogele, verweist die Issuer-Eigenschaft auf den Anspruchssatz (ClaimSet) des Antragstellers und gibt an, dass sie sich selbst ausgestellt hat. Beachten Sie, dass zur Darstellung der Ansprüche andere Klassen verwendet werden, die Gesamtstruktur ist jedoch dem vorherigen Beispiel sehr ähnlich.
HelloService.Hello sees 1 claim set(s):

------------------------------

RIGHT CLAIM_TYPE      RESOURCE
ID    .../thumbprint  (Byte[])
PP    .../thumbprint  (Byte[])
PP    .../x500distinguishedname CN=SampleClient (X500DistinguishedName)
PP    .../dns         SampleClient (string)
PP    .../name        SampleClient (string)
PP    .../rsa         2048 bit RSA public key (RSACryptoServiceProvider)

ISSUED BY: self

Wenn Sie schließlich Abbildung 5 betrachten, sehen Sie die Ausgabe von Hello, wenn der Client eine Informationskarte für die Authentifizierung beim Dienst verwendet. In diesem Fall wurden die E-Mail-Adresse und der Vorname des Clients als Ansprüche gesendet, und da eine persönliche Karte verwendet wurde, wird der Aussteller mit einem öffentlichen RSA-Schlüssel identifiziert. Wenn Sie mehr über Informationskarten sowie darüber erfahren möchten, was diese speziellen ClaimSets bedeuten, lesen Sie meine beiden letzten Artikel zu Windows CardSpace™ in den Ausgaben von Oktober und Mai 2006 des MSDN® Magazins (msdn.microsoft.com/msdnmag/issues/06/10/SecurityBriefs und msdn.microsoft.com/msdnmag/issues/06/05/SecurityBriefs).
HelloService.Hello sees 2 claim sets:

------------------------------

RIGHT CLAIM_TYPE      RESOURCE
PP    .../hash        (Byte[])

ISSUED BY: self
------------------------------

RIGHT CLAIM_TYPE       RESOURCE
PP    .../emailaddress alice@contoso.com (string)
PP    .../givenname    Alice (string)

ISSUED BY:
RIGHT CLAIM_TYPE      RESOURCE
ID    .../rsa         2048 bit RSA public key (RSACryptoServiceProvider)
PP    .../rsa         2048 bit RSA public key (RSACryptoServiceProvider)

ISSUED BY:
RIGHT CLAIM_TYPE      RESOURCE

ISSUED BY: self

Die Entwicklung von Systemen, bei denen die Clientidentität aus einer anspruchsbasierten Perspektive betrachtet wird, bietet mehrere Vorteile. Ein offensichtlicher Vorteil besteht darin, dass Sie ein einziges Programmiermodell für die Prüfung jeder Art von Clientidentität erhalten. Einfach auf der Grundlage von ClaimSet programmieren, und schon sind Sie fertig, oder? Nun, wenn Sie in der Praxis ein Zertifikat erwarten, halten Sie nach anderen Arten von Ansprüchen Ausschau, als wenn Sie Windows-Anmeldeinformationen oder eine persönliche Informationskarte erwarten. Tatsächlich gibt es von ClaimSet abgeleitete Klassen wie X509CertificateClaimSet und WindowsClaimSet, die das Ganze ein wenig erleichtern, wenn Sie wissen, welche Art von Anmeldeinformation Sie erwarten. Ein einziges Programmiermodell ist eine gute Sache. Sie werden jedoch die wahre Leistungsfähigkeit von Ansprüchen entdecken, wenn Sie erkennen, dass Sie mit diesem Konzept der Antragsteller, Aussteller und Ansprüche nahezu jede Art von Authentifizierungs- und Autorisierungssystem modellieren können. Dies lässt sich am besten anhand der Anspruchstransformation demonstrieren.

Anspruchstransformation und IAuthorizationPolicy
Stellen Sie sich vor, dass Sie einen WCF-Dienst erstellen müssen, der viele verschiedene Arten von Anmeldeinformationen akzeptiert, wobei Sie zur Autorisierung des Zugriffs auf Features Rollen verwenden möchten. Wenn der Benutzer beispielsweise über die Rolle „Manager“ verfügt, hat er auf mehr Features Zugriff, als wenn ihm nur die Rolle „Sachbearbeiter“ zugewiesen ist. Sie können Rollen anhand von Ansprüchen modellieren und mithilfe der Anspruchstransformation die zahlreichen verschiedenen Formate von Anmeldeinformationen verarbeiten, die Sie möglicherweise akzeptieren.
System.IdentityModel verfügt über eine Schnittstelle, die Ihnen die Transformation beliebiger Ansprüche ermöglicht. Ihr Name lautet IAuthorizationPolicy, und ihre Hauptmethode ist Evaluate, die eine Instanz der folgenden Klasse als Argument annimmt:
public abstract class EvaluationContext {
    // Methods
    protected EvaluationContext();
    public abstract void AddClaimSet(
        IAuthorizationPolicy policy, ClaimSet claimSet);
    public abstract void RecordExpirationTime(DateTime expirationTime);

    // Properties
    public abstract ReadOnlyCollection<ClaimSet> ClaimSets { get; }
    public abstract int Generation { get; }
    public abstract IDictionary<string, object> Properties { get; }
}
Dieser Kontext bietet Ihnen mehrere Optionen, für mich steht hier jedoch an erster Stelle, dass er es Ihnen ermöglicht, die von einem Client übergebenen Anspruchssätze zu überprüfen, eine bestimmte Zuordnung vorzunehmen und dann Ihren eigenen Anspruchssatz hinzuzufügen. Angenommen, der Client hat ein Zertifikat gesendet. Anhand Ihrer Autorisierungsrichtlinie könnte der Namen des Antragstellers im Zertifikat einem Satz von Rollen zugeordnet werden, und diese Rollen könnten als Ansprüche in einem neuen ClaimSet verfügbar gemacht werden, das Ihre Anwendung als Aussteller angibt. Wenn der Benutzer stattdessen Windows-Anmeldeinformationen bereitgestellt hat, könnten Sie seine Gruppen mithilfe eines ähnlichen ClaimSets denselben Rollen zuordnen. Nachdem diese Transformation stattgefunden hat, muss Ihr Dienst einfach nach dem lokal ausgestellten ClaimSet suchen und dann die Rollen des Benutzers von dort lesen.
WCF bietet ausgezeichnete Unterstützung für dieses Modell. In der Konfigurationsdatei Ihres Diensts können Sie ein ServiceAuthorization-Verhalten einbinden, das WCF anweist, Ihre Implementierung von IAuthorizationPolicy aufzurufen, bevor die Methode Ihres Diensts (zum Beispiel Hello) aufgerufen wird. Dadurch können Sie den Code zentralisieren, der die Vielzahl von Clientanmeldeinformationen verarbeitet, die Ihr Dienst akzeptiert. Wenn Sie die Verwendung rollenbasierter Sicherheit planen, können Sie einen Schritt weitergehen und das PrincipalPermissionMode-Attribut für das ServiceAuthorization-Verhalten auf „Custom“ setzen und dann eine Instanz von IPrincipal bereitstellen, die Ihre Rollen über den Auswertungskontext enthält. Dadurch können Sie das PrincipalPermission-Attribut bei den Methoden Ihres Diensts verwenden, um auf Grundlage von Rollen Zugriff zu gewähren. Dieses Verhalten sieht in der Konfigurationsdatei wie folgt aus:
<serviceAuthorization principalPermissionMode="Custom">
  <authorizationPolicies>
    <add policyType="MyAuthorizationPolicy, MyAssembly"/>
  </authorizationPolicies>
</serviceAuthorization>
So übermitteln Sie WCF einen benutzerdefinierten Prinzipal mithilfe des Auswertungskontexts:
string[] roles = lookupRolesForUser(evaluationContext);
GenericIdentity id = new GenericIdentity(clientName);
evaluationContext.Properties["Principal"] =
    new GenericPrincipal(id, roles);
WCF richtet dann Thread.CurrentPrincipal mit dem angegebenen Prinzipal ein, bevor die Methode des Diensts aufgerufen wird. Ich erwähne diesen Trick mit dem benutzerdefinierten Prinzipal nur, weil viele Programmierer gern mit PrincipalPermission arbeiten und dies praktisch finden werden. Kehren wir jedoch zu den Ansprüchen zurück, da darin die eigentliche Leistungsfähigkeit liegt. Wie würden Sie also vorgehen, um ein eigenes ClaimSet zur Darstellung eines Satzes von Rollen zu erstellen? Obwohl ein vollständiges Beispiel in dem für diesen Artikel auf der MSDN Magazin-Website verfügbaren Beispielcode zu finden ist, folgt hier ein Auszug, der Ihnen die entsprechende Vorgehensweise zeigt:
string clientName = getClientNameFromClaims(EvaluationContext);
Claim c1 = new Claim(ClaimTypes.Name, clientName, Rights.Identity);
Claim c2 = new Claim("urn:role", "foo", Rights.PossessProperty);
Claim c2 = new Claim("urn:role", "bar", Rights.PossessProperty);
ClaimSet newClaimSet = new DefaultClaimSet(
    DefaultClaimSet.System, c1, c2, c3);
Ich habe einen bekannten Anspruchssatz namens „System“ verwendet, um anzugeben, dass es sich hierbei um einen internen Anspruchssatz handelt, der von meinem eigenen Code ausgestellt wird. Der System-Anspruch hat nichts mit dem SYSTEM-Konto in Windows zu tun. Er ist nur ein einfacher Anspruchssatz, nach dem es sich leicht suchen lässt. Beachten Sie, dass ich Rollen als PossessProperty-Ansprüche mit einem benutzerdefinierten URI hinzufüge. Sie sollten sich wahrscheinlich für den Anspruchstyp-URI etwas Besseres einfallen lassen als „urn:role“, da jedoch System.IdentityModel keinen URI für einen Rollenanspruch definiert, können Sie einen Typ wählen, der für Ihre Anwendung sinnvoll ist.
Nachdem Sie den neuen Anspruchssatz erstellt haben, können Sie ihn dem Auswertungskontext über die AddClaimSet-Methode hinzufügen. Jetzt muss der Code in Ihrer Anwendung einfach nach diesem von Ihnen ausgestellten ClaimSet suchen. Dann erhält er eine Liste von Rollen für den Benutzer, unabhängig davon, ob er ein Zertifikat oder ein Windows-Konto für die Authentifizierung verwendet hat.

Sicherheitstokendienst
Stellen Sie sich einen Moment lang vor, dass Sie sich die Zeit genommen haben, um eine große Implementierung von IAuthorizationPolicy zu erstellen, die wie oben beschrieben verschiedene Arten von Anmeldeinformationen einem einfachen Satz rollenbasierter Ansprüche zuordnet. Möglicherweise verfügen Sie über viele verschiedene Dienste, für die dieses Feature erforderlich ist. Eine Methode für die Wiederverwendung dieses Codes wäre, die Klasse in eine eigene Assembly einzufügen und sie mit jedem Dienst zu verbinden, von dem sie benötigt wird.
Ein interessanterer Ansatz besteht darin, Ihre eigenen Sicherheitstokens auszustellen, indem Sie Ihre Autorisierungsrichtlinie als Grundlage für die Implementierung eines Sicherheitstokendiensts (Security Token Service, STS) verwenden, der Sicherheitstokens über ein Webdienstprotokoll namens „WS-Trust“ ausstellt. Ein STS kann ein Sicherheitstoken eines Typs annehmen und ein Token eines anderen Typs ausstellen und alle erforderlichen Anspruchstransformationen verarbeiten, beispielsweise die Zuordnung eines Clientzertifikats zu einem SAML-Token, das einen Satz von Rollen enthält, die von einer Anwendung verstanden werden. In diesem Szenario würden die Anwendungen in Ihrem Unternehmen auf ein von Ihrem eigenen STS ausgestelltes (und signiertes) ClaimSet warten.
Zum Zeitpunkt des Verfassens dieses Artikels im Juni 2007 ist die Implementierung eines STS möglich, aber nicht gerade einfach. (Microsoft arbeitet daran, diesen Vorgang einfacher zu gestalten.) Unabhängig davon, ob Sie einen eigenen STS implementieren, erleichtert Ihnen die Erstellung einer Anwendung, die Ansprüche unterstützt, die Nutzung eines STS, falls Ihnen einmal ein derartiger Dienst zur Verfügung steht.

Vertrauensstellung und Verbundidentität
Bei WCF und anderen Kommunikationsframeworks wird Kryptografie verwendet, um sicherzustellen, dass der Absender eines Sicherheitstokens tatsächlich der Antragsteller ist und dass die Ansprüche im Token von dem im Token genannten Aussteller signiert wurden. All diese ausgeklügelten Mechanismen können jedoch nicht einschätzen, wie sehr Sie dem Aussteller vertrauen. Wenn Sie ihm nicht vertrauen, werden Sie auch den Erklärungen nicht vertrauen, die er zu den jeweiligen Antragstellern abgibt! Daher wird der Aussteller stets identifiziert, wenn Sie einen Anspruchssatz empfangen, und dies ist der erste Aspekt, auf den Sie bei der Verarbeitung eines Anspruchssatzes achten sollten.
Es ist einfach, Code zu schreiben, der Tokens von einem einzigen vertrauenswürdigen Aussteller annimmt. Stellen Sie nur sicher, dass der Anspruchssatz, den Sie empfangen haben, von der einen Autorität ausgestellt wurde, der Sie vertrauen. Dann können Sie diese Ansprüche verwenden, um Sicherheitsentscheidungen zu treffen. Sie haben im Wesentlichen die Verantwortung für schwierige Aufgaben wie die Zuordnung von Benutzern zu Rollen und die Verarbeitung verschiedener Arten von Sicherheitstokens an den STS delegiert.
Stellen Sie sich nun vor, dass Sie noch einen Schritt weitergehen möchten. Wie wäre es, wenn Ihr STS nicht nur Windows-Anmeldeinformationen und X.509-Zertifikate, sondern auch signierte SAML-Tokens akzeptieren würde, die von einem STS bei einem vertrauenswürdigen Partner ausgestellt wurden? Dies führt zum äußerst leistungsstarken Bereich der Verbundidentität. Statt sich um die Verwaltung von Benutzerkonten für externe Benutzer von Partnerunternehmen Gedanken machen zu müssen, können Sie signierte Erklärungen von diesen Partnern in Form von SAML-Tokens akzeptieren. Ich habe die zahlreichen Vorteile der Automatisierung dieser Arten von Business-to-Business-Vertrauensstellungen in meinem Artikel zu Active Directory-Verbunddiensten (msdn.microsoft.com/msdnmag/issues/06/11/SingleSignOn) erläutert.
Wenn Sie darüber nachdenken, läuft das Konzept der Verbundidentität letzten Endes auf die Anspruchstransformation hinaus. Der STS des Partners erleichtert dem Client die Authentifizierung, indem er die für sein Betriebssystem und seine Plattform natürlichsten Anmeldeinformationen als Eingabe akzeptiert. Wenn der Client beispielsweise Windows ausführt, könnte der STS Kerberos zu seiner automatischen Authentifizierung verwenden und ein SAML-Token ausstellen. Ein anderes Partnerunternehmen führt möglicherweise ein ganz anderes Betriebssystem aus, bei dem strenge Authentifizierungsprotokolle verwendet werden. Der STS in diesem Unternehmen würde jedoch diese Protokolle zur nahtlosen Authentifizierung des Benutzers und zur Ausstellung eines SAML-Tokens verwenden. Gleichzeitig profitiert der Benutzer von den Vorteilen des einmaligen Anmeldens, selbst wenn er Anwendungen wie Ihre von verbundenen Partnerunternehmen verwendet.
Der STS in Ihrem Unternehmen nimmt Ihren Anwendungen Arbeit ab. Ihr STS stellt sicher, dass nur SAML-Tokens von vertrauenswürdigen Partnern akzeptiert werden, und stellt dann ein neues Token für die Verwendung mit Ihrer Anwendung aus. Ihre Anwendung muss während ihres gesamten Lebenszyklus lediglich nach ClaimSets suchen, die vom STS Ihres Unternehmens ausgestellt wurden. Abbildung 6 zeigt den Nachrichtenfluss in diesem Verbundsystem. Der Client fordert zuerst beim Konto-STS seines Unternehmens ein SAML-Token an. Er sendet dieses Token an den Ressourcen-STS, um ein neues SAML-Token zu erhalten, das vom Dienst verwendet wird.
Abbildung 6 Verbundauthentifizierungs-Meldungsfluss 
Sicherheitstokendienste sollen die Lücke zwischen Sicherheits- oder Technologiebereichen schließen. Durch Transformation eines Tokentyps in einen anderen und gleichzeitige Transformation der Ansprüche innerhalb dieser Tokens bieten sie eine hervorragende Möglichkeit, Authentifizierungs- und Autorisierungsrichtlinien zu zentralisieren.
Ich werde zu einem späteren Zeitpunkt näher auf diese Modelle der Verbundidentität eingehen. Fürs Erste hoffe ich, dass Sie mir vertrauen, wenn ich sage, dass Lösungen für Verbundidentität sich immer mehr durchsetzen und Sie eine Übereinstimmung mit diesem Modell erreichen können, wenn Sie bereits heute Anwendungen erstellen, die Ansprüche unterstützen. In der Zukunft können Sie in diesem Bereich mit weiteren Innovationen rechnen.

Bis zum nächsten Mal
Durch Erstellen von Anwendungen, die Ansprüche unterstützen, sind Sie auf zukünftige Entwicklungen im Bereich des Identitätsmodells auf der Windows-Plattform vorbereitet. Sie können dann beispielsweise nicht nur herkömmliche Tokenformate wie Windows-Anmeldeinformationen oder X.509-Zertifikate, sondern auch Informationskarten akzeptieren. Darüber hinaus sind Sie auch besser auf die Implementierung von Verbundidentität vorbereitet, wenn die Zeit dafür reif ist.
Der erste Schritt, um sich mit Ansprüchen vertraut zu machen, besteht darin, einige Zeit mit der Untersuchung von System.IdentityModel zu verbringen, dem neuen anspruchsbasierten Programmiermodell, das in .NET Framework 3.0 eingeführt wurde. Verwenden Sie den Beispielcode für diesen Artikel als Einstieg, um zu erfahren, wie Sie die Leistungsfähigkeit anspruchsbasierter Identität für Ihre eigenen Anwendungen nutzen können.

Senden Sie Fragen und Kommentare für Keith Brown in englischer Sprache an briefs@microsoft.com.


Keith Brown ist Mitgründer von Pluralsight, einem wichtigen Microsoft .NET-Schulungsanbieter. Er ist Autor des Pluralsight-Kurses „Applied .NET Security“ sowie mehrerer Bücher, unter anderem „The .NET Developer's Guide to Windows Security“, das als Druckversion und im Internet erhältlich ist. Weitere Informationen finden Sie unter www.pluralsight.com/keith.

Page view tracker