Skydda ett webb-API för backend för program med flera program

Exempelkod

Tailspin Surveys-programmet använder ett webb-API för backend för att hantera CRUD-åtgärder på undersökningar. När en användare till exempel klickar på "Mina undersökningar" skickar webbappen en HTTP-begäran till webb-API:et:

GET /users/{userId}/surveys

Webb-API:et returnerar ett JSON-objekt:

{
  "Published":[],
  "Own":[
    {"Id":1,"Title":"Survey 1"},
    {"Id":3,"Title":"Survey 3"},
    ],
  "Contribute": [{"Id":8,"Title":"My survey"}]
}

Webb-API:et tillåter inte anonyma begäranden, så webbappen måste autentisera sig själv med hjälp av OAuth 2-bearer-tokens.

Anteckning

Det här är ett server-till-server-scenario. Programmet gör inga AJAX-anrop till API:et från webbläsarklienten.

Det finns två huvudsakliga metoder som du kan använda:

  • Delegerad användaridentitet. Webbprogrammet autentiseras med användarens identitet.
  • Programidentitet. Webbprogrammet autentiseras med sitt klient-ID med hjälp av flödet för autentiseringsuppgifter för OAuth 2-klienten.

Tailspin-programmet implementerar delegerad användaridentitet. Här är de viktigaste skillnaderna:

Delegerad användaridentitet:

  • Den bearer-token som skickas till webb-API:et innehåller användaridentiteten.
  • Webb-API:et fattar auktoriseringsbeslut baserat på användaridentiteten.
  • Webbprogrammet måste hantera 403-fel (förbjudet) från webb-API:et om användaren inte har behörighet att utföra en åtgärd.
  • Normalt fattar webbappen fortfarande vissa auktoriseringsbeslut som påverkar användargränssnittet, till exempel att visa eller dölja gränssnittselement).
  • Webb-API:et kan potentiellt användas av ej betrodda klienter, till exempel ett JavaScript-program eller ett inbyggt klientprogram.

Programidentitet:

  • Webb-API:et hämtar inte information om användaren.
  • Webb-API:et kan inte utföra någon auktorisering baserat på användaridentiteten. Alla auktoriseringsbeslut fattas av webbprogrammet.
  • Webb-API:et kan inte användas av en obetrott klient (JavaScript eller ett inbyggt klientprogram).
  • Den här metoden kan vara lite enklare att implementera eftersom det inte finns någon auktoriseringslogik i webb-API:et.

I båda endera metoden måste webbappen hämta en åtkomsttoken, vilket är den autentiseringsbehörighet som krävs för att anropa webb-API:et.

  • För delegerad användaridentitet måste token komma från en identitetsprovider (IDP), till exempel Azure Active Directory, som kan utfärda en token för användarens räkning.
  • För klientautentiseringsuppgifter kan ett program hämta token från IDP eller vara värd för en egen tokenserver. (Men skriv inte en tokenserver från grunden. Använd ett vältestat ramverk som IdentityServer4.) Om du autentiserar med Azure AD rekommenderar vi starkt att du hämtar åtkomsttoken från Azure AD, även med flödet för klientautentiska autentiseringsuppgifter.

Resten av den här artikeln förutsätter att programmet autentiserar med Azure AD.

Hämta åtkomsttoken

Ett diagram som visar webbappen som begär en åtkomsttoken från Azure AD och skickar token till webb-API:et.

Registrera webb-API:et i Azure AD

För att Azure AD ska kunna utfärda en bearer-token för webb-API:et måste du konfigurera vissa saker i Azure AD.

  1. Registrera webb-API:et i Azure AD.

  2. Lägg till klient-ID:t för webbappen i webb-API-programmanifestet i knownClientApplications egenskapen . Se GitHub readme för mer information.

  3. Ge webbappen behörighet att anropa webb-API:et. I Azure Portal kan du ange två typer av behörigheter: "Programbehörigheter" för programidentitet (flödet för klientbehörigheter) eller "Delegerade behörigheter" för delegerad användaridentitet.

    Delegerade behörigheter

    En skärmbild av Azure Portal som visar programbehörigheter och delegerade behörigheter.

Hämta en åtkomsttoken

Innan webb-API:et anropas får webbappen en åtkomsttoken från Azure AD. I ett .NET-program använder du Microsoft Authentication Library för .NET (MSAL.NET). Lägg .EnableTokenAcquisitionToCallDownstreamApi() till i Startup.cs för programmet.

När du har skaffat en token cachelagrar MSAL den. Därför måste du också välja en tokencacheimplementering som ingår i MSAL. I det här exemplet används distribuerad cache. Mer information finns i Cachelagring av token.

Anropa webb-API:et med hjälp av åtkomsttoken

När du har token anropar du ett nedströms webb-API. Den här processen beskrivs i Anropa ett nedströms webb-API med hjälpklassen.

Autentisering i webb-API:et

Webb-API:et måste autentisera bearer-token. I ASP.NET Core kan du använda paketet Microsoft.AspNet.Authentication.JwtBearer. Det här paketet innehåller mellanprogram som gör att programmet kan ta emot OpenID Anslut bearer-tokens.

Registrera mellanprogram i Startup webb-API-klassen.

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(jtwOptions =>
        {
            jtwOptions.Events = new SurveysJwtBearerEvents(loggerFactory.CreateLogger<SurveysJwtBearerEvents>()); 
        },
        msIdentityOptions => {
            Configuration.GetSection("AzureAd").Bind(msIdentityOptions);
        });

