OData

OData, die Zugriffssteuerung für Entity Framework und Windows Azure

Sean Iannuzzi

In diesem Artikel beschreibe ich die Implementierung von Open Data Protocol (OData) mit Entity Framework. Dabei erfolgt die Verfügbarmachung über die RESTful-Dienste der Windows Communication Foundation (WCF), und die Sicherheit wird mit dem Zugriffssteuerungsdienst (ACS) für Windows Azure realisiert.

Wie die meisten Entwickler versuche auch ich meist, Technologien auf neue und verschiedenartige Weise so zu kombinieren, dass mein Projekt möglichst effizient durchführbar wird und ich eine flexible, einfach zu verwaltende Lösung erhalte. Das kann zu einem schwierigen Unterfangen werden, besonders wenn das Projekt erfordert, dass Daten schnell und sicher verfügbar gemacht werden.

Vor Kurzem wurde ich gebeten, für eine bestehende Datenbank- und Webanwendung einen sicheren Webdienst zu erstellen. Die Vorstellung, all den CRUD-Code – CRUD steht für Create, Read, Update, Delete (Erstellen, Lesen, Aktualisieren, Löschen) – implementieren zu müssen, sagte mir absolut nicht zu. Stattdessen war es verlockend, einfach benutzerdefinierte Dienstverträge, Vorgangsverträge und Datenverträge zu erstellen, mit denen genau gesteuert werden kann, wie die Daten verfügbar gemacht werden und wie die Nutzung der Daten über Dienste erfolgt. Mir war aber bewusst, dass es einen noch besseren Weg geben musste. Also informierte ich mich über verschiedene Möglichkeiten und sah in OData ein gewisses Potenzial. Das Problem dabei war allerdings, dass OData von sich aus nicht so gesichert ist, wie ich es für angebracht halte. Um die Sicherheit zu erlangen, die mir als angemessen erscheint, ist eine weitere Sicherheitsschicht zusätzlich zum OData-Dienst erforderlich. Während dieser Überlegungen stieß ich auch auf ACS. Der Zugriffssteuerungsdienst eignet sich hervorragend für die Implementierung eines cloudbasierten Diensts für Verbundauthentifizierung und Autorisierung und entsprach somit genau meinen Anforderungen. Und dann erkannte ich es: Ich musste nur ACS mit OData verbinden und hatte meine Lösung.

Ich habe auch in Erwägung gezogen, benutzerdefinierte Dienstverträge zu implementieren. Diese eignen sich nämlich besonders dann, wenn vor einem Datenmodell eine Abstraktionsschicht erforderlich ist und wenn die Datenbankentitäten vor der direkten Verfügbarmachung für Dienstnutzer geschützt werden müssen. Angesichts des enormen Zeitaufwands könnte das Projekt allerdings schnell außer Kontrolle geraten. Schließlich müsste bei dieser Herangehensweise das entsprechende Dokument erstellt werden, in dem die Nutzung des Diensts geregelt ist, und die Herstellung der Sicherheit („MessageCredential“ und „TransportWithMessageCredentials“) wäre zusätzlicher Aufwand. Außerdem befürchtete ich, dass möglicherweise weitere Methoden erforderlich werden könnten, um die Art der Dienstnutzung zu unterstützen. Das hätte wiederum mehr Zeit, mehr Verwaltungsaufwand und weitere Anpassungen bedeutet. Selbst wenn ich bei meiner Implementierung des Diensts das Entity Framework im Gegensatz zu ADO.NET direkt verwenden würde, müsste ich womöglich immer noch den gesamten CRUD-Code erstellen, um so die Datenschichten synchron zu halten. Ausgehend von einigen Dutzend Tabellen wäre das ein extrem mühseliges Unterfangen. Dazu käme noch, dass zusätzliche Dokumentation und Implementierungsdetails erstellt und verwaltet werden müssten, damit die Endbenutzer den Dienst auch nutzen können, was die Arbeit stark verkomplizieren würde.

Eine einfachere Methode

