Dela via


Skydda ett webb-API med enskilda konton och lokal inloggning i ASP.NET Web API 2.2

av Mike Wasson

Ladda ned exempelappen

Det här avsnittet visar hur du skyddar ett webb-API med OAuth2 för att autentisera mot en medlemskapsdatabas.

Programvaruversioner som används i handledningen

I Visual Studio 2013 ger webb-API-projektmallen tre alternativ för autentisering:

  • Enskilda konton. Appen använder en medlemskapsdatabas.
  • Organisationskonton. Användare loggar in med sina autentiseringsuppgifter för Azure Active Directory, Office 365 eller lokala Active Directory.
  • Windows-autentisering. Det här alternativet är avsett för intranätsprogram och använder IIS-modulen för Windows-autentisering.

Enskilda konton tillhandahåller två sätt för en användare att logga in:

  • Lokal inloggning. Användaren registrerar sig på webbplatsen och anger ett användarnamn och lösenord. Appen lagrar lösenordshash i medlemskapsdatabasen. När användaren loggar in verifierar ASP.NET identitetssystemet lösenordet.
  • Social inloggning. Användaren loggar in med en extern tjänst, till exempel Facebook, Microsoft eller Google. Appen skapar fortfarande en post för användaren i medlemskapsdatabasen, men lagrar inga autentiseringsuppgifter. Användaren autentiserar genom att logga in på den externa tjänsten.

Den här artikeln tittar på scenariot för lokal inloggning. Webb-API:et använder OAuth2 för både lokal och social inloggning för att autentisera begäranden. Autentiseringsflödena skiljer sig dock åt för lokal och social inloggning.

I den här artikeln ska jag demonstrera en enkel app som låter användaren logga in och skicka autentiserade AJAX-anrop till ett webb-API. Du kan ladda ned exempelkoden här. Readme beskriver hur du skapar exemplet från grunden i Visual Studio.

Bild av exempelformulär

Exempelappen använder Knockout.js för databindning och jQuery för att skicka AJAX-begäranden. Jag kommer att fokusera på AJAX-samtal, så du behöver inte veta Knockout.js för den här artikeln.

Längs vägen ska jag beskriva:

  • Vad appen gör på klientsidan.
  • Vad som händer på servern.
  • HTTP-trafiken i mitten.

Först måste vi definiera viss OAuth2-terminologi.

  • Resurs. Vissa data som kan skyddas.
  • Resurs-server. Servern som är värd för resursen.
  • Resursägare. Entiteten som kan ge behörighet att komma åt en resurs. (Vanligtvis användaren.)
  • Client: Den app som vill ha åtkomst till resursen. I den här artikeln är klienten en webbläsare.
  • Åtkomsttoken. En token som ger åtkomst till en resurs.
  • Bearer-token. En viss typ av åtkomsttoken med den egenskap som vem som helst kan använda token. Med andra ord behöver en klient inte någon kryptografisk nyckel eller annan hemlighet för att använda en ägartoken. Därför bör ägartoken endast användas via en HTTPS och ha relativt korta förfallotider.
  • Autentiseringsserver. En server som ger ut åtkomsttoken.

Ett program kan fungera som både auktoriseringsserver och resursserver. Webb-API-projektmallen följer det här mönstret.

Autentiseringsflöde för lokal inloggning

För lokal inloggning använder webb-API resursägarens lösenordsflöde definierat i OAuth2.

  1. Användaren anger ett namn och lösenord i klienten.
  2. Klienten skickar dessa autentiseringsuppgifter till auktoriseringsservern.
  3. Auktoriseringsservern autentiserar autentiseringsuppgifterna och returnerar en åtkomsttoken.
  4. För att få åtkomst till en skyddad resurs innehåller klienten åtkomsttoken i auktoriseringshuvudet för HTTP-begäran.

diagram över autentiseringsflödet för lokal inloggning

När du väljer enskilda konton i webb-API-projektmallen innehåller projektet en auktoriseringsserver som validerar användarautentiseringsuppgifter och utfärdar token. Följande diagram visar samma flöde för autentiseringsuppgifter när det gäller webb-API-komponenter.

diagram när enskilda konton väljs i ett Web API

I det här scenariot fungerar webb-API-styrenheter som resursservrar. Ett autentiseringsfilter validerar åtkomsttoken och attributet [Auktorisera] används för att skydda en resurs. När en kontrollant eller åtgärd har attributet [Auktorisera] måste alla begäranden till kontrollanten eller åtgärden autentiseras. I annat fall nekas auktorisering och webb-API returnerar ett 401-fel (obehörigt).

Auktoriseringsservern och autentiseringsfiltret anropar båda till en OWIN-mellanprogram komponent som hanterar information om OAuth2. Jag ska beskriva designen mer detaljerat senare i den här instruktionen.

