MSDN Magazin > Home > Ausgaben > 2007 > August >  Foundations: Deklarative WCF-Sicherheit
Foundations
Deklarative WCF-Sicherheit
Juval Lowy

Codedownload verfügbar unter: Foundations2007_08.exe (231 KB)
Browse the Code Online
Sicherheit ist der bei weitem komplizierteste Bereich von Windows® Communication Foundation (WCF). Bei jedem WCF-Vorgangsaufruf auf der Dienstseite unterliegt die Sicherheit dem Dienstvertrag, dem Vorgangsvertrag, dem Fehlervertrag (falls vorhanden), dem Dienstverhalten, dem Vorgangsverhalten, der Hostkonfiguration und der Methodenkonfiguration und ihrem Code. Jedes dieser Elemente kann, wie in Abbildung 1 dargestellt, bis zu einem Dutzend oder mehr sicherheitsbezogene Eigenschaften haben, die die Sicherheitseigenschaften von ServiceHostBase (die Basis von ServiceHost) zeigen.
Abbildung 1 Sicherheitsbezogene Eigenschaften von ServiceHostBase (Klicken Sie zum Vergrößern auf das Bild)
Es müssen nicht nur beängstigend viele Details bewältigt werden, sondern zwischen den verschiedenen Teilen bestehen auch komplizierte Beziehungen, was zu einer überwältigenden Anzahl möglicher Permutationen führt. Das Ganze wird noch dadurch erschwert, dass nicht alle Kombinationen zulässig sind oder unterstützt werden, und nicht alle zulässigen Kombinationen sind konsistent oder logisch.
Es überrascht nicht, dass das Programmiermodell sehr komplex ist, und es gibt sowohl auf der Anwendungs- als auch auf der Geschäftsebene schwerwiegende Implikationen, wenn Fehler gemacht werden. Dies ist das Ergebnis der zugrunde liegenden Komplexität des Ziels – die Sicherstellung sicherer Kommunikation über Lieferanten-, Vertrauens-, Plattform- und Technologiegrenzen hinweg. Es ist bei weitem keine unbedeutende Aufgabe. Doch im Gegensatz zu den anderen Aspekten von WCF wie z. B. Transaktionen, Synchronisierung und Instanzen hat die Sicherheit keine Attribute oder eine einzelne Möglichkeit aus einer Hand zum Durchführen der Konfiguration. Bei der Standardversion müssen Entwickler das unbearbeitete Sicherheitsmodell zu beherrschen lernen.
Es gibt gute Gründe dafür, kein Programmiermodell auf höherer Ebene für die Sicherheit zu liefern: Ein solches Modell könnte bei weitem nicht alle Anwendungen und Szenarios ansprechen, aber dennoch wäre es unklug gewesen, Anwendungen auszulassen, da die Sicherheit in allen Szenarios wichtig ist.
Da es nicht möglich ist, ein einheitliches Sicherheitsmodell auf hoher Ebene zu schaffen, das für alle Anwendungen optimiert wurde, stellte sich folgende Frage: Gibt es ein Modell, das für die meisten Anwendungen gut genug ist? Obwohl ein umfassendes Framework, das alle Szenarios anspricht, unwahrscheinlich ist, sollte eine Lösung erstellt werden, die das Konfigurieren von Sicherheitseinstellungen für die Mehrheit der Fälle erleichtern würde. Dieser Artikel stellt das deklarative Sicherheitsframework vor.
Für den Dienst wurde ein Sicherheitsattribut bereitgestellt und für den Client ein paar Hilfsklassen und sichere Proxyklassen. Durch das deklarative Framework kann sich die Sicherheitskonfiguration mit den anderen Aspekten der WCF-Konfiguration messen. Es sollte ein deklaratives Modell entwickelt werden, das sich leicht verwenden lässt, wobei es nicht erforderlich ist, die vielen Details der Sicherheit zu verstehen. Als Entwickler müssen Sie nur das richtige Szenario auswählen, und das Framework automatisiert die Konfiguration. Außerdem erfordert das Framework die richtigen Optionen und setzt bewährte Methoden durch. Gleichzeitig sollte das Modell Granularität und Steuerung der zugrunde liegenden Konfiguration bieten, falls eine solche Steuerung erforderlich ist.
In diesem Artikel geht es um die Zielszenarios, die Implementierung des Frameworks und seine Verwendung. Es werden auch einige interessante WCF-Programmiertechniken vorgestellt, die die WCF-Erweiterbarkeit demonstrieren. (Sie können das Sicherheitsframework von der MSDN® Magazine-Website herunterladen.)

Sicherheitsszenarios
Das Framework unterstützt fünf Schlüsselszenarios sowie einige leichte Abweichungen, die Sicherheitsanforderungen die Mehrzahl der heutigen Anwendungen ansprechen. Bei diesen Szenarios handelt es sich um folgende:
  • Intranetanwendung
  • Internetanwendung
  • Business-to-Business-Anwendung
  • Anonyme Anwendung
  • Keine Sicherheit
Wenn Sie ein anderes Szenario ansprechen müssen, können Sie meinen Ansatz verwenden, um die erforderlichen Sicherheitsaspekte und Einstellungen mithilfe des beigefügten Quellcodes abzuleiten.

Intranetanwendung
In der Intranetanwendung verwenden sowohl der Client als auch der Dienst WCF, und beide werden auf demselben Intranet bereitgestellt. Client und Dienst sind durch keine Firewall getrennt, und Sie können Windows-basierte Sicherheit für die Übertragungssicherheit und Authentifizierung verwenden. Windows-Konten und -Gruppen speichern die Anmeldeinformationen des Clients, und Sie können prinzipalbasierte, rollenbasierte Sicherheit zum Überprüfen der Mitgliedschaft anhand von Windows Gruppen verwenden.
Für dieses Szenario ermöglicht das Framework die Verwendung der Intranetbindungen, nämlich NetTcpBinding, NetNamedPipeBinding und NetMsmqBinding. Die verwendeten Bindungen sind für die höchste Schutzebene konfiguriert (verschlüsselt und signiert). Sie können sich auf den Transportmodus für Übertragungssicherheit verlassen, da die Aufrufe ausnahmslos von Punkt zu Punkt erfolgen. Außerdem können Sie die Identitätswechselebene steuern und sogar automatischen Identitätswechsel für alle Vorgänge anfordern.

