Freigeben über


Secure a Web API with Individual Accounts and Local Login in ASP.NET Web API 2.2 (Schützen einer Web-API mit einzelnen Konten und lokaler Anmeldung in ASP.NET-Web-API 2.2)

von Mike Wasson

Beispiel-App herunterladen

In diesem Thema wird gezeigt, wie Sie eine Web-API mit OAuth2 sichern, um sich bei einer Mitgliedschaftsdatenbank zu authentifizieren.

Im Lernprogramm verwendete Softwareversionen

In Visual Studio 2013 bietet ihnen die Web-API-Projektvorlage drei Optionen für die Authentifizierung:

  • Einzelne Konten: Die App verwendet eine Mitgliedschaftsdatenbank.
  • Organisationskonten. Benutzer melden sich mit ihren Azure Active Directory-, Office 365- oder lokalen Active Directory-Anmeldeinformationen an.
  • Windows-Authentifizierung. Diese Option ist für Intranetanwendungen vorgesehen und verwendet das IIS-Modul der Windows-Authentifizierung.

Einzelne Konten bieten zwei Möglichkeiten, wie sich ein Benutzer anmelden kann:

  • Lokale Anmeldung. Der Benutzer registriert sich auf der Website und gibt einen Benutzernamen und ein Kennwort ein. Die App speichert den Kennworthash in der Mitgliedschaftsdatenbank. Wenn sich der Benutzer anmeldet, überprüft das ASP.NET Identity-System das Kennwort.
  • Anmeldung für soziale Netzwerke. Der Benutzer meldet sich mit einem externen Dienst an, z. B. Facebook, Microsoft oder Google. Die App erstellt weiterhin einen Eintrag für den Benutzer in der Mitgliedschaftsdatenbank, speichert jedoch keine Anmeldeinformationen. Der Benutzer authentifiziert sich, indem er sich beim externen Dienst anmeldet.

In diesem Artikel wird das lokale Anmeldeszenario erläutert. Für die lokale und soziale Anmeldung verwendet die Web-API OAuth2 zum Authentifizieren von Anforderungen. Die Anmeldeinformationsflüsse unterscheiden sich jedoch für die lokale und soziale Anmeldung.

In diesem Artikel zeige ich eine einfache App, mit der der Benutzer sich anmelden und authentifizierte AJAX-Aufrufe an eine Web-API senden kann. Hier können Sie den Beispielcode herunterladen. In der Infodatei wird beschrieben, wie Sie das Beispiel ganz neu in Visual Studio erstellen.

Abbildung des Beispielformulars

Die Beispiel-App verwendet Knockout.js für datenbindung und jQuery zum Senden von AJAX-Anforderungen. Ich werde mich auf die AJAX-Aufrufe konzentrieren, daher müssen Sie Knockout.js für diesen Artikel nicht kennen.

Auf dem Weg werde ich Folgendes beschreiben:

  • Was die App auf clientseitiger Seite tut.
  • Was auf dem Server passiert.
  • Der HTTP-Datenverkehr in der Mitte.

Zunächst müssen wir einige OAuth2-Terminologie definieren.

  • Ressource: Einige Daten, die geschützt werden können.
  • Ressourcenserver. Der Server, auf dem die Ressource gehostet wird.
  • Ressourcenbesitzer. Die Entität, die die Berechtigung für den Zugriff auf eine Ressource erteilen kann. (Normalerweise der Benutzer.)
  • Client: Die App, die Zugriff auf die Ressource möchte. In diesem Artikel ist der Client ein Webbrowser.
  • Zugriffstoken. Ein Token, das Zugriff auf eine Ressource gewährt.
  • Bearertoken. Ein bestimmter Zugriffstokentyp mit der Eigenschaft, die jeder verwenden kann. Mit anderen Worten, ein Client benötigt keinen kryptografischen Schlüssel oder einen anderen geheimen Schlüssel, um ein Bearertoken zu verwenden. Aus diesem Grund sollten Bearertoken nur über ein HTTPS verwendet werden und relativ kurze Ablaufzeiten aufweisen.
  • Autorisierungsserver. Ein Server, der Zugriffstoken ausgibt.

Eine Anwendung kann sowohl als Autorisierungsserver als auch als Ressourcenserver fungieren. Die Web-API-Projektvorlage folgt diesem Muster.

Lokaler Anmeldeinformationsfluss