Nachdem ich bei den zentralen Technologien eine Entscheidung getroffen hatte, suchte ich nach weiteren Möglichkeiten, um Lücken zu füllen und eine geschlossene Lösung zu erhalten. Das Ziel bestand darin, die Menge des zu schreibenden bzw. zu verwaltenden Codes gering zu halten und gleichzeitig eine sichere Verfügbarmachung meiner OData-WCF-RESTful-Dienste zu realisieren. Diese Technologien habe ich miteinander verknüpft: ACS, OData, Entity Data Model (EDM), WCF Data Services mit Entitätsberechtigungen und eine benutzerdefinierte Windows Azure-Sicherheitsimplementierung. Für sich genommen ist jede dieser Technologien schon von hohem Nutzen. Miteinander kombiniert steigt der Nutzen jedoch exponentiell. Abbildung 1 gibt einen allgemeinen Überblick darüber, wie einige dieser Technologien nach der Implementierung funktionieren.

High-Level Overview of ACS with a Security InterceptAbbildung 1: Allgemeiner Überblick über ACS mit Sicherheitsinterceptor

Vor dem Kombinieren all dieser Technologien musste ich noch einmal einen Schritt zurückgehen und mich genau über jede einzelne Technologie informieren, um zu sehen, wie sie sich auf mein Projekt auswirkt. Danach hatte ich eine gute Vorstellung davon, wie ich die Technologien miteinander kombinieren kann. Auch wusste ich, was für andere erforderlich ist, damit sie meine Dienste mit anderen Technologien verwenden können.

Was ist ACS?

ACS wird als Komponente der Windows Azure-Plattform bereitgestellt. Durch ACS kann ich meinen eigenen cloudbasierten Anbieter für Verbundauthentifizierung und Autorisierung einrichten, mit dem ich meine OData-WCF-Dienste schütze. ACS kann aber auch für die Sicherung jeder anderen App genutzt werden. Mit dem cloudbasierten Dienst ACS wird die Sicherheitslücke überbrückt, wenn eine Funktion für einmaliges Anmelden (Single Sign-On, SSO) für mehrere Anwendungen, Dienste oder Produkte implementiert werden soll. Dabei kann die Implementierung über Plattformen oder Domänen hinweg erfolgen und verschiedene SSO-Implementierungen unterstützen. Zu diesem Thema haben Sie über ein Windows Azure-Konto Zugriff auf wesentlich mehr Informationen. Unter windowsazure.com können Sie sich für eine kostenlose Testversion registrieren. Weitere Informationen zu ACS finden Sie unter bit.ly/zLpIbw.

Was ist OData und wozu dient es?

OData ist ein webbasiertes Protokoll für die Abfrage und Aktualisierung von Daten. Außerdem werden mit dem Protokoll über eine standardisierte Syntax Daten verfügbar gemacht. OData greift auf Technologien wie HTTP, XML, JSON und das Atom Publishing Protocol zurück und ermöglicht so einen anderen Datenzugriff. Die Implementierung von OData mit Entity Framework und WCF Data Services hat viele Vorteile.

An einem bestimmten Punkt habe ich mich gefragt, weshalb ich diesem Protokoll den Vorzug gebe und nicht benutzerdefinierten WCF-Verträgen. Die Antwort darauf ist sehr einfach. Der praktischste Grund bestand darin, dass ich die bereits vorhandene Dienstdokumentation nutzen konnte und die Möglichkeit hatte, mit einer standardisierten Syntax den Zugriff auf die Daten meines Diensts zu unterstützen. Nachdem ich inzwischen Dutzende Dienste geschrieben habe, scheint sich herauszustellen, dass ich immer eine zusätzliche Methode hinzufügen muss, weil ich benutzerdefinierte Dienste anbieten möchte. Und die Nutzer von benutzerdefinierten Diensten neigen dazu, nach immer mehr Features zu verlangen.

 Weitere Informationen zu OData und OData-URI-Konventionen finden Sie auf den folgenden Websites:

OData mit Entity Framework und WCF Data Services

Durch die Verwendung von OData mit WCF Data Services und Entity Framework stehen Standardfunktionen zur Verfügung, mit denen der Abruf und die Speicherung von Daten mit nur wenig Implementierungscode realisiert werden. Dies ist auch bereits dokumentiert. Als ich damit begann, meine EDMX-Datei (Entity Data Model for Data Services Packaging Format) zu erstellen und sie durch Datendienste mit meinem WCF-Dienst verknüpfte, war ich sehr skeptisch. Es hat jedoch perfekt funktioniert. Alle Entitäten in meiner EDMX-Datei waren automatisch in meinem WCF-Dienst in einer RESTful-Implementierung enthalten und verfügbar gemacht. Abbildung 2 enthält Beispielcode dazu.

Abbildung 2: Implementieren von OData WCF Data Services