Internetanwendung
Im Internetszenario dürfen der Client oder der Dienst WCF und auch Windows nicht verwenden. Zudem hat eine Internetanwendung in der Regel eine verhältnismäßig große Anzahl von Clients, die den Dienst aufrufen. Diese Clientaufrufe haben ihren Ursprung außerhalb der Firewall. Sie müssen sich auf HTTP für den Transport verlassen, und mehrere Zwischenstellen sind wahrscheinlich.
In einer Internet Anwendung ist es in der Regel nicht angebracht, Windows-Konten und -Gruppen für Anmeldeinformationen zu verwenden. Stattdessen muss die Anwendung auf einen benutzerdefinierten Anmeldeinformationsspeicher zugreifen. (Dennoch könnten Sie Windows-Sicherheit verwenden, wie später noch demonstriert wird.) Hier verwendet das Framework Nachrichtensicherheit für die Übertragungssicherheit, womit Sicherheit für alle Zwischenstellen bereitgestellt wird. Der Client stellt Anmeldeinformationen in Form eines Benutzernamens und Kennworts bereit.
Für dieses Szenario sollten Sie WSHttpBinding oder WSDualHttpBinding verwenden. Wenn Sie zudem eine Intranetanwendung haben, die NetTcpBinding verwendet, aber Windows-Sicherheit für Benutzerkonten und Gruppen nicht verwenden wollen, können Sie Nachrichtensicherheit und benutzerdefinierte Benutzernamen und Kennwörter im Intranet verwenden.
Da im Internetszenario die an den Service gesendete Nachricht des Clients über einfaches HTTP übertragen wird, ist es wichtig, den Inhalt der Nachricht (sowohl die Anmeldeinformationen des Clients und den Nachrichtentext) zu schützen, indem sie mit einem vom Dienst bereitgestellten Zertifikat verschlüsselt wird. WCF unterstützt mehrere Optionen für den Erhalt dieses Zertifikats. Der Cliententwickler kann das Dienstzertifikat beispielsweise mit einem Out-of-Band-Mechanismus wie eine E-Mail-Nachricht oder über eine öffentliche Webseite erhalten.
Mithilfe des WCF-Konfigurationsschemas kann der Client in seiner Konfigurationsdatei (speziell im Endpunktverhaltensabschnitt) ausführliche Information zum Dienstzertifikat enthalten, z. B. wo es auf der Clientseite gespeichert wird und wie es gesucht werden kann. Dies ist die bei weitem sicherste Option aus der Sicht des Clients, da jeder Versuch, die Adressenauflösung des Clients zu untergraben und den Aufruf an einen schädlichen Dienst umzuleiten, versagen wird, da der andere Dienst nicht über das richtige Zertifikat verfügt. Dies ist jedoch die am wenigsten flexible Option, da der Clientadministrator jedes Mal, wenn der Client mit einem anderen Dienst interagieren muss, die Konfigurationsdatei des Clients überarbeiten muss.
Eine angemessene Alternative zum ausdrücklichen Verweis auf die Zertifikate aller Dienste, mit denen der Client interagieren könnte, ist das Speichern dieser Zertifikate im Clientzertifikatspeicher vertrauenswürdiger Personen und die Anweisung von WCF, Aufrufe nur an einen Dienst zu ermöglichen, dessen Zertifikat in diesem Ordner enthalten ist. Der Client muss in diesem Fall das Dienstzertifikat zur Laufzeit als Teil der anfänglichen Voraufrufsverhandlung erhalten, es prüfen, um festzustellen, ob es im Speicher vertrauenswürdiger Personen enthalten ist, und es, wenn dies der Fall ist, zum Schutz der Nachricht verwenden. Diese Form der Zertifikatsverhandlung ist das Standardverhalten der WS-Bindungen. Für das Internetszenario verwende ich die Zertifikatsverhandlung gekoppelt mit der Überprüfung des Dienstzertifikats im Clientspeicher vertrauenswürdiger Personen. Dies wird als Peer-Trust-Überprüfung bezeichnet.
Wenn die Benutzername- und Kennwortanmeldeinformationen bei dem Dienst eingegangen sind, werden sie vom Host authentifiziert. Standardmäßig werden die Anmeldeinformationen als Windows-Anmeldeinformationen (anhand der Hostdomäne oder des Computers) authentifiziert, aber Sie können den Dienst so konfigurieren, dass die ASP.NET-Mitgliedschaftsanbieter verwendet werden.
Der Dienst kann prinzipalbasierte, rollenbasierte Sicherheit verwenden: Wenn es sich bei den Anmeldeinformationen um Windows-Anmeldeinformationen handelt, müssen die spezifizierten Rollen Windows-Gruppen sein. Wenn die ASP.NET-Anbieter verwendet werden, dann werden die Rollen mithilfe der ASP.NET-Rollenanbieter überprüft.