Für die lokale Anmeldung verwendet die Web-API den in OAuth2 definierten Ressourcenbesitzerkennwortfluss .

  1. Der Benutzer gibt einen Namen und ein Kennwort in den Client ein.
  2. Der Client sendet diese Anmeldeinformationen an den Autorisierungsserver.
  3. Der Autorisierungsserver authentifiziert die Anmeldeinformationen und gibt ein Zugriffstoken zurück.
  4. Um auf eine geschützte Ressource zuzugreifen, enthält der Client das Zugriffstoken im Autorisierungsheader der HTTP-Anforderung.

Diagramm des lokalen Anmeldeinformationsflusses

Wenn Sie "Einzelne Konten" in der Web-API-Projektvorlage auswählen, enthält das Projekt einen Autorisierungsserver, der Benutzeranmeldeinformationen überprüft und Token ausgibt. Das folgende Diagramm zeigt den gleichen Anmeldeinformationsfluss in Bezug auf Web-API-Komponenten.

Diagramm, wenn einzelne Konten im Web A P I ausgewählt werden

In diesem Szenario fungieren Web-API-Controller als Ressourcenserver. Ein Authentifizierungsfilter überprüft Zugriffstoken, und das [Authorize] -Attribut wird verwendet, um eine Ressource zu schützen. Wenn ein Controller oder eine Aktion das Attribut [Autorisieren] aufweist, müssen alle Anforderungen an diesen Controller oder diese Aktion authentifiziert werden. Andernfalls wird die Autorisierung verweigert, und die Web-API gibt einen Fehler 401 (Nicht autorisiert) zurück.

Der Autorisierungsserver und der Authentifizierungsfilter rufen beide eine OWIN-Middleware-Komponente auf, die die Details von OAuth2 verarbeitet. Ich werde das Design später in diesem Lernprogramm ausführlicher beschreiben.

Senden einer nicht autorisierten Anforderung

Führen Sie zunächst die App aus, und klicken Sie auf die Schaltfläche "API aufrufen". Nach Abschluss der Anforderung sollte im Feld "Ergebnis " eine Fehlermeldung angezeigt werden. Der Grund dafür ist, dass die Anforderung kein Zugriffstoken enthält, sodass die Anforderung nicht autorisiert ist.

Abbildung der Ergebnisfehlermeldung

Die Schaltfläche "API aufrufen" sendet eine AJAX-Anforderung an ~/api/values, die eine Web-API-Controlleraktion aufruft. Im Folgenden sehen Sie den Abschnitt von JavaScript-Code, der die AJAX-Anforderung sendet. In der Beispiel-App befindet sich der gesamte JavaScript-App-Code in der Datei "Scripts\app.js".

// If we already have a bearer token, set the Authorization header.
var token = sessionStorage.getItem(tokenKey);
var headers = {};
if (token) {
    headers.Authorization = 'Bearer ' + token;
}

$.ajax({
    type: 'GET',
    url: 'api/values/1',
    headers: headers
}).done(function (data) {
    self.result(data);
}).fail(showError);

Bis sich der Benutzer anmeldet, gibt es kein Bearertoken und daher keinen Autorisierungsheader in der Anforderung. Dies führt dazu, dass die Anforderung einen Fehler vom Typ 401 zurückgibt.

Dies ist die HTTP-Anforderung. (Ich habe verwendet Fiddler zum Erfassen des HTTP-Datenverkehrs.)