using System.Data.Services;
using System.Data.Services.Common;
namespace WCFDataServiceExample
{
  public class NorthwindService : DataService<NorthwindEntities>
  {
    // This method is called only once to initialize service-wide policies.
    public static void InitializeService(DataServiceConfiguration config)
    {
      // Give full access to all of the entities.
      config.SetEntitySetAccessRule("*", EntitySetRights.All);
      // Page size will change the max number of rows returned.
      config.SetEntitySetPageSize("*", 25);
      config.DataServiceBehavior.MaxProtocolVersion =
        DataServiceProtocolVersion.V2;
    }
  }
}

Ich habe eine EDMX-Datei erstellt und sie mit einigen Tabellen in meiner Datenbank (Northwind-Beispieldatenbank) verknüpft. Dann habe ich meine Datenbankentitäten mit meinem WCF Data Service verknüpft und mit der Methode „SetEntitySetAccessRule“ und einem Platzhalterattribut „*“ für alle Entitäten die Entitäten verfügbar gemacht. So konnte ich für die Entitäten verschiedene Berechtigungen für Lesen, Schreiben und Abfragezugriff sowie die Seitengröße festlegen, siehe Abbildung 2. Bei dem Code in Abbildung 2 handelt es sich um die gesamte Implementierung des Codes für OData WCF Data Services.

Die Dienstverträge, Vorgangsverträge und Datenverträge werden hauptsächlich über die Konfiguration bei der Initialisierung des bereitgestellten Diensts gesteuert. Bei der Initialisierungsmethode habe ich die Möglichkeit, verschiedene Berechtigungen dafür festzulegen, wie meine Entitäten verfügbar gemacht werden sollen, und welchen Zugang ich denen bieten möchte, die meine Dienste nutzen möchten. Ich könnte auch mithilfe von T4-Vorlagen eine abstrakte Schicht bzw. eine Vorlagenschicht auf den Entitäten mit benutzerdefinierten Bezeichnungen erstellen. Dem Dienstnutzer würde das zu mehr Klarheit verhelfen. Für einen Schutz auf noch tieferer Ebene könnte ich sogar die Berechtigung für eine bestimmte Tabelle oder die Tabellenbezeichnung gemeinsam mit den entsprechenden Sicherheitseinstellungen festlegen. Hier sehen Sie ein Beispiel, wie Sie die Tabelle „Customer“ mit Lesezugriff versehen:

config.SetEntitySetAccessRule("Customer",
  EntitySetRights.AllRead);

Mit OData und WCF Data Services sind viele verschiedene Sicherheitsimplementierungen möglich. Im Moment befasse ich mich allerdings nur damit, wie ich meine WCF-Dienste mit ACS und den Zugriffsregeln für Datendienste schützen kann.

In Abbildung 3 sehen Sie eine kurz Liste der verwendeten Technologien sowie die jeweiligen Gründe, weshalb diese Technologien eingesetzt werden.

Abbildung 3: Eingesetzte Technologien mit Begründungen

Technologie Grund für den Einsatz
ACS Ermöglicht sichere Dienste über ein cloudbasiertes Sicherheitsmodul für Verbundauthentifizierung und Autorisierung
OData Bietet eine Standardsyntax für die Abfrage und Aktualisierung von Daten und nutzt gängige Technologien wie HTTP, JSON, das Atom Publishing Protocol und XML
Entity Data Models Ermöglicht die schnelle Erstellung eines gemeinsamen Datenzugriffs für eine Datenbankebene und bietet serialisierbare Datenverträge für Datenbanktabellen
WCF Data Services mit Entitätsberechtigungen Macht den Entity Framework-Datenvertrag mit der entsprechenden Berechtigungsstufe für CRUD als WCF-RESTful-Dienst verfügbar
Benutzerdefinierte Windows Azure-Sicherheitsimplementierung Schützt Dienste (in diesem Fall OData-Dienste) vor der Nutzung ohne angemessene Sicherheitsmaßnahme, wie Token oder Zertifikat

Bei jeder dieser Technologien müssen stets je nach Projekt Abstriche gemacht werden. Ich habe allerdings festgestellt, dass ich durch die Kombination dieser Technologien weniger Aufwand bei der anfänglichen Einrichtung habe, die Wartung einfacher wird, weniger Code benötigt wird und sich weitere enorme Vorteile ergeben – insbesondere dann, wenn meine Daten sicher verfügbar gemacht werden müssen und gängige Datenzugriffsmethoden über eine standardisierte Syntax erfolgen sollen.