Business-to-Business-Anwendung
Im Business-to-Business-Szenario sind der Dienst und seine Clients verschiedene Geschäftsentitäten. Sie verwenden nicht dieselben Anmeldeinformationen oder Konten, und die Kommunikation zwischen ihnen ist in der Regel der Öffentlichkeit nicht zugänglich. Es sind verhältnismäßig wenige Clients, die mit dem Dienst interagieren, und der Client kann mit dem Dienst erst interagieren, wenn ein umfangreiches Geschäftsabkommen und andere Bedingungen erfüllt wurden.
Statt Windows-Konten oder -Benutzernamen zu verwenden, identifizieren sich die Clients gegenüber dem Dienst mithilfe von X509-Zertifikaten, die dem Dienst in der Regel bereits bekannt sind. Der Client oder der Dienst dürfen WCF oder auch Windows nicht verwenden. Clientaufrufe kommen aus dem Bereich außerhalb der Firewall, Sie müssen sich zum Transport auf HTTP verlassen, und mehrere Zwischenstellen sind möglich.
Für das Business-to-Business-Szenario ermöglicht mein Framework die Verwendung von BasicHttpBinding, WSHttpBinding oder WSDualHttpBinding. Sie müssen Nachrichtsicherheit für die Übertragungssicherheit verwenden, um Sicherheit für alle Zwischenstellen bereitzustellen. Die Nachricht wird genau wie im Internetszenario mithilfe eines dienstseitigen Zertifikats geschützt. Doch im Gegensatz zum Internetszenario stellen die Clients in diesem Szenario Anmeldeinformationen in Form eines Zertifikats bereit. Der Client stellt dem Proxy das Zertifikat in der Konfigurationsdatei (oder programmgesteuert) bereit, und der Proxy bündelt das Zertifikat in der Nachricht und sendet es an den Dienst.
WCF bietet dem Dienstadministrator eine Reihe von Optionen zum Überprüfen der vom Client gesendeten Zertifikate. Wenn das Zertifikat geprüft wurde, gilt der Client als authentifiziert. Mein Framework verwendet Peer-Trust-Überprüfung auf der Dienstseite. Daher sollte der Dienstadministrator alle Zertifikate des Clients, die mit dem Dienst interagieren dürfen, im Speicher vertrauenswürdiger Personen auf dem lokalen Computer des Diensts installieren.
Beim Eingang des Clientzertifikats beim Dienst wird der Client authentifiziert, wenn das Zertifikat im vertrauenswürdigen Speicher gefunden wird. Standardmäßig kann der Dienst keine prinzipalbasierte, rollenbasierte Sicherheit verwenden. Das liegt daran, dass die bereitgestellten Anmeldeinformationen, nämlich das Zertifikat des Clients, weder Windows- noch ASP.NET-Benutzerkonten zugeordnet sind. Da Business-to-Business-Endpunkte oft für einen kleinen Satz an Clients oder sogar einen bestimmten Client vorgesehen sind, dürfte dieser Mangel an Autorisierungssupport kein Problem darstellen. Wenn Sie andererseits die Clients weiterhin autorisieren möchten, nutzt mein Framework die ASP.NET-Rollenanbieter zur Autorisierung, obwohl der Mitgliedschaftsanbieter nicht zur Authentifizierung verwendet wurde. Diese Möglichkeit zur separaten Verwendung der Anbieter war ein Hauptziel für das ASP.NET-Anbietermodell in WCF.

Anonyme Anwendung
Im anonymen Szenario greifen die Clients auf den Dienst zu, ohne Anmeldeinformationen anzuzeigen – sie sind unbekannt. Andererseits ist für die Clients und den Dienst sichere Nachrichtübertragung erforderlich, die vor Manipulation und Abfangen von Daten geschützt ist. Sowohl für eine mit dem Internet verbundene als auch für eine Intranet-basierte Anwendung kann es erforderlich sein, anonymen, aber gleichzeitig sicheren Zugriff bereitzustellen.
Das anonyme Szenario kann eine beliebige Anzahl Clients haben. Die Clients können sich über HTTP oder TCP verbinden. Zum Sichern der Nachricht und aufgrund der Tatsache, dass die Clients möglicherweise Aufrufe über das Internet mit mehreren Zwischenstellen senden, sollten Sie Nachrichtensicherheit verwenden. Für dieses Szenario können Sie NetTcpBinding, WSHttpBinding, WSDualHttpBinding und NetMsmqBinding – eine Mischung sowohl aus Internet- als auch Intranetbindungen – verwenden. Es ist zu beachten, dass Sie BasicHttpBinding, NetNamedPipeBinding, NetPeerTcpBinding oder WSFederationHttpBinding nicht verwenden können, da diese Bindungen die Nachrichtensicherheit entweder nicht unterstützen oder nicht die Tatsache unterstützen, dass keine Anmeldeinformationen in der Nachricht enthalten sind.
Beachten Sie, dass im anonymen Szenario ebenfalls keine Clientauthentifizierung durchgeführt wird und der Client für den Proxy keine Anmeldeinformationen bereitstellen muss. Zur Dienstauthentifizierung gegenüber dem Client und zum Nachrichtenschutz muss der Dienst sein Zertifikat bereitstellen. Da die Clients anonym (und nicht authentifiziert) sind, sind Autorisierung und rollenbasierte Sicherheit ausgeschlossen.

Keine Sicherheit
In diesem letzten Szenario schaltet Ihre Anwendung die Sicherheit vollständig aus. Der Dienst verlässt sich nicht auf die Übertragungssicherheit und authentifiziert oder autorisiert seine Aufrufer nicht. Der Dienst kann eine beliebige Anzahl von Clients akzeptieren, und Clients müssen für den Proxy keine Anmeldeinformationen bereitstellen. Da die Clients anonym (und nicht authentifiziert) sind, sind Autorisierung und rollenbasierte Sicherheit ausgeschlossen. Sowohl Internet- als auch Intranetdienste können so konfiguriert werden, dass keine Sicherheit vorhanden ist, aber natürlich ist ein solcher Dienst völlig ungeschützt, und im Allgemeinen ist eine geschäftliche Rechtfertigung erforderlich, um völlig auf die Sicherheit zu verzichten.
Abbildung 2 und Abbildung 3 zeigen eine Zusammenfassung der Schlüsselelemente der Sicherheitsszenarios. Abbildung 2 listet die Bindungen auf, die in jedem Szenario verwendet werden, und Abbildung 3 zeigt, wie sich die Sicherheitsaspekte auf das jeweilige Szenario beziehen.

