Verwenden von SAML-Ansprüchen, SharePoint, WCF, des Forderungen an den Windows-Tokendiensts und der eingeschränkten Delegierung für den Zugriff auf SQL Server

Veröffentlichung des Originalartikels: 07.08.2011

Dies ist wahrscheinlich der Betrag mit dem längsten Titel, den ich je verfasst habe, denn ich wollte sicherstellen, dass alle relevanten Technologien berücksichtigt werden. Die Frage, die zuletzt vielfach diskutiert wurde und die ich beantworten möchte, lautet, wie ein Benutzer von SAML-Ansprüchen einen Windows-Kontext erhalten kann, um auf andere Anwendungen zuzugreifen. SharePoint 2010 bietet eine eingeschränkte Unterstützung für den Einsatz des Forderungen an den Windows-Tokendiensts, jedoch nur für Benutzer von Windows-Ansprüchen mit einer kleinen Anzahl von Dienstanwendungen. Eine gängige Frage ist, warum nicht ein Benutzer mit SAML-Ansprüchen mit einem gültigen UPN-Anspruch verwendet werden kann, denn aus technologischer Sicht spricht eigentlich nichts dagegen. Angesichts der Einschränkung bei den Authentifizierungstypen und bei Dienstanwendungen, die diese verwenden können, müssen auch Sie ggf. für eine Möglichkeit sorgen, SAML-Benutzer mit anderen Anwendungen als mit ihrem zugrunde liegenden Windows-Konto zu verbinden. In diesem Beitrag möchte ich die Grundlagen vermitteln, wie dies erreicht werden kann.

Der Standardansatz in diesem Szenario ist das Erstellen einer WCF-Dienstanwendung, die alle Endbenutzeranforderungen nach Daten aus der anderen Anwendung verarbeitet, die in unserem Fall SQL Server ist. Ich wähle also einen SAML-Benutzer, der die SharePoint-Website besucht, und möchte eine Anforderung als das Windows-Konto dieses SAML-Benutzers stellen, wenn ich Daten aus SQL Server abrufe. HINWEIS: Auch wenn es in diesem Artikel um Benutzer von SAML-Ansprüchen geht, kann dieselbe Vorgehensweise auch bei Benutzern von Windows-Ansprüchen befolgt werden, die standardmäßig einen UPN-Anspruch erhalten, wenn sie sich anmelden. Hier sehen Sie ein Diagramm des gesamten Prozesses:

Konfigurieren von SQL Server

Lassen Sie uns auf der SQL Server-Seite beginnen. In meinem Szenario wird SQL Server auf dem Server „SQL2“ ausgeführt. Der SQL-Dienst selbst wird als Netzwerkdienst ausgeführt, weshalb für diesen kein Dienstprinzipalname (SPN) erstellt werden muss. Würde der Dienst als Domänenkonto ausgeführt, müsste ich für dieses Dienstkonto für MSSQLSvc einen SPN erstellen. Für dieses spezielle Szenario verwende ich die alte Datenbank Northwinds zum Abrufen von Daten. Ich möchte die Identität des Benutzers einfach veranschaulichen, weshalb ich die gespeicherte Prozedur TenMostExpensiveProducts wie folgt geändert habe:

 

CREATE procedure [dbo].[TenProductsAndUser] AS

SET ROWCOUNT 10

SELECT Products.ProductName AS TenMostExpensiveProducts, Products.UnitPrice, SYSTEM_USER As CurrentUser

FROM Products

ORDER BY Products.UnitPrice DESC

 

Der wichtigste hier zu beachtende Punkt ist, dass ich SYSTEM_USER der SELECT-Anweisung hinzugefügt habe, was lediglich bewirkt, dass der aktuelle Benutzer in der Spalte zurückgegeben wird. Wenn ich also eine Abfrage ausführe und die Ergebnisse erhalte, enthält das Raster eine Spalte mit dem Namen des aktuellen Benutzers, sodass ich mühelos erkennen kann, ob die Abfrage mit der Identität des aktuellen Benutzers ausgeführt wurde oder nicht. In diesem Szenario habe ich drei Windows-Benutzern Rechte zum Ausführen dieser gespeicherten Prozedur erteilt. Alle anderen Benutzer sind dazu nicht berechtigt (was auch ein nützliches Beispiel in der endgültigen Ausgabe sein wird).