Technologien kombinieren

Nachdem ich verstanden hatte, wie OData, das Entity Framework und WCF Data Services zusammen eingesetzt werden, konnte ich mithilfe von ACS weitere Sicherheitsmerkmale implementieren. Es gab mehrere Möglichkeiten, meinen Dienst vor Zugriffen zu schützen, wie etwa den Entitäten verschiedene Berechtigungen zu geben, die Dienstnutzung durch Abfrageinterceptors zu verhindern oder die Möglichkeiten der Dienstnutzung zu steuern.

Es wäre allerdings mühsam gewesen, Abfrageinterceptors zu implementieren oder Berechtigungen festzulegen. Außerdem zog ich es vor, eine Sicherheitsschicht auf den Dienst aufzusetzen, um ihn vor Nutzung zu schützen, anstatt mehr Code schreiben zu müssen. Die Implementierung eines allgemeinen Sicherheitsmechanismus, durch den vertrauenswürdige Personen oder externe Unternehmen auf die Dienste zugreifen können, war die ideale Lösung. Im Gegenzug konnte ich diese Sicherheitsmaßnahme mit dem Entitätenschutz kombinieren und so für eine besonders sichere und flexible Dienstimplementierung sorgen.

Diese Vorgehensweise bedeutet, dass sich jeder Nutzer des Diensts zunächst über ACS authentifizieren muss, um ein gültiges Zugangstoken zu erhalten. Ohne Zugangstoken ist der Zugriff eingeschränkt. Bevor der Zugang zum Dienst gewährt wird, muss der Anforderungsheader ein gültiges Zugangstoken aufweisen. Nachdem sich der Dienstnutzer autorisiert hat, erhalten die Entitäten einen fein abgestuften Sicherheitsmechanismus, damit nur autorisierte Personen Zugriff auf die Daten oder Entitäten haben.

Einrichtung und Konfiguration von ACS

Für die Implementierung von ACS ist ein wenig Einrichtungs- und Konfigurationsarbeit erforderlich. Abbildung 4 zeigt, was ich für dieses Beispiel eingerichtet habe.

Abbildung 4: Einrichtung von ACS

Konfiguration Werte
ACS-Namespace WCFoDataACS
Anwendung der vertrauenden Seite

Name: wcfodataacsexampleDevelopment

Modus: Standardwert (Einstellungen manuell eingeben)

Bereich: http://localhost/WCFDataServiceExample/<servicename>.svc

Rückgabe-URL: Standardwert (leer lassen)

Fehler-URL: Standardwert (leer lassen)

Tokenformat: SWT

Verschlüsselungsrichtlinie für Token: Standardwert (Keine)

Tokengültigkeitsdauer: Standardwert (600)

Authentifizierungseinstellungen

Identitätsprovider: Windows Live ID deaktivieren

Regelgruppe: „Neue Regelgruppe erstellen“ aktivieren

Hinweis: Ich habe für Entwicklung, Tests und Produktion unterschiedliche Einstellungen gewählt.

Tokensignatureinstellungen

Auf die Schaltfläche „Generieren“ klicken

Gültigkeitsdatum: Standardwert

Ablaufdatum: Standardwert

Regelgruppe Hinweis: Die Regelgruppe wird auf Grundlage der gewählten Einstellungen automatisch erstellt. Es muss nur noch die Anspruchskonfiguration hinzugefügt werden.
Anspruchskonfiguration

If-Abschnitt:

Zugriffssteuerungssystem: Ausgewählt

Eingabeanspruchtyp: Standardwert (beliebig)

Eingabeanspruchwert: Standardwert (beliebig)

Then-Abschnitt:

Ausgabeanspruchtyp: Standardwert (durch Eingabeanspruchtyp leiten)

Ausgabeanspruchwert: Standardwert (durch Eingabeanspruchwert leiten)

Regelinformationen:

Beschreibung: Standardwert lassen oder Beschreibung eingeben

Dienstidentität in Windows

Name: der Benutzername, der weitergegeben wird (in diesem Beispiel habe ich wcfodataacsDevUser verwendet)

Beschreibung: Standardwert lassen (oder Beschreibung für Benutzer eingeben)

Bereich: http://localhost/WCFDataServiceExample/<servicename>.svc

Anmeldeinformationseinstellungen:

Typ: Kennwort wählen

Kennwort: gewünschtes Kennwort eingeben

Gültigkeitsdatum: Standardwert