Aspekt Intranet Internet Business-to-Business Anonym Keine Sicherheit
Transport Ja Nein Nein Nein Nein
Nachricht Nein Ja Ja Ja Nein
Dienstauthentifizierung Windows Zertifikat Zertifikat Zertifikat Nein
Clientauthentifizierung Windows ASP.NET Zertifikat Nein Nein
Autorisierung Windows ASP.NET Nein/ASP.NET Nein Nein
Identitätswechsel Ja Nein Nein Nein Nein

Bindung Intranet Internet Business-to-Business Anonym Keine Sicherheit
BasicHttpBinding Nein Nein Ja Nein Ja
NetTcpBinding Ja Ja Nein Ja Ja
NetPeerTcpBinding Nein Nein Nein Nein Ja
NetNamedPipeBinding Ja Nein Nein Nein Ja
WSHttpBinding Nein Ja Ja Ja Ja
WSDualHttpBinding Nein Ja Ja Ja Ja
NetMsmqBinding Ja Nein Nein Ja Ja

Das deklarative Sicherheitsframework
Abbildung 4 listet die Definition von SecurityBehaviorAttribute und die ServiceSecurity-Aufzählung auf. ServiceSecurity definiert die fünf Szenarios, die von meinem Framework unterstützt werden.
public enum ServiceSecurity
{
   None,
   Anonymous,
   BusinessToBusiness,
   Internet,
   Intranet
}

[AttributeUsage(AttributeTargets.Class)]
public class SecurityBehaviorAttribute : Attribute,IServiceBehavior
{
   public SecurityBehaviorAttribute(ServiceSecurity mode);
   public SecurityBehaviorAttribute(ServiceSecurity mode,
      string serviceCertificateName); 
   public SecurityBehaviorAttribute(ServiceSecurity mode,
      StoreLocation storeLocation,
      StoreName storeName,
      X509FindType findType,
      string serviceCertificateName);
   public bool ImpersonateAll {get;set;}
   public string ApplicationName {get;set;}
   public bool UseAspNetProviders {get;set;}
}

Beim Anwenden von SecurityBehaviorAttribute muss das Zielszenario in Form eines ServiceSecurity-Werts bereitgestellt werden. Sie können einfach nur die Konstruktoren von SecurityBehaviorAttribute verwenden oder die Eigenschaften festlegen. Bei Nichtfestlegung der Eigenschaften werden für sie geeignete Standardwerte im Kontext des Zielszenarios angegeben. Bei der Wahl eines Szenarios befolgt das konfigurierte Verhalten meine vorherige Beschreibung der einzelnen Szenarios ganz genau.
SecurityBehaviorAttribute ergibt ein zusammensetzbares Sicherheitsmodell, das einige Permutationen und Unterszenarios zulässt. Beim Verwenden des Attributs kann sogar eine sicherheitsfreie Hostkonfigurationsdatei vorhanden sein, oder Sie können Einstellungen aus der Konfigurationsdatei mit Werten kombinieren, die vom Attribut gesteuert werden. Ganz ähnlich kann Ihr Hostcode sicherheitsfrei sein, oder Sie können programmgesteuerte Hostsicherheit mit dem Attribut kombinieren.
Um einen Dienst für das Intranetsicherheitsszenario zu konfigurieren, wenden Sie SecurityBehavior mit ServiceSecurity.Intranet wie folgt an:
[ServiceContract]
interface IMyContract
{
   [OperationContract]
   void MyMethod();
}
[SecurityBehavior(ServiceSecurity.Intranet)]
class MyService : IMyContract
{
   public void MyMethod() {...}
}
Obwohl der verwendete Dienstvertrag die Schutzebene nicht einschränken darf, fügt das Attribut diese Anforderung programmgesteuert hinzu, um den Nachrichtenschutz durchzusetzen. Sie können Windows NT-Gruppen für rollenbasierte Sicherheit folgendermaßen verwenden:
[SecurityBehavior(ServiceSecurity.Intranet)]
class MyService : IMyContract
{
   [PrincipalPermission(SecurityAction.Demand,
        Role = @”<Domain>\Customer”)]
   public void MyMethod() {...}
}
Der Dienst kann programmgesteuert einen Identitätswechsel der Aufrufer durchführen oder das Vorgangsverhaltensattribut zum Identitätswechsel einzelner Methoden verwenden. Sie können den Dienst auch so konfigurieren, dass der Identitätswechsel aller Aufrufer in allen Methoden automatisch über die ImpersonateAll-Eigenschaft durchgeführt wird. Der Standardwert für ImpersonateAll wird auf „False“ gesetzt. Doch wenn er auf „True“ gesetzt wird, führt das Attribut einen Identitätswechsel aller Aufrufer in allen Vorgängen durch, ohne dass ein Vorgangsverhaltensattribut angewendet werden muss:
[SecurityBehavior(ServiceSecurity.Intranet,ImpersonateAll = true)]
class MyService : IMyContract {...}
Bei dem Internetszenario muss sowohl mit ServiceSecurity.Internet konfiguriert als auch das zu verwendete Dienstzertifikat ausgewählt werden. Beachten Sie in Abbildung 4, dass der ServiceBehavior-Attributkonstruktor den Namen des Dienstzertifikats annehmen kann. Bei nicht angegebenem Namen wird das Dienstzertifikat aus der Hostkonfigurationsdatei wie bei der einfachen WCF-Sicherheit geladen:
[SecurityBehavior(ServiceSecurity.Internet)]
class MyService : IMyContract {...}
Sie können den Dienstzertifikatnamen auch angeben, wobei das angegebene Zertifikat aus dem LocalMachine-Speicher aus dem „My folder by name“ geladen wird:
[SecurityBehavior(ServiceSecurity.Internet,”MyServiceCert”)]
class MyService : IMyContract {...}
Wenn der Zertifikatname als leere Zeichenfolge festgelegt ist, leitet das Attribut den Zertifikatnamen mithilfe des Hostcomputernamens (oder der Domäne) für den Zertifikatnamen ab und lädt das Zertifikat aus dem LocalMachine-Speicher aus dem „My folder by name“:
[SecurityBehavior(ServiceSecurity.Internet,””)]
class MyService : IMyContract {...}
Schließlich können Sie mithilfe des Attributs den Speicherort, den Speichernamen und die Suchmethode explizit angeben:
[SecurityBehavior(ServiceSecurity.Internet,
    StoreLocation.LocalMachine,StoreName.My,
    X509FindType.FindBySubjectName,”MyServiceCert”)]