Erstellen der WCF-Dienstanwendung

Als Nächstes habe ich eine WCF-Dienstanwendung erstellt, die die Daten aus SQL Server abgerufen hat. Dabei habe ich die Anleitungen befolgt, die ich zuvor im 2. Teil des Beitrags zum CASI Kit beschrieben habe (https://blogs.msdn.com/b/sharepoint_de/archive/2010/12/17/toolkit-f-252-r-die-integration-von-forderungen-azure-und-sharepoint-teil-2.aspx). Dies erfolgte zum Einrichten der Vertrauensstellung zwischen der SharePoint-Farm und WCF-Anwendung. Zweck ist das Abrufen der Ansprüche des Benutzers, der die Anforderung stellt. Das alleinige Übergeben des UPN-Anspruchs als Parameter ist nicht ratsam, da dann Dritte die Identität einer anderen Person vortäuschen könnten, indem einfach ein anderer UPN-Anspruchswert übergeben wird. Nachdem die Vertrauensstellung zwischen WCF und SharePoint ordnungsgemäß konfiguriert wurde, konnte ich mit dem Schreiben meiner Methode fortfahren, die Folgendes ausführt:

  • den UPN-Anspruch extrahiert
  • die Identität des Benutzers des Forderungen an den Windows-Tokendiensts annimmt
  • die Daten aus SQL Server als dieser Benutzer abruft

 

Es folgt der entsprechende Code:

 

//für dieses Codebeispiel wurde Folgendes hinzugefügt:

using Microsoft.IdentityModel;

using Microsoft.IdentityModel.Claims;

using System.Data;

using System.Data.SqlClient;

using System.Security.Principal;

using Microsoft.IdentityModel.WindowsTokenService;

using System.ServiceModel.Security;

 

 

public DataSet GetProducts()

{

 

   DataSet ds = null;

 

   try

   {

       string conStr = "Data Source=SQL2;Initial Catalog=

       Northwind;Integrated Security=True;";

 

       //die aktuelle Anspruchsidentität abfragen

       IClaimsIdentity ci =

          System.Threading.Thread.CurrentPrincipal.Identity as IClaimsIdentity;

 

       //sicherstellen, dass der Anforderung eine Anspruchsidentität angefügt wurde

       if (ci != null)

       {

          //prüfen, ob Ansprüche vorhanden sind, ehe dies durchlaufen wird

          if (ci.Claims.Count > 0)

          {

              //nach dem UPN-Anspruch suchen

              var eClaim = from Microsoft.IdentityModel.Claims.Claim c in ci.Claims

              where c.ClaimType == System.IdentityModel.Claims.ClaimTypes.Upn

              select c;

 

              //bei einer Übereinstimmung den Wert für die Anmeldung abrufen

              if (eClaim.Count() > 0)

              {

                 //den UPN-Anspruchswert abrufen

                 string upn = eClaim.First().Value;

 

                 //die Windows-Identität für den Identitätswechsel erstellen

                 WindowsIdentity wid = null;

 

                 try

                 {

                     wid = S4UClient.UpnLogon(upn);

                 }

                 catch (SecurityAccessDeniedException adEx)

                 {

                           Debug.WriteLine("Could not map the upn claim to " +

                     "a valid windows identity: " + adEx.Message);

                 }

 

                 //prüfen, ob eine Anmeldung möglich ist

                 if (wid != null)

                 {

                        using (WindowsImpersonationContext ctx = wid.Impersonate())

                    {

                       //die Daten aus SQL Server abrufen

                        using (SqlConnection cn = new SqlConnection(conStr))

                        {

                           ds = new DataSet();

                           SqlDataAdapter da =

                               new SqlDataAdapter("TenProductsAndUser", cn);

                           da.SelectCommand.CommandType =

                               CommandType.StoredProcedure;

                           da.Fill(ds);

                        }

                     }

                 }

              }

          }

       }

   }

   catch (Exception ex)

   {

       Debug.WriteLine(ex.Message);

   }

 

   return ds;

}

 

Letztlich ist dies kein sehr komplizierter Code, den ich jetzt im Einzelnen kurz erläutern möchte. Zunächst stelle ich sicher, dass ein gültiger Anspruchsidentitätskontext vorliegt. Falls ja, frage ich anschließend die Liste der Ansprüche auf den UPN-Anspruch ab. Sofern ich den UPN-Anspruch finde, extrahiere ich dessen Wert und rufe den Forderungen an den Windows-Tokendienst auf, um eine S4U-Anmeldung (Service For User) als dieser Benutzer auszuführen. Ist diese Anmeldung erfolgreich, wird eine Windows-Identität zurückgegeben. Mithilfe dieser Windows-Identität erstelle ich dann einen Identitätswechselkontext. Danach erstelle ich die Identität des Benutzers annehmend meine Verbindung mit SQL Server und rufe die Daten ab. Hier nun noch ein paar Tipps zur Problembehandlung, die Sie ggf. beachten sollten:

  1. Wenn Sie den Forderungen an den Windows-Tokendienst nicht so konfiguriert haben, dass Ihr Anwendungspool ihn verwenden darf, erhalten Sie einen Fehler, der im äußeren Catch-Block erfasst wird. Der Fehler lautet ungefähr wie folgt: „WTS0003: Der Aufrufer ist nicht berechtigt, auf den Dienst zuzugreifen.“ Weiter unten finden Sie Einzelheiten und einen Link zur Konfiguration des Forderungen an den Windows-Tokendiensts.
  2. Wenn die eingeschränkte Kerberos-Delegierung nicht ordnungsgemäß eingerichtet ist, wird beim Versuch der Ausführung der gespeicherten Prozedur mit der Codezeile da.Fill(ds); eine Ausnahme ausgelöst, die besagt, dass der anonyme Benutzer keine Rechte zum Ausführen der gespeicherten Prozedur hat. Weiter unten gebe ich verschiedene Tipps zum Konfigurieren der eingeschränkten Delegierung.

Konfigurieren des Forderungen an den Windows-Tokendiensts

Der Forderungen an den Windows-Tokendienst ist standardmäßig so konfiguriert, dass der Start a) manuell erfolgt und b) keiner für seine Nutzung berechtigt ist. Ich habe ihn so geändert, dass a) der Start automatisch erfolgt und b) der Anwendungspool meiner WCF-Dienstanwendung ihn verwenden darf. Anstatt hier die Details zur Konfiguration dieser Autorisierung darzulegen, empfehle ich Ihnen die Lektüre dieses Artikels, an dessen Ende Sie die Konfigurationsinformationen finden: https://msdn.microsoft.com/en-us/library/ee517258.aspx. Damit sollten Sie die ersten Schritte in den Griff bekommen. Weitere Hintergrundinformationen zum Forderungen an den Windows-Tokendienst finden Sie in diesem Artikel: https://msdn.microsoft.com/en-us/library/ee517278.aspx.

 