Händelser är en klass som härleds från JwtBearerEvents.

Utfärdarvalidering

Verifiera tokenutfärdaren i händelsen JwtBearerEvents.TokenValidated. Utfärdaren skickas i "iss"-anspråket.

I programmet Surveys hanterar webb-API:et inte registrering av klientorganisationen. Därför kontrollerar den bara om utfärdaren redan finns i programdatabasen. Om inte ylar det ett undantag, vilket gör att autentiseringen misslyckas.

public override async Task TokenValidated(TokenValidatedContext context)
{
    var principal = context.Ticket.Principal;
    var tenantManager = context.HttpContext.RequestServices.GetService<TenantManager>();
    var userManager = context.HttpContext.RequestServices.GetService<UserManager>();
    var issuerValue = principal.GetIssuerValue();
    var tenant = await tenantManager.FindByIssuerValueAsync(issuerValue);

    if (tenant == null)
    {
        // The caller was not from a trusted issuer. Throw to block the authentication flow.
        throw new SecurityTokenValidationException();
    }

    var identity = principal.Identities.First();

    // Add new claim for survey_userid
    var registeredUser = await userManager.FindByObjectIdentifier(principal.GetObjectIdentifierValue());
    identity.AddClaim(new Claim(SurveyClaimTypes.SurveyUserIdClaimType, registeredUser.Id.ToString()));
    identity.AddClaim(new Claim(SurveyClaimTypes.SurveyTenantIdClaimType, registeredUser.TenantId.ToString()));

    // Add new claim for Email
    var email = principal.FindFirst(ClaimTypes.Upn)?.Value;
    if (!string.IsNullOrWhiteSpace(email))
    {
        identity.AddClaim(new Claim(ClaimTypes.Email, email));
    }
}

Som det här exemplet visar kan du också använda händelsen TokenValidated för att ändra anspråken. Kom ihåg att anspråken kommer direkt från Azure AD. Om webbappen ändrar de anspråk som den får visas inte ändringarna i den bearer-token som webb-API:et tar emot. Mer information finns i Anspråksomvandlar.

Auktorisering

En allmän diskussion om auktorisering finns i Rollbaserad och resursbaserad auktorisering.

JwtBearer-mellanprogram hanterar auktoriseringssvaren. Om du till exempel vill begränsa en kontrollantåtgärd till autentiserade användare använder du attributet [Authorize] och anger JwtBearerDefaults.AuthenticationScheme som autentiseringsschema:

[Authorize(ActiveAuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]

Detta returnerar en 401-statuskod om användaren inte är autentiserad.

Om du vill begränsa en kontrollantåtgärd efter auktoriseringsprincip anger du principnamnet i attributet [Auktorisera] :

[Authorize(Policy = PolicyNames.RequireSurveyCreator)]

Detta returnerar en 401-statuskod om användaren inte är autentiserad och 403 om användaren är autentiserad men inte auktoriserad. Registrera principen vid start:

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthorization(options =>
    {
        options.AddPolicy(PolicyNames.RequireSurveyCreator,
            policy =>
            {
                policy.AddRequirements(new SurveyCreatorRequirement());
                policy.RequireAuthenticatedUser(); // Adds DenyAnonymousAuthorizationRequirement
                policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);
            });
        options.AddPolicy(PolicyNames.RequireSurveyAdmin,
            policy =>
            {
                policy.AddRequirements(new SurveyAdminRequirement());
                policy.RequireAuthenticatedUser(); // Adds DenyAnonymousAuthorizationRequirement
                policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);
            });
    });

    // ...
}

Skydda programhemligheter

Det är vanligt att ha programinställningar som är känsliga och måste skyddas, till exempel:

  • Databasanslutningssträngar
  • Lösenord
  • Krypteringsnycklar

Som en säkerhetsmetod bör du aldrig lagra dessa hemligheter i källkontroll. Det är för enkelt för dem att läcka – även om lagringsplatsen för källkoden är privat. Och det handlar inte bara om att hålla hemligheter från allmänheten. I större projekt kanske du vill begränsa vilka utvecklare och operatörer som kan komma åt produktionshemligheterna. (Inställningar för test- eller utvecklingsmiljöer skiljer sig åt.)

Ett säkrare alternativ är att lagra dessa hemligheter i Azure Key Vault. Key Vault är en molnbaserad tjänst för hantering av kryptografiska nycklar och andra hemligheter. Den här artikeln visar hur du använder Key Vault för att lagra konfigurationsinställningar för din app.

I programmet Tailspin Surveys är följande inställningar hemliga:

  • Databasanslutningssträngen.
  • Redis-anslutningssträngen.
  • Klienthemligheten för webbappen.

Programmet Surveys läser in konfigurationsinställningar från följande platser:

  • Filen appsettings.json
  • Arkivet med användarhemligheter (endast utvecklingsmiljön, för testning)
  • Värdmiljön (appinställningar i Azure-webbappar)
  • Key Vault (när det är aktiverat)

Var och en av dessa åsidosätter den tidigare, så alla inställningar som lagras Key Vault har företräde.

Anteckning

Som standard är Key Vault-konfigurationsprovidern inaktiverad. Det behövs inte för att köra programmet lokalt. Du aktiverar den i en produktionsdistribution.

Vid start läser programmet inställningar från varje registrerad konfigurationsprovider och använder dem för att fylla i ett starkt typat alternativobjekt. Mer information finns i Använda alternativ och konfigurationsobjekt.

Nästa