class MyService : IMyContract {...}
Beachten Sie, dass Sie einen expliziten Ort mit einem abgeleiteten Zertifikatnamen folgendermaßen kombinieren können:
[SecurityBehavior(ServiceSecurity.Internet,
    StoreLocation.LocalMachine,StoreName.My,
    X509FindType.FindBySubjectName,””)]
class MyService : IMyContract {...}
Anhand welches Anmeldeinformationenspeichers der Client authentifiziert werden muss, wird von der UseAspNetProviders-Eigenschaft angezeigt. Der Standardwert für diese Eigenschaft wird auf „False“ gesetzt, d. h. der Standardwert muss den Benutzernamen und das Kennwort des Clients als Windows-Anmeldeinformationen authentifizieren:
[SecurityBehavior(ServiceSecurity.Internet,”MyServiceCert”)]
class MyService : IMyContract
{
   [PrincipalPermission(
      SecurityAction.Demand,Role = @”<Domain>\Customer”)]
   public void MyMethod() {...}
}
Sie können sogar einen Identitätswechsel aller Aufrufer durchführen:
[SecurityBehavior(
   ServiceSecurity.Internet,”MyServiceCert”,ImpersonateAll = true)]
class MyService : IMyContract {...}
Wenn UseAspNetProviders auf „False“ gesetzt ist, verwendet das Attribut die ASP.NET-Mitgliedschaft und Rollenanbieter wie für das Internetszenario vorgeschrieben:
[SecurityBehavior(
   ServiceSecurity.Internet,”MyServiceCert”,UseAspNetProviders = true)]
class MyService : IMyContract
{
   [PrincipalPermission(SecurityAction.Demand,Role = “Manager”)]
   public void MyMethod() {...}
}
Das Attribut aktiviert den Rollenverwaltungsabschnitt in der Konfigurationsdatei, sodass nur Endpunkte in der Hostkonfigurationsdatei erforderlich sind.
Als Nächstes geht es um das Problem der Bereitstellung des Anwendungsnamens für die ASP.NET-Anbieter mithilfe der ApplicationName-Eigenschaft. Wenn er nicht zugewiesen ist, sucht das Attribut wie bei einfacher WCF den Anwendungsnamen in der Konfigurationsdatei. Wenn in der Hostkonfigurationsdatei kein Wert gefunden wird, verwendet das Attribut nicht das bedeutungslose / aus machine.config als Standardwert. Stattdessen wird als Standardwert der Name der Hostassembly verwendet. Wenn der ApplicationName-Eigenschaft ein Wert zugewiesen ist, wird der jeweilige in den Hostkonfigurationsdateien vorhandene Anwendungsname außer Kraft gesetzt.
[SecurityBehavior(ServiceSecurity.Internet,”MyServiceCert”,
  UseAspNetProviders = true,ApplicationName = “MyApplication”)]
class MyService : IMyContract {...}
Das Konfigurieren für das Business-to-Business-Szenario erfordert das Einstellen der ServiceSecurity als ServiceSecurity.BusinessToBusiness, genau wie dies bei ServiceSecurity.Internet der Fall ist. Zum Beispiel:
[SecurityBehavior(ServiceSecurity.BusinessToBusiness)]
class MyService : IMyContract {...}

[SecurityBehavior(ServiceSecurity.BusinessToBusiness,””)]
class MyService : IMyContract {...}

[SecurityBehavior(ServiceSecurity.BusinessToBusiness,”MyServiceCert”)]
class MyService : IMyContract {...}
Standardmäßig kann der Dienst bei Verwendung von ServiceSecurity.BusinessToBusiness seine Aufrufer nicht autorisieren. Doch das Setzen der UseAspNetProviders-Eigenschaft auf „True“ ermöglicht den Einsatz der ASP.NET-Rollenanbieter:
[SecurityBehavior(ServiceSecurity.BusinessToBusiness,
   UseAspNetProviders = true)]
class MyService : IMyContract {...}
Bei Verwendung der ASP.NET-Rollenanbieter wird der Anwendungsname gesucht und bestimmt, genau wie dies bei der ServiceSecurity.Internet-Einstellung der Fall wäre:
[SecurityBehavior(ServiceSecurity.BusinessToBusiness,”MyServiceCert”,
   UseAspNetProviders = true,ApplicationName = “MyApplication”)]
class MyService : IMyContract {...}
Um Aufrufer gemäß dem anonymen Szenario zuzulassen, muss das Attribut mit ServiceSecurity.Anonymous konfiguriert werden. Die Konfiguration des Dienstzertifikats erfolgt genau wie bei ServiceSecurity.Internet:
[SecurityBehavior(ServiceSecurity.Anonymous)]
class MyService : IMyContract {...}
Zum vollständigen Ausschalten der Sicherheit wird genau wie bei dem Szenario ohne Sicherheit ServiceSecurity.None für das Attribut bereitgestellt:
[SecurityBehavior(ServiceSecurity.None)]
class MyService : IMyContract {...}

Implementieren von SecurityBehaviorAttribute
Abbildung 5 ist eine partielle Liste der Implementierung von SecurityBehaviorAttribute. Die drei öffentlichen Eigenschaften des Attributs entsprechen drei privaten Mitgliedern, bei denen das Attribut die konfigurierten Werte speichert.
[AttributeUsage(AttributeTargets.Class)]
class SecurityBehaviorAttribute : Attribute,IServiceBehavior
{
   SecurityBehavior m_SecurityBehavior;
   string m_ApplicationName = String.Empty;
   bool m_UseAspNetProviders;
   bool m_ImpersonateAll;