HINWEIS: Dieser Artikel enthält einen GROSSEN Fehler. Es wird empfohlen, eine Abhängigkeit für den Forderungen an den Windows-Tokendienst durch Ausführen dieses Codes zu erstellen: sc config c2wts depend=cryptosvcUNTERLASSEN SIE DAS!! Dies ist ein Tippfehler, denn „cryptosvc“ ist kein gültiger Dienstname, zumindest nicht unter Windows Server 2008 R2. Wenn Sie den Befehl dennoch ausführen, wird der Forderungen an den Windows-Tokendienst nicht mehr gestartet, da angezeigt wird, dass die Abhängigkeit zum Löschen markiert ist oder nicht gefunden werden kann. Mir ist das passiert, und ich habe die Abhängigkeit in iisadmin geändert (was logisch ist, da in meinem Fall mindestens mein WCF-Host für mich ausgeführt werden muss, um den Forderungen an den Windows-Tokendienst auszuführen). Andernfalls landete ich in einer Sackgasse.

Konfigurieren der eingeschränkten Kerberos-Delegierung

Okay, bevor jemand zu panisch angesichts dieses Themas wird, möchte ich eines anmerken:

  1. Ich gehe hier nicht allzu sehr ins Detail dahingehend, wie die eingeschränkte Kerberos-Delegierung funktioniert, da es im Internet bereits Bände zu diesem Thema gibt.
  2. Nebenbei bemerkt, funktionierte dieser Teil relativ reibungslos, als ich diesen Beitrag verfasste.

 