Ablaufdatum: Standardwert

Hinweis: Es gibt mehrere Möglichkeiten, wie ein Benutzer in den erstellten Diensten authentifiziert werden kann. Der Einfachheit halber habe ich als Anmeldeinformationstyp die Kennworteingabe gewählt. Andere Möglichkeiten bieten eventuell mehr Sicherheit, wie ein x509-Zertifikat oder ein symmetrischer Schlüssel. Aber zu Anschauungszwecken habe ich mich auf das Einfache beschränkt.

Nach der Einrichtung von ACS konnte ich meine OData-WCF-RESTful-Dienste sichern. Doch zuvor musste ich noch ein benutzerdefiniertes Sicherheitsmodul implementieren, über das die Anforderungen abgefangen und die Sicherheit überprüft werden, um nicht autorisierte Zugriffe zu verhindern.

ACS-Sicherheitsimplementierung

Als Beispiel habe ich die Sicherheit mit einem benutzerdefinierten HTTP-Modul implementiert. Dieses Modul fängt alle Anforderungen an meinen Dienst ab und überprüft, ob die richtige Authentifizierung und Autorisierung vorliegt. Ohne dieses HTTP-Modul wären meine Dienste je nach Einstellungen bei der Datendienstkonfiguration nur auf Entitätsebene sicher.

In diesem Fall habe ich die Dienste mit ACS gesichert. Dadurch wurden Anforderungen abgefangen und dahin gehend überprüft, ob die richtige Sicherheitsstufe vorliegt. So konnte sichergestellt werden, dass die Dienstnutzer über die entsprechende Autorisierung verfügen. Wie bereits erwähnt wurde ein fein abgestufter Sicherheitsmechanismus auf Entitätsebene implementiert, der greift, nachdem der Zugriff autorisiert wurde.

Bei der Implementierung der IHTTPModule-Schnittstelle entschied ich mich für einige weitere Features. Damit die Dienstnutzer automatische Klassen generieren können, habe ich Teile der Dienstmetadaten verfügbar gemacht (vergleichbar mit dem Hinzufügen anderer beliebiger Webdienste). Diese Codeabschnitte habe ich als konfigurierbare Attribute hinzugefügt. Sie können aktiviert und deaktiviert werden, um mehr Sicherheit zu bieten, Tests zu erleichtern und die Integration zu vereinfachen.

Abbildung 5 zeigt den Code, mit dem Anforderungen abgefangen und Sicherheitsüberprüfungen durchgeführt werden.

Abbildung 5: Sicherheitsüberprüfung

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Web;
using Microsoft.AccessControl2.SDK;
namespace Azure.oAuth.SecurityModule
{
  internal class SWTModule : IHttpModule
  {
    // Service namespace setup in Windows Azure
    string _serviceNamespace = 
      ConfigurationManager.AppSettings["serviceNamespace"];
    // ACS name
    string _acsHostName = ConfigurationManager.AppSettings["acsHostname"];
    // The key for which the service was signed
    string _trustedSigningKey =
      ConfigurationManager.AppSettings["trustedSigningKey"];
    // The URL that was setup in the rely party in Windows Azure
    string _trustedAudience = 
      ConfigurationManager.AppSettings["trustedAudience"];
    // Setting to allow the metadata to be shown
    bool _enableMetaData =  
       Convert.ToBoolean(ConfigurationManager.AppSettings["enableMetadata"]);
    // Setting to disable or enable the security module
    bool _disableSecurity =
      Convert.ToBoolean(ConfigurationManager.AppSettings["disableSecurity"]);
    const string _metaData = "$metadata";
    private void context_BeginRequest(object sender, EventArgs e)
    {
      if (!_disableSecurity)
      {
        string tempAcceptableURLs = String.Empty;
        // Check if the audiencename has trailing slash
        tempAcceptableURLs = _trustedAudience.ToLower();
        if (tempAcceptableURLs.Substring(_trustedAudience.Length - 1, 1) == "/")
        {
          tempAcceptableURLs =
            tempAcceptableURLs.Substring(0, _trustedAudience.Length - 1);
        }
        // First check if the person is requesting the WSDL or .svc
        if (_enableMetaData != false
          && HttpContext.Current.Request.Url.AbsoluteUri.ToLower() !=
          tempAcceptableURLs
          && HttpContext.Current.Request.Url.AbsoluteUri.ToLower() !=
          tempAcceptableURLs + _metaData
          && HttpContext.Current.Request.Url.AbsoluteUri.ToLower() !=
          tempAcceptableURLs + "/"
          && HttpContext.Current.Request.Url.AbsoluteUri.ToLower() !=
          tempAcceptableURLs + "/" + _metaData)
        {
          // SWT Validation...
          // Get the authorization header
          string headerValue =
            ttpContext.Current.Request.Headers.Get("Authorization");
          // Check that a value is there
          if (string.IsNullOrEmpty(headerValue))
          {
            throw new ApplicationException("unauthorized-1.1");
          }
          // Check that it starts with 'WRAP'
          if (!headerValue.StartsWith("WRAP "))
          {
            throw new ApplicationException("unauthorized-1.2");
          }
          // ... <code truncated> ...
        }
      }
    }
  }
}