   public SecurityBehaviorAttribute(ServiceSecurity mode)
   {
      m_SecurityBehavior = new SecurityBehavior(mode);
   }

   public SecurityBehaviorAttribute(ServiceSecurity mode,
      string serviceCertificateName)
   {
      m_SecurityBehavior = 
         new SecurityBehavior(mode,serviceCertificateName);
   }

   public bool ImpersonateAll {get;set;} // m_ImpersonateAll

   public string ApplicationName {get;set;} // m_ApplicationName

   public bool UseAspNetProviders {get;set;} // m_UseAspNetProviders

   void IServiceBehavior.AddBindingParameters(
      ServiceDescription description,
      ServiceHostBase serviceHostBase,
      Collection<ServiceEndpoint> endpoints,
      BindingParameterCollection parameters)
   {
      m_SecurityBehavior.AddBindingParameters(
         description,serviceHostBase, endpoints,parameters);
   }
   void IServiceBehavior.Validate(ServiceDescription description,
      ServiceHostBase serviceHostBase)
   {
      m_SecurityBehavior.UseAspNetProviders = UseAspNetProviders;
      m_SecurityBehavior.ApplicationName = ApplicationName;
      m_SecurityBehavior.ImpersonateAll = ImpersonateAll;
      m_SecurityBehavior.Validate(description,serviceHostBase);
   }

   ... //Rest of the implementation 
}

SecurityBehaviorAttribute ist ein Attribut des Dienstverhaltens, sodass Sie es direkt auf die Dienstklasse anwenden können. Das Attribut unterstützt die WCF-Erweiterungsschnittstelle IServiceBehavior, die wie folgt definiert ist:
public interface IServiceBehavior
{
   void AddBindingParameters(ServiceDescription serviceDescription,
      ServiceHostBase serviceHostBase, 
      Collection<ServiceEndpoint> endpoints, 
      BindingParameterCollection bindingParameters);

   void Validate(ServiceDescription serviceDescription, 
      ServiceHostBase serviceHostBase);

   void ApplyDispatchBehavior(...);
}
IServiceBehavior ist eine besondere Schnittstelle. Wenn dies beim Starten des Hosts und später bei der Instanziierung des Diensts von einem Attribut auf der Dienstklasse unterstützt wird, ruft WCF die verschiedenen Methoden von IServiceBehavior auf, sodass sich der Dienst mit WCF verknüpfen und ihn erweitern kann, was sich auf die Konfiguration der Bindung, des Verteilers und des Hosts auswirkt.
Wenn die AddBindingParameters-Methode von IServiceBehavior aufgerufen wird, setzt SecurityBehaviorAttribute die Bindungskonfiguration durch, die dem angeforderten Szenario entspricht. Der Host wird in der Validate-Methode von IServiceBehavior konfiguriert. Die eigentliche Konfiguration wird mithilfe einer Hilfsklasse namens „SecurityBehavior“ erreicht. Im Allgemeinen empfiehlt sich das Trennen des eigentlichen Codes einer Erweiterung vom Attribut, sodass der Code an anderen Stellen verwendet werden kann. So enthält beispielsweise der Code, der diesem Artikel beigefügt ist, auch eine Hostklasse, die deklarative Sicherheit auf der gehosteten Dienstklasse so anwenden kann, als ob diese Klasse SecurityBehaviorAttribute verwenden würde. Ähnlich verwendet der clientseitige Teil meines Frameworks ebenfalls SecurityBehavior. SecurityBehaviorAttribute erstellt eine Instanz von SecurityBehavior und stellt diesem das Szenario (den Modusparameter) sowie den Zertifikatnamen im entsprechenden Konstruktor bereit. SecurityBehavior stellt die systematische, sorgfältige, programmgesteuerte Einstellung aller Sicherheitsszenarios bereit. Dadurch werden die Konfigurationsdatei und der Hostcode von jeglicher Sicherheit befreit.
SecurityBehavior ist ein selbständiges Dienstverhalten und so ausgelegt, dass es unabhängig vom Attribut verwendet werden kann. Abbildung 6 enthält eine partielle Liste von SecurityBehavior.
class SecurityBehavior : IServiceBehavior
{
   ServiceSecurity m_Mode;
   StoreLocation m_StoreLocation;
   StoreName m_StoreName;
   X509FindType m_FindType;
   string m_SubjectName;
   bool m_UseAspNetProviders;
   string m_ApplicationName = String.Empty;
   bool m_ImpersonateAll;

   public SecurityBehavior(ServiceSecurity mode) : 
      this(mode,StoreLocation.LocalMachine,StoreName.My,
         X509FindType.FindBySubjectName,null) {}

   public SecurityBehavior(ServiceSecurity mode,
      StoreLocation storeLocation,StoreName storeName,
      X509FindType findType,string subjectName)  
        {...} //Sets the corresponding members
 
   public bool ImpersonateAll {get;set;} // m_ImpersonateAll
 
   public bool UseAspNetProviders {get;set;} //m_UseAspNetProviders
 
   public string ApplicationName {get;set;} // m_ApplicationName
 
   public void Validate(ServiceDescription description,
     ServiceHostBase serviceHostBase)
   {
      if(m_SubjectName != null)
      {
         switch(m_Mode)
         {
            case ServiceSecurity.Anonymous:
            case ServiceSecurity.BusinessToBusiness:
            case ServiceSecurity.Internet:
                 string subjectName = m_SubjectName != String.Empty ?
                    m_SubjectName : 
                    description.Endpoints[0].Address.Uri.Host;
               serviceHostBase.Credentials.ServiceCertificate.
                  SetCertificate(m_StoreLocation,m_StoreName,
                    m_FindType,subjectName);
               break;
         }
      }
      ...
   }