Lassen Sie uns nun die für die Delegierung erforderlichen Schritte durchlaufen. Zunächst einmal wird, wie schon erwähnt, mein SQL Server-Dienst als Netzwerkdienst ausgeführt, weshalb ich hierfür keine Konfiguration vornehmen muss. Zweitens wird mein WCF-Anwendungspool als Domänenkonto mit dem Namen vbtoysportal ausgeführt. Hierfür sind zwei Dinge erforderlich:

  1. Erstellen eines HTTP-SPN unter Verwendung sowohl des NetBIOS-Namens als auch den vollqualifizierten Namens des Servers, an den die Delegierung erfolgt. In meinem Fall hat der WCF-Server den Namen „AZ1“, weshalb ich zwei SPN wie die folgenden erstellt habe:
    1. setspn -A HTTP/az1 vbtoysportal
    2. setspn -A HTTP/az1.vbtoys.com vbtoysportal
  2. Ich muss mein Konto so konfigurieren, dass ihm für die eingeschränkte Kerberos-Delegierung an die SQL Server-Dienste vertraut wird, die auf dem Server „SQL2“ ausgeführt werden. Zu diesem Zweck wechselte ich auf meinen Domänencontroller und öffnete Active Directory-Benutzer und -Computer. Ich doppelklickte auf den Benutzer vbtoysportal und klickte dann auf die Registerkarte Delegierung, um diese Vertrauensstellung zu konfigurieren. Ich richtete lediglich eine Delegierung für bestimmte Dienste unter Verwendung eines beliebigen Authentifizierungsprotokolls ein. Hier ist ein Link zu einer Abbildung, die die Konfiguration der Delegierung veranschaulicht:

 

Drittens musste ich meinen WCF-Anwendungsserver als vertrauenswürdig für die eingeschränkte Delegierung konfigurieren. Zum Glück ist der Prozess mit dem für Benutzer identisch. Wechseln Sie in Active Directory-Benutzer und -Computer zum Computerkonto und konfigurieren Sie es entsprechend. Hier ist ein Link zu einer Abbildung dieser Konfiguration:

 

 

Damit sind alle nicht SharePoint-bezogenen Komponenten eingerichtet, konfiguriert und betriebsbereit. Als Letztes wird ein Webpart benötigt, um einen Test durchzuführen.

Erstellen des SharePoint-Webparts