GET https://localhost:44305/api/values HTTP/1.1
Host: localhost:44305
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:32.0) Gecko/20100101 Firefox/32.0
Accept: */*
Accept-Language: en-US,en;q=0.5
X-Requested-With: XMLHttpRequest
Referer: https://localhost:44305/

HTTP-Antwort:

HTTP/1.1 401 Unauthorized
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/8.0
WWW-Authenticate: Bearer
Date: Tue, 30 Sep 2014 21:54:43 GMT
Content-Length: 61

{"Message":"Authorization has been denied for this request."}

Beachten Sie, dass die Antwort einen Www-Authenticate-Header enthält, bei dem die Abfrage auf Bearer festgelegt ist. Das bedeutet, dass der Server ein Bearertoken erwartet.

Registrieren eines Benutzers

Geben Sie im Abschnitt "Registrieren" der App eine E-Mail und ein Kennwort ein, und klicken Sie auf die Schaltfläche "Registrieren ".

Sie müssen für dieses Beispiel keine gültige E-Mail-Adresse verwenden, aber eine echte App bestätigt die Adresse. (Siehe Erstellen Sie eine sichere ASP.NET MVC 5 Web App mit Anmeldung, E-Mail-Bestätigung und Kennwortzurücksetzung.) Verwenden Sie für das Kennwort etwas wie "Password1!" mit einem Großbuchstaben, Kleinbuchstaben, Zahlen und nicht alphanumerischen Zeichen. Um die App einfach zu halten, habe ich die clientseitige Überprüfung verlassen. Wenn also ein Problem mit dem Kennwortformat vorliegt, wird ein Fehler von 400 (ungültige Anforderung) angezeigt.

Abbildung des Registers eines Benutzerabschnitts

Die Schaltfläche "Registrieren " sendet eine POST-Anforderung an ~/api/Account/Register/. Der Anforderungstext ist ein JSON-Objekt, das den Namen und das Kennwort enthält. Hier sehen Sie den JavaScript-Code, der die Anforderung sendet:

var data = {
    Email: self.registerEmail(),
    Password: self.registerPassword(),
    ConfirmPassword: self.registerPassword2()
};

$.ajax({
    type: 'POST',
    url: '/api/Account/Register',
    contentType: 'application/json; charset=utf-8',
    data: JSON.stringify(data)
}).done(function (data) {
    self.result("Done!");
}).fail(showError);

HTTP-Anforderung, wobei $CREDENTIAL_PLACEHOLDER$ es sich um einen Platzhalter für das Kennwortschlüssel-Wert-Paar handelt:

POST https://localhost:44305/api/Account/Register HTTP/1.1
Host: localhost:44305
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:32.0) Gecko/20100101 Firefox/32.0
Accept: */*
Content-Type: application/json; charset=utf-8
X-Requested-With: XMLHttpRequest
Referer: https://localhost:44305/
Content-Length: 84

{"Email":"alice@example.com",$CREDENTIAL_PLACEHOLDER1$,$CREDENTIAL_PLACEHOLDER2$"}

HTTP-Antwort:

HTTP/1.1 200 OK
Server: Microsoft-IIS/8.0
Date: Wed, 01 Oct 2014 00:57:58 GMT
Content-Length: 0

Diese Anforderung wird von der AccountController Klasse behandelt. Intern wird ASP.NET Identity verwendet, AccountController um die Mitgliedschaftsdatenbank zu verwalten.

Wenn Sie die App lokal aus Visual Studio ausführen, werden Benutzerkonten in LocalDB in der AspNetUsers-Tabelle gespeichert. Wenn Sie die Tabellen in Visual Studio anzeigen möchten, klicken Sie auf das Menü "Ansicht ", wählen Sie "Server-Explorer" aus, und erweitern Sie dann "Datenverbindungen".

Abbildung von Datenverbindungen

Abrufen eines Zugriffstokens

Bisher haben wir keine OAuth ausgeführt, aber jetzt sehen wir den OAuth-Autorisierungsserver in Aktion, wenn wir ein Zugriffstoken anfordern. Geben Sie im Anmeldebereich der Beispiel-App die E-Mail und das Kennwort ein, und klicken Sie auf "Anmelden".

Abbildung des Anmeldeabschnitts

Die Schaltfläche "Anmelden " sendet eine Anforderung an den Tokenendpunkt. Der Textkörper der Anforderung enthält die folgenden formular-url-codierten Daten:

  • grant_type: "Kennwort"
  • Benutzername: <die E-Mail des Benutzers>
  • Kennwort: <Kennwort>

Hier sehen Sie den JavaScript-Code, der die AJAX-Anforderung sendet:

var loginData = {
    grant_type: 'password',
    username: self.loginEmail(),
    password: self.loginPassword()
};

$.ajax({
    type: 'POST',
    url: '/Token',
    data: loginData
}).done(function (data) {
    self.user(data.userName);
    // Cache the access token in session storage.
    sessionStorage.setItem(tokenKey, data.access_token);
}).fail(showError);

Wenn die Anforderung erfolgreich ist, gibt der Autorisierungsserver ein Zugriffstoken im Antworttext zurück. Beachten Sie, dass wir das Token im Sitzungsspeicher speichern, um es später beim Senden von Anforderungen an die API zu verwenden. Im Gegensatz zu einigen Authentifizierungsformen (z. B. cookiebasierter Authentifizierung) schließt der Browser das Zugriffstoken nicht automatisch in nachfolgende Anforderungen ein. Die Anwendung muss dies explizit tun. Das ist eine gute Sache, weil sie CSRF-Sicherheitsrisiken begrenzt.

HTTP-Anforderung:

POST https://localhost:44305/Token HTTP/1.1
Host: localhost:44305
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:32.0) Gecko/20100101 Firefox/32.0
Accept: */*
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Referer: https://localhost:44305/
Content-Length: 68

grant_type=password&username=alice%40example.com&password=Password1!

Sie können sehen, dass die Anforderung die Anmeldeinformationen des Benutzers enthält. Sie müssen HTTPS verwenden, um sicherheit auf Transportebene bereitzustellen.

HTTP-Antwort:

HTTP/1.1 200 OK
Content-Length: 669
Content-Type: application/json;charset=UTF-8
Server: Microsoft-IIS/8.0
Date: Wed, 01 Oct 2014 01:22:36 GMT

{
  "access_token":"imSXTs2OqSrGWzsFQhIXziFCO3rF...",
  "token_type":"bearer",
  "expires_in":1209599,
  "userName":"alice@example.com",
  ".issued":"Wed, 01 Oct 2014 01:22:33 GMT",
  ".expires":"Wed, 15 Oct 2014 01:22:33 GMT"
}

Zur Lesbarkeit habe ich den JSON-Code eingezogen und das Zugriffstoken abgeschnitten, was ziemlich lang ist.

Die access_tokenEigenschaften token_typeund expires_in Eigenschaften werden durch die OAuth2-Spezifikation definiert. Die anderen Eigenschaften (userName, .issuedund .expires) dienen lediglich informationszwecken. Sie finden den Code, der diese zusätzlichen Eigenschaften in der TokenEndpoint Methode hinzufügt, in der Datei "/Providers/ApplicationOAuthProvider.cs".

Senden einer authentifizierten Anforderung

Da wir nun über ein Bearertoken verfügen, können wir eine authentifizierte Anforderung an die API senden. Dazu legen Sie den Autorisierungsheader in der Anforderung fest. Klicken Sie erneut auf die Schaltfläche "API aufrufen", um dies anzuzeigen.

Bild nach dem Klicken auf die Schaltfläche

HTTP-Anforderung:

GET https://localhost:44305/api/values/1 HTTP/1.1
Host: localhost:44305
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:32.0) Gecko/20100101 Firefox/32.0
Accept: */*
Authorization: Bearer imSXTs2OqSrGWzsFQhIXziFCO3rF...
X-Requested-With: XMLHttpRequest