   public void AddBindingParameters(ServiceDescription description,
      ServiceHostBase serviceHostBase,
      Collection<ServiceEndpoint> endpoints,
      BindingParameterCollection parameters)
   {
      ...
      switch(m_Mode)
      {
         case ServiceSecurity.Intranet:
            ConfigureIntranet(endpoints);
            break;

         case ServiceSecurity.Internet:
            ConfigureInternet(endpoints,UseAspNetProviders);
            break;
         ...
      }
   }

   internal static void ConfigureInternet(
      Collection<ServiceEndpoint> endpoints)
   {
      foreach(ServiceEndpoint endpoint in endpoints)
      {
         Binding binding = endpoint.Binding;
         if(binding is WSHttpBinding)
         {
            WSHttpBinding wsBinding = (WSHttpBinding)binding;
            wsBinding.Security.Mode = SecurityMode.Message;
            wsBinding.Security.Message.ClientCredentialType = 
               MessageCredentialType.UserName;
            continue;
         }
         ...
         throw new InvalidOperationException(binding.GetType() + 
            “is unsupprted with ServiceSecurity.Internet”);
      }
    }

    ... //Rest of the implementation
}

Die SecurityBehavior-Konstruktoren speichern ihre Parameter wie beispielsweise den Sicherheitsmodus und die Details des Zertifikats in Membervariablen. Die Validate-Methode ist eine Entscheidungsstruktur, die den Host gemäß dem Szenario und den bereitgestellten Informationen konfiguriert, wodurch das bereits beschriebene Verhalten von SecurityBehaviorAttribute unterstützt wird.
AddBindingParameters ruft eine spezielle Hilfsmethode für jedes Szenario auf, um die Auflistung der Endpunkte des Hosts zu konfigurieren. Jede Hilfsmethode (wie z. B. ConfigureInternet) durchläuft diese Auflistung und prüft für jeden Endpunkt, ob die verwendete Bindung dem Szenario entspricht, und konfiguriert dann die Bindung gemäß dem Szenario.

Clientseitige deklarative Sicherheit
WCF unterstützt die Anwendung eines Attributs auf der Proxyklasse nicht. Selbst wenn dies der Fall wäre, muss der Client möglicherweise seine Anmeldeinformationen und anderen Einstellungen zur Laufzeit bereitstellen. Um dies wettzumachen, ist der erste Schritt bei der Unterstützung der deklarativen Sicherheit auf der Clientseite meine in Abbildung 7 definierte statische SecurityHelper-Hilfsklasse.
public static class SecurityHelper
{
   public static void UnsecuredProxy<T>(ClientBase<T> proxy) 
      where T : class;

   public static void AnonymousProxy<T>(ClientBase<T> proxy) 
      where T : class;

   public static void SecureProxy<T>(ClientBase<T> proxy,
      string userName,string password) where T : class;

   public static void SecureProxy<T>(ClientBase<T> proxy,
      string domain,string userName,string password) where T : class;

   public static void SecureProxy<T>(ClientBase<T> proxy,
      string clientCertificateName) where T : class;

   public static void SecureProxy<T>(ClientBase<T> proxy,
      StoreLocation storeLocation,StoreName storeName,
      X509FindType findType,string clientCertificateName) 
      where T : class;
   ... //More members
}

Mit SecurityHelper kann ein einfacher Proxy gemäß dem gewünschten Sicherheitsszenario und Verhalten konfiguriert werden. Sie können den Proxy nur vor dem Öffnen konfigurieren. In der Konfigurationsdatei des Clients oder an anderer Stelle im Clientcode sind keine Sicherheitseinstellungen erforderlich. SecurityHelper ist intelligent und wählt das richtige Sicherheitsverhalten auf der Basis der bereitgestellten Parameter und der aufgerufenen Methode. Dieses Beispiel zeigt, wie ein Proxy für das Intranetszenario gesichert werden kann und ihm die Windows-Anmeldeinformationen des Clients bereitgestellt werden können:
MyContractClient proxy = new MyContractClient();
SecurityHelper.SecureProxy(proxy,”MyDomain”,”MyUsername”,”MyPassword”);
proxy.MyMethod();
proxy.Close();
Für das Internetszenario muss der Client nur den Benutzernamen und das Kennwort bereitstellen (beachten Sie, dass die Entscheidung, ob dies Windows- oder ASP.NET-Anbieteranmeldeinformationen sind, eine Entscheidung der Dienstseite ist):
MyContractClient proxy = new MyContractClient();
SecurityHelper.SecureProxy(proxy,”MyUsername”,”MyPassword”);
proxy.MyMethod();
proxy.Close();
Für das Business-to-Business-Szenario kann der Client eine Null oder eine leere Zeichenfolge für den Zertifikatnamen des Clients angeben, wenn das Zertifikat wie bei einfacher WFC in seiner Konfigurationsdatei verwendet werden soll, oder er kann den Zertifikatnamen explizit auflisten:
MyContractClient proxy = new MyContractClient();
SecurityHelper.SecureProxy(proxy,”MyClientCert”);
proxy.MyMethod();
proxy.Close();
SecurityHelper lädt das Zertifikat aus dem LocalMachine-Speicher des Clients aus dem „My folder by name“. Der Client kann auch alle erforderlichen Informationen festlegen, die für die Suche nach dem Zertifikat und für das Laden erforderlich sind. Bei einem anonymen Client verwenden Sie die AnonymousProxy-Methode:
MyContractClient proxy = new MyContractClient();
SecurityHelper.AnonymousProxy(proxy);
proxy.MyMethod();
proxy.Close();
Bei einem Szenario ohne Sicherheit verwenden Sie die UnsecuredProxy-Methode:
MyContractClient proxy = new MyContractClient();
SecurityHelper.UnsecuredProxy(proxy);
proxy.MyMethod();
proxy.Close();