Das Erstellen des Webparts ist nicht schwierig. Ich befolgte dazu das zuvor beschriebene Muster zum Erstellen von WCF-Aufrufen an SharePoint und Übergeben der Identität des aktuellen Benutzers (https://blogs.technet.com/b/speschka/archive/2010/09/08/calling-a-claims-aware-wcf-service-from-a-sharepoint-2010-claims-site.aspx). Ich hätte auch das CASI Kit verwenden können, um die Verbindung herzustellen und die WCF aufzurufen, doch ich entschloss mich, dies manuell zu tun, damit die Schritte besser veranschaulicht werden konnten. Es folgen die grundlegenden Schritte zum Erstellen des Webparts:

  1. Erstellen eines neuen SharePoint 2010-Projekts in Visual Studio 2010
  2. Erstellen eines Dienstverweises auf meine WCF-Dienstanwendung
  3. Hinzufügen eines neuen Webparts
  4. Hinzufügen des Codes zum Webpart zum Abrufen der Daten aus der WCF und deren Anzeige in einem Raster
  5. Hinzufügen aller Informationen in der Datei app.config, die im Visual Studio-Projekt generiert wird, zum Abschnitt <system.ServiceModel> der Datei web.config für die Webanwendung, in der mein Webpart gehostet wird

HINWEIS: Die Datei app.config enthält das Attribut decompressionEnabled. Sie müssen dieses Attribut löschen, bevor Sie die Datei der Datei WEB.CONFIG hinzufügen. Wenn Sie es nicht löschen, löst Ihr Webpart einen Fehler aus, sobald Sie versuchen, eine Instanz Ihres Dienstverweisproxys zu erstellen.

Mit Ausnahme von Schritt 4 sind alle obigen Schritte unproblematisch, weshalb sie hier nicht im Detail behandelt werden sollen. Der Code für das Webpart sieht hingegen wie folgt aus:

private DataGrid dataGrd = null;

private Label statusLbl = null;

 

 

protected override void CreateChildControls()

{

   try

   {

       //die Verbindung mit der WCF herstellen und versuchen, die Daten abzurufen

       SqlDataSvc.SqlDataClient sqlDC = new SqlDataSvc.SqlDataClient();

 

       //den Kanal konfigurieren, damit dieser mit "FederatedClientCredentials" aufgerufen werden kann

       SPChannelFactoryOperations.ConfigureCredentials<SqlDataSvc.ISqlData>(

       sqlDC.ChannelFactory, Microsoft.SharePoint.SPServiceAuthenticationMode.Claims);

 

       //den Endpunkt erstellen, mit dem eine Verbindung hergestellt werden soll

       EndpointAddress svcEndPt =

          new EndpointAddress("https://az1.vbtoys.com/ClaimsToSqlWCF/SqlData.svc");

 

       //einen Kanal zum WCF-Endpunkt unter Verwendung des

       //Tokens und der Ansprüche des aktuellen Benutzers erstellen

       SqlDataSvc.ISqlData sqlData =

          SPChannelFactoryOperations.CreateChannelActingAsLoggedOnUser

          <SqlDataSvc.ISqlData>(sqlDC.ChannelFactory, svcEndPt);

 

       //die Daten anfordern

       DataSet ds = sqlData.GetProducts();

 

       if ((ds == null) || (ds.Tables.Count == 0))

       {

          statusLbl = new Label();

          statusLbl.Text = "No data was returned at " + DateTime.Now.ToString();

          statusLbl.ForeColor = System.Drawing.Color.Red;

          this.Controls.Add(statusLbl);

       }

       else

       {

          dataGrd = new DataGrid();

          dataGrd.AutoGenerateColumns = true;

          dataGrd.DataSource = ds.Tables[0];

          dataGrd.DataBind();

          this.Controls.Add(dataGrd);

       }

   }

   catch (Exception ex)

   {

       Debug.WriteLine(ex.Message);

   }

}

 

Auch diese Schritte sind nicht weiter schwierig. Im ersten Teil wird eine Verbindung mit dem WCF-Dienst so hergestellt, dass die Ansprüche des aktuellen Benutzers übergeben werden. Weitere Informationen erhalten Sie, wenn Sie auf den Link oben zu meinem vorherigen Blogbeitrag zu diesem Thema klicken. Anschließend wird nur noch ein Dataset abgerufen und an ein Raster gebunden, sofern Daten vorhanden sind, oder eine Meldung angezeigt, wenn im umgekehrten Fall keine Daten vorhanden sein sollten. Die nachfolgenden drei Bildschirmfotos veranschaulichen die Interaktion dieser Elemente. Die ersten beiden zeigen, wie das Ganze für zwei verschiedene Benutzer funktioniert, was Sie in der Spalte CurrentUser zu erkennen ist. Die dritte Abbildung gehört zu einem Benutzer, dem keine Rechte zur Ausführung der gespeicherten Prozedur erteilt wurden.