Skicka en obehörig begäran

Kom igång genom att köra appen och klicka på knappen Anropa API. När begäran är klar bör du se ett felmeddelande i rutan Resultat. Det beror på att begäran inte innehåller en åtkomsttoken, så begäran är obehörig.

Bild av resultatsfelmeddelande

Knappen Anropa API skickar en AJAX-begäran till ~/api/values, som anropar en webb-API-kontrollantåtgärd. Här är avsnittet i JavaScript-kod som skickar AJAX-begäran. I exempelappen finns all JavaScript-appkod i filen 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);

Innan användaren loggar in finns det ingen bärartoken och därför ingen Authorization-header i begäran. Detta gör att begäran returnerar ett 401-fel.

Här är HTTP-begäran. (Jag använde Fiddler för att fånga HTTP-trafiken.)

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-svar:

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."}

Observera att svaret innehåller en Www-Authenticate-header med utmaningen inställd på Bearer. Det indikerar att servern förväntar sig en bearertoken.

Registrera en användare

I avsnittet Registrera i appen anger du ett e-postmeddelande och lösenord och klickar på knappen Registrera.

Du behöver inte använda en giltig e-postadress för det här exemplet, men en riktig app bekräftar adressen. (Se Skapa en säker ASP.NET MVC 5-webbapp med inloggning, e-postbekräftelse och lösenordsåterställning.) För lösenordet använder du något som "Password1!", med versaler, gemener, siffror och icke-alfanumeriska tecken. För att hålla appen enkel utelämnade jag validering på klientsidan, så om det finns ett problem med lösenordsformatet får du ett fel på 400 (felaktig begäran).

Bild av registreringssektion för användare

Knappen Registrera skickar en POST-begäran till ~/api/Account/Register/. Begärandetexten är ett JSON-objekt som innehåller namnet och lösenordet. Här är JavaScript-koden som skickar begäran:

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-begäran, där $CREDENTIAL_PLACEHOLDER$ är platshållare för nyckel/värde-paret för lösenord:

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-svar:

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

Den här begäran hanteras av klassen AccountController. Internt använder AccountController ASP.NET identitet för att hantera medlemskapsdatabasen.

Om du kör appen lokalt från Visual Studio lagras användarkonton i LocalDB i tabellen AspNetUsers. Om du vill visa tabellerna i Visual Studio klickar du på menyn Visa, väljer Server Exploreroch expanderar sedan dataanslutningar.

bild av dataanslutningar

Hämta en åtkomsttoken

Hittills har vi inte gjort någon OAuth, men nu visas OAuth-auktoriseringsservern i praktiken när vi begär en åtkomsttoken. I området Logga in i exempelappen anger du e-postmeddelandet och lösenordet och klickar på Logga in.

Bild av inloggningsavsnitt

Knappen Logga in skickar en begäran till tokenslutpunkten. Innehållet i begäran innehåller följande data kodade som formulär-url:

  • grant_type: "lösenord"
  • användarnamn: <användarens e-mail>
  • lösenord: <lösenord>

Här är JavaScript-koden som skickar AJAX-begäran:

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);

Om begäran lyckas returnerar auktoriseringsservern en åtkomsttoken i svarstexten. Observera att vi lagrar token i sessionslagring så att den kan användas senare när begäranden skickas till API:et. Till skillnad från vissa former av autentisering (till exempel cookiebaserad autentisering) kommer webbläsaren inte automatiskt att inkludera åtkomsttoken i efterföljande begäranden. Programmet måste göra det explicit. Det är bra eftersom det begränsar CSRF-sårbarheter.

HTTP-begäran:

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!

Du kan se att begäran innehåller användarens autentiseringsuppgifter. Du måste använda HTTPS för att tillhandahålla säkerhet på transportnivå.

HTTP-svar:

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"
}

För läsbarhet drog jag in JSON och trunkerade åtkomsttoken, som är ganska lång.

Egenskaperna access_token, token_typeoch expires_in definieras av OAuth2-specifikationen. De andra egenskaperna (userName, .issuedoch .expires) är bara i informationssyfte. Du hittar koden som lägger till dessa ytterligare egenskaper i metoden TokenEndpoint i filen /Providers/ApplicationOAuthProvider.cs.

Skicka en autentiserad begäran

Nu när vi har en ägartoken kan vi göra en autentiserad begäran till API:et. Detta görs genom att ange auktoriseringshuvudet i begäran. Klicka på knappen Anropa API på nytt för att se detta.

Bild efter anropet En P I-knapp har klickats

HTTP-begäran:

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-svar:

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."

Logga ut