HTTP-Antwort:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/8.0
Date: Wed, 01 Oct 2014 01:41:29 GMT
Content-Length: 27

"Hello, alice@example.com."

Abmelden

Da der Browser die Anmeldeinformationen oder das Zugriffstoken nicht zwischenspeichert, ist die Abmeldung einfach eine Frage des "Vergessens" des Tokens, indem es aus dem Sitzungsspeicher entfernt wird:

self.logout = function () {
    sessionStorage.removeItem(tokenKey)
}

Grundlegendes zur Projektvorlage für einzelne Konten

Wenn Sie "Einzelne Konten" in der Projektvorlage ASP.NET Webanwendung auswählen, enthält das Projekt Folgendes:

  • Ein OAuth2-Autorisierungsserver.
  • Ein Web-API-Endpunkt zum Verwalten von Benutzerkonten
  • Ein EF-Modell zum Speichern von Benutzerkonten.

Dies sind die wichtigsten Anwendungsklassen, die diese Features implementieren:

  • AccountController. Stellt einen Web-API-Endpunkt zum Verwalten von Benutzerkonten bereit. Die Register Aktion ist die einzige Aktion, die wir in diesem Lernprogramm verwendet haben. Andere Methoden für die Kennwortzurücksetzung von Klassen unterstützen, Anmeldungen für soziale Netzwerke und andere Funktionen.
  • ApplicationUser, definiert in /Models/IdentityModels.cs. Diese Klasse ist das EF-Modell für Benutzerkonten in der Mitgliedschaftsdatenbank.
  • ApplicationUserManager, definiert in /App_Start/IdentityConfig.cs Diese Klasse wird von UserManager abgeleitet und führt Vorgänge für Benutzerkonten aus, z. B. das Erstellen eines neuen Benutzers, das Überprüfen von Kennwörtern usw. und speichert automatisch Änderungen an der Datenbank.
  • ApplicationOAuthProvider. Dieses Objekt wird in die OWIN-Middleware eingebunden und verarbeitet Ereignisse, die von der Middleware ausgelöst werden. Sie wird von OAuthAuthorizationServerProvider abgeleitet.