Windows Azure SDK

Für die Tokenüberprüfung im Rahmen dieser Implementierung habe ich eine Klasse aus dem Windows Azure SDK genommen. Das Projekt finden Sie unter bit.ly/utQd3S. Nach der Installation des SDK habe ich die Datei „tokenvalidator.cs“ in ein neues Projekt kopiert. Bei dieser Klasse habe ich die Überprüfungsmethode aufgerufen, um zu bestimmen, ob der Benutzer über die in ACS konfigurierten Daten autorisiert ist. Damit die Implementierung einfacher wird, habe ich eine benutzerdefinierte DLL nur mit dem benötigten Sicherheitsmechanismus erstellt. Nach der Zusammenstellung benötigte ich nur noch einen Verweis vom OData-WCF-Dienst auf die Sicherheits-DLL. Das Ergebnis: eine sichere und geschützte Implementierung.

Implementierung des sicheren OData-Diensts

Durch die zusätzliche Sicherheitsmaßnahme wurde das Schützen des OData-WCF-Diensts ein Leichtes. Ich musste nur noch einen Verweis auf die Assembly „Azure.AccessControl.SecurityModule“ erstellen und die zusätzlichen Konfigurationseinstellungen hinzufügen. Danach waren alle Sicherheitsmaßnahmen aktiv. In Abbildung 6 sehen Sie die Einstellungen für die Sicherheitskonfiguration.

Abbildung 6. Die Einstellungen für die Sicherheitskonfiguration

<appSettings>
  <add key="acsHostName" value="accesscontrol.windows.net" />
  <add key="serviceNamespace" value="Service Namespace" />
  <add key="trustedAudience"
    value="http://localhost/WCFDataServiceExample/NorthwindService.svc/" />
  <add key="trustedSigningKey" value="Trusted Signing Key" />
  <add key="enableMetadata" value="true" />
  <add key="disableSecurity" value="false"/>
</appSettings>
<system.webServer>
  <validation validateIntegratedModeConfiguration="false" />
  <modules runAllManagedModulesForAllRequests="true">
    <add name="SWTModule" type="Azure.AccessControl.SecurityModule.SWTModule,
      Azure.AccessControl.SecurityModule" preCondition="managedHandler" />
  </modules>
</system.webServer>

Je nach Sicherheitseinstellungen können die Berechtigungen so eingeschränkt werden, dass Dienstnutzer nur die Metadaten sehen. Das ist von großem Vorteil, da die Nutzer weiterhin die Entitätsobjekte und die Eigenschaften im Code referenzieren können, was die Implementierung vereinfacht. Damit die Dienstnutzer keinen Zugriff mehr auf die Metadaten haben, habe ich das Attribut „enableMetadata“ auf „False“ gesetzt. Sollten die Dienstnutzer nur über clientseitigen Code auf den Dienst zugreifen, würde ich die Metadaten nicht aktivieren, da es nicht nötig wäre. Bei aktivierten Metadaten hingegen sieht der Dienst wie ein normaler Webdienst aus. Der einzige Unterschied ist, dass der Dienst ohne passende Authentifizierung und Autorisierung nicht genutzt werden kann (siehe Abbildung 7).

OData WCF Service with Metadata Exposed
Abbildung 7: OData-WCF-Dienst mit verfügbar gemachten Metadaten