SecurityHelper und SecureClientBase<T>
Intern verwendet SecurityHelper SecurityBehavior zum Konfigurieren des Endpunkts des Proxys sowie zum Festlegen der Anmeldeinformationen (siehe Abbildung 8).
public static class SecurityHelper
{
   public static void SecureProxy<T>(ClientBase<T> proxy,
      string userName,string password) where T : class
   {
      if(proxy.State == CommunicationState.Opened)
      {
         throw new InvalidOperationException(
            “Proxy channel is already opened”);
      }

      Collection<ServiceEndpoint> endpoints = 
         new Collection<ServiceEndpoint>();
      endpoints.Add(proxy.Endpoint);

      SecurityBehavior.ConfigureInternet(endpoints);

      proxy.ClientCredentials.UserName.UserName = userName;
      proxy.ClientCredentials.UserName.Password = password;
      proxy.ClientCredentials.ServiceCertificate.Authentication.
      CertificateValidationMode = X509CertificateValidationMode.PeerTrust;
   }
   ... //Rest of the implementation
}

Der Vorteil bei der Verwendung von SecurityHelper besteht darin, dass die Klasse bei jedem Proxy eingesetzt werden kann, selbst bei einem Proxy, für dessen Erstellung der Cliententwickler nicht verantwortlich ist. Wenn Sie für das Generieren des Proxys verantwortlich sind, können Sie die in Abbildung 9 definierte SecureClientBase>T-Klasse verwenden.<
public abstract class SecureClientBase<T> : ClientBase<T> where T : class
{
   //These constructors target the default endpoint
   protected SecureClientBase();
   protected SecureClientBase(ServiceSecurity mode);
   protected SecureClientBase(string userName,string password);
   protected SecureClientBase(
      string domain,string userName,string password);
   protected SecureClientBase(string clientCertificateName);
   protected SecureClientBase(StoreLocation storeLocation,
      StoreName storeName,X509FindType findType,
      string clientCertificateName);

   ... //More constructors for other types of endpoints 
}

SecureClientBase<T> leitet sich aus der konventionellen ClientBase<T>-Klasse ab und fügt die deklarative Sicherheitsunterstützung hinzu. Sie müssen Ihren Proxy aus SecureClientBase<T> statt ClientBase<T> ableiten, Konstruktoren bereitstellen, die Ihrem Sicherheitsszenario entsprechen, und die Basiskonstruktoren von SecureClientBase<T> mit den bereitgestellten Anmeldeinformationen und Endpunktinformationen aufrufen, wie hier dargestellt:
class MyContractClient : SecureClientBase<IMyContract>,IMyContract
{
   public MyContractClient(ServiceSecurity mode) : base(mode) {}

   public MyContractClient(string userName,string password) : 
     base(userName,password) {}

   ... // More constructors
 
   public void MyMethod()
   {
      Channel.MyMethod();
   }
}
Das Verwenden des abgeleiteten Proxys ist einfach. Für das Internetszenario würde dies beispielsweise so aussehen:
MyContractClient proxy = new MyContractClient(“MyUsername”,
                                              ”MyPassword”);
proxy.MyMethod();
proxy.Close();
Und für das anonyme Szenario so:
MyContractClient proxy = new MyContractClient(ServiceSecurity.                                                        Anonymous);
proxy.MyMethod();
proxy.Close();
Bei der Implementierung von SecureClientBase<T> wird einfach SecurityHelper wie in Abbildung 10 dargestellt verwendet.
public class SecureClientBase<T> : ClientBase<T> where T : class
{
   protected SecureClientBase(ServiceSecurity mode)
   {
      switch(mode)
      {
         case ServiceSecurity.None:
            SecurityHelper.UnsecuredProxy(this);
            break;

         case ServiceSecurity.Anonymous:
            SecurityHelper.AnonymousProxy(this);
            break;

         ...
      }
   }

   protected SecureClientBase(string userName,string password)
   {
      SecurityHelper.SecureProxy(this,userName,password);
   }
 
   ... //More constructors 
}

Wenn keine Proxyklasse verwendet wird, können Sie SecureChannelFactory (sie ist im Quellcode dieses Artikels enthalten) genau wie die von WCF bereitgestellte Kanalfactory verwenden. Doch mit SecureChannelFactory können Sie die deklarative Sicherheit nutzen.

Welche Bedeutung hat dies?
Durch Ermöglichen sehr breit angelegter Interaktionen zwischen Clients und Diensten führt WCF-Sicherheit eine Komplexität ein, die nur schwer zu beherrschen ist. Das Ziel des deklarativen Sicherheitsframeworks ist das Eliminieren dieser Komplexität, ohne die Sicherheit oder die Konfigurationsflexibilität für die unterstützten Szenarios zu verringern. Sie müssen nur das richtige Sicherheitsszenario für Ihre Anwendung auswählen, und Sie haben immer noch so viel Kontrolle wie bei der einfachen WCF-Sicherheit. Einzelheiten zur Funktion der einfachen WCF-Sicherheit finden Sie in dem Artikel Security Briefs (August 2006).
Der Kompromiss bei einem Framework wie meinem besteht darin, dass nicht alle Szenarios (beispielsweise die verbundene Sicherheit) abgedeckt werden. Sie können jedoch mein Framework als Ausgangspunkt zum Aktivieren der deklarativen Sicherheit verwenden und es dann Ihrem speziellen Szenario anpassen.

Senden Sie Ihre Fragen und Kommentare (in englischer Sprache) an  mmnet30@microsoft.com.


Juval Lowy ist als Softwarearchitekt bei IDesign tätig. Er bietet WCF-Training und Beratung zur WCF-Architektur an. Dieser Artikel enthält Auszüge aus seinem neuen Buch Programming WCF Services (O'Reilly, 2007). Er ist zudem der Microsoft Regional Director für Silicon Valley. Sie erreichen Juval unter idesign.net.

Page view tracker