Abbildung der Hauptanwendungsklassen

Konfigurieren des Autorisierungsservers

In StartupAuth.cs konfiguriert der folgende Code den OAuth2-Autorisierungsserver.

PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
    TokenEndpointPath = new PathString("/Token"),
    Provider = new ApplicationOAuthProvider(PublicClientId),
    AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
    AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
    // Note: Remove the following line before you deploy to production:
    AllowInsecureHttp = true
};

// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);

Die TokenEndpointPath Eigenschaft ist der URL-Pfad zum Autorisierungsserverendpunkt. Dies ist die URL, die von der App zum Abrufen der Bearertoken verwendet wird.

Die Provider Eigenschaft gibt einen Anbieter an, der in die OWIN-Middleware eingebunden wird, und Ereignisse verarbeitet, die von der Middleware ausgelöst werden.

Dies ist der grundlegende Fluss, wenn die App ein Token abrufen möchte:

  1. Um ein Zugriffstoken abzurufen, sendet die App eine Anforderung an ~/Token.
  2. Die OAuth-Middleware ruft den Anbieter auf GrantResourceOwnerCredentials .
  3. Der Anbieter ruft die ApplicationUserManager Anmeldeinformationen auf, um die Anmeldeinformationen zu überprüfen und eine Anspruchsidentität zu erstellen.
  4. Wenn dies erfolgreich ist, erstellt der Anbieter ein Authentifizierungsticket, das zum Generieren des Tokens verwendet wird.

Diagramm des Autorisierungsflusses

Die OAuth-Middleware kennt nichts über die Benutzerkonten. Der Anbieter kommuniziert zwischen der Middleware und ASP.NET Identity. Weitere Informationen zur Implementierung des Autorisierungsservers finden Sie unter OWIN OAuth 2.0 Authorization Server.

Konfigurieren der Web-API für die Verwendung von Bearertoken

In der WebApiConfig.Register Methode richtet der folgende Code die Authentifizierung für die Web-API-Pipeline ein:

config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));

Die HostAuthenticationFilter-Klasse ermöglicht die Authentifizierung mithilfe von Bearertoken.

Die SuppressDefaultHostAuthentication-Methode weist die Web-API an, alle Authentifizierungen zu ignorieren, die vor erreichen, bevor die Anforderung die Web-API-Pipeline erreicht, entweder von IIS oder von OWIN Middleware. Auf diese Weise können Sie die Authentifizierung der Web-API ausschließlich auf Bearertoken einschränken.

Hinweis

Insbesondere kann der MVC-Teil Ihrer App die Formularauthentifizierung verwenden, die Anmeldeinformationen in einem Cookie speichert. Die cookiebasierte Authentifizierung erfordert die Verwendung von Anti-Fälschungstoken, um CSRF-Angriffe zu verhindern. Das ist ein Problem für Web-APIs, da es keine bequeme Möglichkeit für die Web-API gibt, das Anti-Fälschungstoken an den Client zu senden. (Weitere Informationen zu diesem Problem finden Sie unter Verhindern von CSRF-Angriffen in der Web-API.) Das Aufrufen von SuppressDefaultHostAuthentication stellt sicher, dass die Web-API nicht anfällig für CSRF-Angriffe von Anmeldeinformationen ist, die in Cookies gespeichert sind.

Wenn der Client eine geschützte Ressource anfordert, geschieht dies in der Web-API-Pipeline:

  1. Der HostAuthentication-Filter ruft die OAuth-Middleware auf, um das Token zu überprüfen.
  2. Die Middleware konvertiert das Token in eine Anspruchsidentität.
  3. An diesem Punkt wird die Anforderung authentifiziert , aber nicht autorisiert.
  4. Der Autorisierungsfilter untersucht die Anspruchsidentität. Wenn die Ansprüche den Benutzer für diese Ressource autorisieren, ist die Anforderung autorisiert. Standardmäßig autorisiert das [Authorize] -Attribut jede Anforderung, die authentifiziert wird. Sie können jedoch nach Rolle oder von anderen Ansprüchen autorisieren. Weitere Informationen finden Sie unter Authentifizierung und Autorisierung in der Web-API.
  5. Wenn die vorherigen Schritte erfolgreich sind, gibt der Controller die geschützte Ressource zurück. Andernfalls erhält der Client einen Fehler von 401 (Nicht autorisiert).

Diagramm, in dem der Client eine geschützte Ressource anfordert

Weitere Ressourcen