Eftersom webbläsaren inte cachelagrade autentiseringsuppgifterna eller åtkomsttoken handlar det bara om att "glömma" token genom att ta bort den från sessionslagringen:

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

Introduktion till projektmallen för individuella konton

När du väljer Enskilda Konton i ASP.NET webbprogramsprojektmallen innehåller projektet följande:

  • En OAuth2-auktoriseringsserver.
  • En webb-API-slutpunkt för att hantera användarkonton
  • En EF-modell för lagring av användarkonton.

Här är de viktigaste programklasserna som implementerar dessa funktioner:

  • AccountController. Tillhandahåller en webb-API-slutpunkt för hantering av användarkonton. Den Register åtgärden är den enda som vi använde i den här handledningen. Andra metoder för klassen stöder lösenordsåterställning, sociala inloggningar och andra funktioner.
  • ApplicationUser, definierad i /Models/IdentityModels.cs. Den här klassen är EF-modellen för användarkonton i medlemskapsdatabasen.
  • ApplicationUserManager, som definieras i /App_Start/IdentityConfig.cs Den här klassen härleds från UserManager och utför åtgärder på användarkonton, till exempel att skapa en ny användare, verifiera lösenord och så vidare och automatiskt bevarar ändringar i databasen.
  • ApplicationOAuthProvider. Det här objektet ansluter till OWIN-mellanprogrammet och bearbetar händelser som genereras av mellanprogrammet. Den härleds från OAuthAuthorizationServerProvider.

Bild av huvudprogramklasser

Konfigurera auktoriseringsservern

I StartupAuth.cs konfigurerar följande kod OAuth2-auktoriseringsservern.

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);

Egenskapen TokenEndpointPath är URL-sökvägen till auktoriseringsserverns slutpunkt. Det är den URL-adressen som appen använder för att hämta bärare-token.

Egenskapen Provider anger en provider som ansluter till OWIN-mellanprogrammet och bearbetar händelser som genereras av mellanprogrammet.

Här är det grundläggande flödet när appen vill hämta en token:

  1. För att få en åtkomsttoken skickar appen en begäran till ~/Token.
  2. OAuth-mellanprogrammet anropar GrantResourceOwnerCredentials på providern.
  3. Providern anropar ApplicationUserManager för att verifiera autentiseringsuppgifterna och skapa en anspråksidentitet.
  4. Om det lyckas skapar providern en autentiseringsbiljett som används för att generera token.

Diagram över auktoriseringsflöde

OAuth-mellanprogrammet vet ingenting om användarkontona. Providern kommunicerar mellan mellanprogram och ASP.NET identitet. Mer information om hur du implementerar auktoriseringsservern finns i OWIN OAuth 2.0 Authorization Server.

Konfigurering av webb-API för att använda bärare-token

I metoden WebApiConfig.Register konfigurerar följande kod autentisering för webb-API-pipelinen:

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

Klassen HostAuthenticationFilter aktiverar autentisering med hjälp av ägartoken.

Metoden SuppressDefaultHostAuthentication anger att webb-API:et ska ignorera alla autentiseringar som inträffar innan begäran når webb-API-pipelinen, antingen av IIS eller av OWIN-mellanprogram. På så sätt kan vi begränsa webb-API:et till att endast autentisera med hjälp av ägartoken.

Not

I synnerhet kan MVC-delen av din app använda formulärautentisering, som lagrar autentiseringsuppgifter i en cookie. Cookiebaserad autentisering kräver användning av antiförfalskningstoken för att förhindra CSRF-attacker. Det är ett problem för webb-API:er eftersom det inte finns något praktiskt sätt för webb-API:et att skicka antiförfalskningstoken till klienten. (Mer bakgrund om det här problemet finns i Förhindra CSRF-attacker i webb-API.) Anropa SuppressDefaultHostAuthentication ser till att webb-API:et inte är sårbart för CSRF-attacker från autentiseringsuppgifter som lagras i cookies.

När klienten begär en skyddad resurs händer det här i webb-API-pipelinen:

  1. Filtret HostAuthentication anropar OAuth-mellanprogrammet för att verifiera token.
  2. Mellanprogrammet konverterar token till en anspråksidentitet.
  3. I det här läget är begäran autentiserad men inte auktoriserad.
  4. Auktoriseringsfiltret undersöker anspråksidentiteten. Om anspråken auktoriserar användaren för den resursen godkänns begäran. Som standard auktoriserar attributet [Auktorisera] alla begäranden som autentiseras. Du kan dock auktorisera efter roll eller andra anspråk. Mer information finns i autentisering och auktorisering i webb-API.
  5. Om föregående steg lyckas returnerar kontrollanten den skyddade resursen. I annat fall får klienten ett 401-fel (obehörigt).

diagram över när klienten begär en skyddad resurs

Ytterligare resurser