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
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.
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 .
- Der Benutzer gibt einen Namen und ein Kennwort in den Client ein.
- Der Client sendet diese Anmeldeinformationen an den Autorisierungsserver.
- Der Autorisierungsserver authentifiziert die Anmeldeinformationen und gibt ein Zugriffstoken zurück.
- Um auf eine geschützte Ressource zuzugreifen, enthält der Client das Zugriffstoken im Autorisierungsheader der HTTP-Anforderung.
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.
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.
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.
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".
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".
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_token
Eigenschaften token_type
und expires_in
Eigenschaften werden durch die OAuth2-Spezifikation definiert. Die anderen Eigenschaften (userName
, .issued
und .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.
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. DieRegister
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.
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:
- Um ein Zugriffstoken abzurufen, sendet die App eine Anforderung an ~/Token.
- Die OAuth-Middleware ruft den Anbieter auf
GrantResourceOwnerCredentials
. - Der Anbieter ruft die
ApplicationUserManager
Anmeldeinformationen auf, um die Anmeldeinformationen zu überprüfen und eine Anspruchsidentität zu erstellen. - Wenn dies erfolgreich ist, erstellt der Anbieter ein Authentifizierungsticket, das zum Generieren des Tokens verwendet wird.
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:
- Der HostAuthentication-Filter ruft die OAuth-Middleware auf, um das Token zu überprüfen.
- Die Middleware konvertiert das Token in eine Anspruchsidentität.
- An diesem Punkt wird die Anforderung authentifiziert , aber nicht autorisiert.
- 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.
- 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).
Weitere Ressourcen
- ASP.NET Identity
- Grundlegendes zu Sicherheitsfeatures in der SPA-Vorlage für VS2013 RC. MSDN-Blogbeitrag von Hongye Sun.
- Die Web-API-Vorlage für einzelne Konten –Teil 2: Lokale Konten wird getrennt. Blogbeitrag von Dominick Baier.
- Hostauthentifizierung und Web-API mit OWIN. Eine gute Erklärung von
SuppressDefaultHostAuthentication
undHostAuthenticationFilter
von Brock Allen. - Anpassen von Profilinformationen in ASP.NET Identität in VS 2013-Vorlagen. MSDN-Blogbeitrag von Pranav Rastogi.
- Die Verwaltung der Lebensdauer pro Anforderung für die UserManager-Klasse in ASP.NET Identity. MSDN-Blogbeitrag von Suhas Joshi, mit einer guten Erklärung der
UserManager
Klasse.