Dieses Vorgehen entspricht fast genau der direkten Nutzung von Entity Framework für den Implementierungscode. Es gibt lediglich ein paar kleine Unterschiede. Das wichtigste Codesegment, das hinzugefügt werden muss, ist das beim Senden der Daten an den OData-WCF-Dienst erforderliche Token im Anforderungsheader. Ich erläutere kurz die Funktionsweise des Sicherheitsmechanismus. Zunächst wird der Header auf ein gültiges Token überprüft. Außerdem wird der Zustand aller Komponenten, wie Zielgruppe, Tokenablaufdatum und Tokenwert, geprüft. Als Nächstes wird die Anforderung autorisiert und der Dienstaufruf ist erfolgreich. Durch das Abfangen der Anforderung vor der Rückgabe von Daten an den Dienstnutzer wird sichergestellt, dass der Dienstaufrufer ein gültiges Token hat, bevor er Zugriff auf die Daten erhält.

Jetzt ist der Dienstnutzer je nach Sicherheitsstufe, die für die Entitätsobjekte erforderlich ist, in der Lage, auf Basis der gewählten Sicherheitseinstellungen die vom Dienst verfügbar gemachten Funktionen zu nutzen. Wenn der Sicherheitsmechanismus greift, erhält der Dienstnutzer eine Ausnahmemeldung, dass die gewünschte Aktion nicht zugelassen ist.

Anders als beim herkömmlichen Entity Framework-Code erfordert die Implementierung vor dem Aufrufen des mit Windows Azure gesicherten OData-Diensts mehr Logik. Da der Dienst durch das HTTP-Modul gesichert wird, muss ich mich zuerst bei Windows Azure authentifizieren und ein gültiges Zugangstoken erhalten, bevor ich den OData-Dienst aufrufen kann. Bei jeder Anforderung an den sicheren OData-Dienst wird das von ACS erhaltene Token durch den Anforderungsheader geleitet. Abbildung 8 enthält ein Beispiel für eine Anforderung.

Abbildung 8: Beispiel einer Tokenanforderung

// Request a token from ACS
using (WebClient client = new WebClient())
{
  client.BaseAddress = string.Format("https://{0}.{1}",
    _accessControlNamespace, _accessControlHostName);
  NameValueCollection values = new NameValueCollection();
  values.Add("wrap_name", wrapUsername);
  values.Add("wrap_password", wrapPassword);
  values.Add("wrap_scope", scope);
  byte[] responseBytes =
    client.UploadValues("WRAPv0.9/", "POST", values);
  response = Encoding.UTF8.GetString(responseBytes);
  Console.WriteLine("\nreceived token from ACS: {0}\n", response);
}

Nachdem das Token von Windows Azure zurückerhalten wurde und der Benutzer erfolgreich authentifiziert und autorisiert ist, gibt ACS ein Token zurück, das bis zum Ablauf des Tokens für alle weiteren Anforderungen verwendet wird. An dieser Stelle gleicht die Implementierung des Entity Framework beinahe dem Vorgehen, wenn ich mit einer lokalen Datenbank oder einer Datenbank in meinem Netzwerk verbunden bin. Abbildung 9 zeigt die Nutzung des OData-Diensts mit einem Zugangstoken.

Abbildung 9: Nutzung des sicheren OData-Diensts mit einem Windows Azure-Zugangstoken

// First things first: I obtain a token from Windows Azure
_token = GetTokenFromACS(_rootURL + "NorthwindService.svc");
// Now that I have a valid token, I can call the service as needed
Uri uri = new Uri(_rootURL + "NorthwindService.svc/");
try
{
  var northwindEntities = new ODataServiceClient.NorthwindEntities(uri);
  // Add the event handler to send the token in my request header
  northwindEntities.SendingRequest += new
    EventHandler<SendingRequestEventArgs>(OnSendingRequest);
  // Sample selecting data out ...
  var customersFound = from customers in northwindEntities.Customers
    select customers;
  foreach (var customer in customersFound)
  {
    // custom process ...
    // ... <code truncated> ...
    }
    // Add new data in ...
    var category = oDataServiceClient.Category.CreateCategory(0, "New category");
    northwindEntities.AddToCategories(category);
    northwindEntities.SaveChanges();
}
catch (DataServiceRequestException e)
{
  // Trap any data service exceptions such as a security error
  // In the event that the security does not allow an insert,
  // a forbidden error will be returned
  // ...
}

Die Implementierung des Codes über ein clientseitiges Skript ist ebenso einfach wie ein AJAX-Aufruf an den Dienstendpunkt. In Abbildung 10 sehen Sie, wie der sichere OData-Dienst über ein clientseitiges Skript genutzt wird.

Abbildung 10: Nutzung des sicheren OData-Diensts über ein clientseitiges Skript

// Parse the entity object into JSON
var jsonEntity = window.JSON.stringify(entityObject);
$.support.cors = true;
// Asynchronous AJAX function to create a Cateogory using OData
$.ajax({
  type: "POST",
  contentType: "application/json; charset=utf-8",
  datatype: "jsonp",
  url: serverUrl + ODATA_ENDPOINT + "/" + odataSetName,
  data: jsonEntity,
  beforeSend: function (XMLHttpRequest) {
  // Specifying this header ensures that the results will be returned as JSON
  XMLHttpRequest.setRequestHeader("Accept", "application/json");
  XMLHttpRequest.setRequestHeader("Authorization", token);
  },
  success: function (data, textStatus, XmlHttpRequest) {
  if (successCallback) {
    successCallback(data.d, textStatus, XmlHttpRequest);
    }
  },
  error: function (XmlHttpRequest, textStatus, errorThrown) {
  if (errorCallback)
    errorCallback(XmlHttpRequest, textStatus, errorThrown);
  else
    errorHandler(XmlHttpRequest, textStatus, errorThrown);
  }
});

Ein RESTful-Dienst bietet eine größere Flexibilität bei der Implementierung und kann einfach über Java oder andere clientseitige Skripts oder APIs genutzt werden. Für die Dienstnutzung sind weiterhin Authentifizierung und Token erforderlich, doch OData ist unabhängig von der Plattform aufgrund der Abfragesyntax Standard. In Abbildung 11 sehen Sie die Nutzung des sicheren OData-Diensts mit einem Windows Azure-Zugangstoken in Java.

Abbildung 11: Java-Implementierung der Nutzung des sicheren OData-Diensts mit einem Windows Azure-Zugangstoken

String serviceMethodUrl =  
  "http://localhost/WCFDataServiceExample/NorthwindService.svc/Categories?";
GetMethod method = new GetMethod(serviceMethodUrl + "$top=1");
method.addRequestHeader("Authorization", 
  "WRAP access_token=\"" + authToken + "\"");
try
{
  int returnCode = client.executeMethod(method);
  // ... <code truncated> ...
  br = new BufferedReader(new 
    InputStreamReader(method.getResponseBodyAsStream()));
  String readLine;
  while(((readLine = br.readLine()) != null))
  {
    //System.err.println(readLine);
    result += readLine;
  }
}

Zusammenfassend lässt sich sagen, dass ich häufig Daten auf eine Art und Weise verfügbar machen muss, die eine gewisse Sicherheit erfordern, um unautorisierte Zugriffe zu verhindern. Diesem Anspruch kann ich mit ACS gerecht werden. Denn durch einen cloudbasierten Verbunddienst werden nicht nur meine OData-WCF-Datendienste geschützt, sondern auch viele weitere Anwendungen.

Würde allerdings nur WCF Data Services für sich verwendet werden, wäre für die Verfügbarmachung der Daten die Implementierung individueller Datenverträge und Abfrageinterceptors erforderlich. Durch die Kombination von WCF Data Services und Entity Framework können Datenbankentitäten als Datenverträge eingesetzt werden. Ein weiterer Pluspunkt ist, dass diese Verträge in einem bereits eingerichteten Format zur Verfügung gestellt werden, nämlich als serialisierbare Objekte, auf die über OData zugegriffen werden kann. Zum Schluss muss noch sichergestellt werden, dass meine OData-WCF-RESTful-Dienste vor unautorisiertem Zugriff geschützt sind. Durch den Einsatz von ACS, OData und Entity Framework im Verbund mit WCF-RESTful-Diensten können Daten bei einer Standardabfragesyntax schnell verfügbar gemacht werden. Dabei sind sie durch zusätzliche Sicherheitsmaßnahmen geschützt.

Sean Iannuzzi ist Lösungsarchitekt bei The Agency Inside Harte-Hanks, wo er sich mit bewährten Methoden um Enterprise-, System und Softwarelösungen kümmert. Er erlernt gerne neue Technologien und ist für die Problemlösung von Unternehmen und Entwicklern stets auf der Suche nach neuen Nutzungsmöglichkeiten von Technologien. Er veröffentlicht Blogs unter weblogs.asp.net/seaniannuzzi, und Sie können ihm auf Twitter unter twitter.com/seaniannuzzi folgen.

Unser Dank gilt dem folgenden technischen Experten für die Durchsicht dieses Artikels: Danilo Diaz