Zaimplementuj autoryzację w internetowych interfejsach API przy użyciu Microsoft. Identity.Web

W tym artykule zaimplementujesz autoryzację w ASP.NET Core webowych interfejsach API przy użyciu Microsoft.Identity.Web. Sprawdzisz zakresy (uprawnienia delegowane) i uprawnienia aplikacji (uprawnienia aplikacji), aby kontrolować dostęp do chronionych zasobów. W przykładach użyto Microsoft Entra ID jako dostawcy tożsamości.

Omówienie pojęć związanych z autoryzacją

W tej sekcji opisano kluczowe różnice między uwierzytelnianiem a autoryzacją oraz co Microsoft.Identity.Web weryfikuje w tokenach dostępu.

Uwierzytelnianie a autoryzacja

Pojęcie Purpose Wynik
Uwierzytelnianie Weryfikowanie tożsamości 401 Brak autoryzacji w przypadku niepowodzenia
Autoryzacja Weryfikowanie uprawnień 403 Zabronione, jeśli niewystarczające

Co zostanie zweryfikowane

Gdy internetowy interfejs API odbiera token dostępu, Microsoft. Identity.Web weryfikuje:

  1. Podpis tokenu — czy pochodzi z zaufanego urzędu?
  2. Docelowy odbiorca tokenu — czy jest przeznaczony dla tego interfejsu API?
  3. Wygaśnięcie tokenu — czy nadal jest ono prawidłowe?
  4. Zakresy/role — czy aplikacja kliencka i temat (użytkownik) mają odpowiednie uprawnienia?

Ten przewodnik koncentruje się na 4 — weryfikowaniu zakresów i uprawnień aplikacji.

Zakresy (uprawnienia delegowane)

Zakresy mają zastosowanie, gdy użytkownik deleguje uprawnienia do aplikacji do działania w ich imieniu (na przykład internetowy interfejs API wywoływany w imieniu zalogowanego użytkownika).

Szczegół Wartość
Żądanie tokenu scp lub scope (aplikacja kliencka); roles (użytkownik)
Przykładowe wartości "access_as_user", "User.Read", "Files.ReadWrite"

Uprawnienia aplikacji

Uprawnienia aplikacji mają zastosowanie, gdy aplikacja wywołuje internetowy interfejs API jako sam bez kontekstu użytkownika, na przykład demona lub usługi w tle przy użyciu poświadczeń klienta.

Szczegół Wartość
Żądanie tokenu roles
Przykładowe wartości "Mail.Read.All", "User.Read.All"

Weryfikowanie zakresów za pomocą elementu RequiredScope

Atrybut RequiredScope sprawdza, czy token dostępu zawiera co najmniej jeden z określonych zakresów. Użyj tego atrybutu, gdy interfejs API obsługuje tylko żądania delegowane przez użytkownika.

Konfigurowanie weryfikacji zakresu

Wykonaj następujące kroki, aby włączyć walidację zakresu w interfejsie API.

1. Włącz autoryzację w interfejsie API:

Dodaj usługi uwierzytelniania i autoryzacji do potoku aplikacji:

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Web;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));

builder.Services.AddAuthorization(); // Required for authorization

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization(); // Must be after UseAuthentication
app.MapControllers();

app.Run();

2. Ochrona kontrolerów lub akcji:

Zastosuj atrybuty [Authorize] i [RequiredScope] do swojego kontrolera lub poszczególnych działań:

using Microsoft.AspNetCore.Authorization;
using Microsoft.Identity.Web.Resource;

[Authorize]
[RequiredScope("access_as_user")]
public class TodoListController : ControllerBase
{
    [HttpGet]
    public IActionResult GetTodos()
    {
        // Only accessible if token has "access_as_user" scope
        return Ok(new[] { "Todo 1", "Todo 2" });
    }
}

Zastosuj wzorce zakresu

Wybierz wzorzec, który najlepiej pasuje do sposobu zarządzania zakresami w aplikacji.

Wzorzec 1. Zakresy zakodowane na stałe

Użyj tego wzorca, gdy zakresy są stałe i znane w czasie programowania.

[Authorize]
[RequiredScope("access_as_user")]
public class TodoListController : ControllerBase
{
    // All actions require "access_as_user" scope
}

Aby zaakceptować jeden z wielu zakresów, wyświetl je jako parametry:

[Authorize]
[RequiredScope("read", "write", "admin")]
public class TodoListController : ControllerBase
{
    // Token must have "read" OR "write" OR "admin"
}

Wzorzec 2. Zakresy zdefiniowane w konfiguracji

Użyj tego wzorca, gdy zakresy powinny być konfigurowalne dla poszczególnych środowisk. Zdefiniuj zakresy w pliku konfiguracji:

appsettings.json:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "your-tenant-id",
    "ClientId": "your-api-client-id",
    "Scopes": "access_as_user read write"
  }
}

Odwołaj się do klucza konfiguracji w kontrolerze:

[Authorize]
[RequiredScope(RequiredScopesConfigurationKey = "AzureAd:Scopes")]
public class TodoListController : ControllerBase
{
    // Scopes read from configuration
}

Takie podejście umożliwia zmianę zakresów bez konieczności ponownego kompilowania.

Wzorzec 3. Zakresy na poziomie akcji

Użyj tego wzorca, gdy różne akcje wymagają różnych uprawnień. Zastosuj [RequiredScope] do poszczególnych metod akcji:

[Authorize]
public class TodoListController : ControllerBase
{
    [HttpGet]
    [RequiredScope("read")]
    public IActionResult GetTodos()
    {
        return Ok(todos);
    }

    [HttpPost]
    [RequiredScope("write")]
    public IActionResult CreateTodo([FromBody] Todo todo)
    {
        // Only tokens with "write" scope can create
        return CreatedAtAction(nameof(GetTodos), todo);
    }

    [HttpDelete("{id}")]
    [RequiredScope("admin")]
    public IActionResult DeleteTodo(int id)
    {
        // Only tokens with "admin" scope can delete
        return NoContent();
    }
}

Omówienie przepływu weryfikacji

Po nadejściu żądania oprogramowanie pośredniczące przetwarza je w następującej kolejności:

  1. ASP.NET Core oprogramowanie pośredniczące uwierzytelniania weryfikuje token
  2. RequiredScope sprawdzanie atrybutów dla scp lub scope oświadczenia
  3. Jeśli token zawiera co najmniej jeden pasujący zakres, żądanie będzie kontynuowane.
  4. Jeśli nie zostanie znaleziony pasujący zakres, interfejs API zwróci odpowiedź 403 Zabronione.

W poniższym przykładzie przedstawiono typową odpowiedź na błąd:

{
  "error": "insufficient_scope",
  "error_description": "The token does not have the required scope 'access_as_user'."
}

Weryfikowanie uprawnień aplikacji za pomocą elementu RequiredScopeOrAppPermission

Atrybut RequiredScopeOrAppPermissionweryfikuje zakresy (delegowane) lub uprawnienia aplikacji (aplikacja ). Użyj tego atrybutu, gdy interfejs API obsługuje zarówno aplikacje delegowane przez użytkownika, jak i aplikacje demona/usługi z tego samego punktu końcowego.

Jeśli interfejs API obsługuje tylko żądania delegowane przez użytkownika, użyj RequiredScope zamiast tego.

Konfigurowanie walidacji uprawnień zakresu lub aplikacji

Zastosuj atrybut, aby zaakceptować dowolny typ tokenu:

using Microsoft.Identity.Web.Resource;

[Authorize]
[RequiredScopeOrAppPermission(
    AcceptedScope = new[] { "access_as_user" },
    AcceptedAppPermission = new[] { "TodoList.ReadWrite.All" }
)]
public class TodoListController : ControllerBase
{
    [HttpGet]
    public IActionResult GetTodos()
    {
        // Accessible with EITHER:
        // - User-delegated token with "access_as_user" scope, OR
        // - App-only token with "TodoList.ReadWrite.All" app permission
        return Ok(todos);
    }
}

Konfigurowanie uprawnień aplikacji z ustawień

Przechowywanie zakresów i uprawnień aplikacji w konfiguracji w celu ich zmiany bez ponownej kompilacji.

appsettings.json:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "your-tenant-id",
    "ClientId": "your-api-client-id",
    "Scopes": "access_as_user",
    "AppPermissions": "TodoList.ReadWrite.All TodoList.Admin"
  }
}

Odwołaj się do kluczy konfiguracji w kontrolerze:

[Authorize]
[RequiredScopeOrAppPermission(
    RequiredScopesConfigurationKey = "AzureAd:Scopes",
    RequiredAppPermissionsConfigurationKey = "AzureAd:AppPermissions"
)]
public class TodoListController : ControllerBase
{
    // Scopes and app permissions from configuration
}

Porównanie różnic w roszczeniach tokenu

W poniższej tabeli przedstawiono różnice między oświadczeniami delegowanymi przez użytkownika i tokenami tylko dla aplikacji:

Typ tokenu Roszczenie Przykładowa wartość
Delegowane przez użytkownika scp lub scope "access_as_user User.Read"
Tylko aplikacja roles ["TodoList.ReadWrite.All"]

W poniższym przykładzie pokazano token delegowany przez użytkownika:

{
  "aud": "api://your-api-client-id",
  "iss": "https://login.microsoftonline.com/.../v2.0",
  "scp": "access_as_user",
  "sub": "user-object-id",
  ...
}

W poniższym przykładzie przedstawiono token tylko dla aplikacji:

{
  "aud": "api://your-api-client-id",
  "iss": "https://login.microsoftonline.com/.../v2.0",
  "roles": ["TodoList.ReadWrite.All"],
  "sub": "app-object-id",
  ...
}

Tworzenie zasad autoryzacji

W przypadku złożonych scenariuszy autoryzacji należy użyć zasad autoryzacji ASP.NET Core. Zasady umożliwiają scentralizowanie reguł, łączenie wielu wymagań i pisanie logiki autoryzacji z możliwością testowania.

Benefit Opis
Scentralizowana logika Definiowanie reguł autoryzacji raz, ponowne użycie wszędzie
Komponowalne Łączenie wielu wymagań (zakresy i oświadczenia + logika niestandardowa)
Testowalny Łatwiejsze testowanie jednostkowe logiki autoryzacji
Elastyczny Wymagania niestandardowe wykraczające poza walidację zakresu

Wzorzec 1. Definiowanie zasad za pomocą elementu RequireScope

Zdefiniuj nazwane zasady, które wymagają określonych zakresów, a następnie odwołaj się do nich na kontrolerach:

using Microsoft.Identity.Web;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("TodoReadPolicy", policyBuilder =>
    {
        policyBuilder.RequireScope("read", "access_as_user");
    });

    options.AddPolicy("TodoWritePolicy", policyBuilder =>
    {
        policyBuilder.RequireScope("write", "admin");
    });
});

var app = builder.Build();

Zastosuj zasady do akcji kontrolera:

[Authorize]
public class TodoListController : ControllerBase
{
    [HttpGet]
    [Authorize(Policy = "TodoReadPolicy")]
    public IActionResult GetTodos()
    {
        return Ok(todos);
    }

    [HttpPost]
    [Authorize(Policy = "TodoWritePolicy")]
    public IActionResult CreateTodo([FromBody] Todo todo)
    {
        return CreatedAtAction(nameof(GetTodos), todo);
    }
}

Wzorzec 2. Definiowanie zasad za pomocą elementu ScopeAuthorizationRequirement

Użyj polecenia ScopeAuthorizationRequirement , aby uzyskać bardziej jawne wymagania dotyczące zakresu:

using Microsoft.Identity.Web;
using Microsoft.Identity.Web.Resource;

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("CustomPolicy", policyBuilder =>
    {
        policyBuilder.AddRequirements(
            new ScopeAuthorizationRequirement(new[] { "access_as_user" })
        );
    });
});

Wzorzec 3. Ustawianie zasad domyślnych

Ustaw domyślne zasady, które mają zastosowanie do wszystkich [Authorize] atrybutów automatycznie:

builder.Services.AddAuthorization(options =>
{
    var defaultPolicy = new AuthorizationPolicyBuilder()
        .RequireScope("access_as_user")
        .Build();

    options.DefaultPolicy = defaultPolicy;
});

Teraz każdy [Authorize] atrybut wymaga access_as_user zakresu.

[Authorize] // Automatically requires "access_as_user" scope
public class TodoListController : ControllerBase
{
    // All actions protected by default policy
}

Wzorzec 4. Łączenie wielu wymagań

Łączenie wymagań dotyczących zakresu, roli i uwierzytelniania w ramach jednej zasady:

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AdminPolicy", policyBuilder =>
    {
        policyBuilder.RequireScope("admin");
        policyBuilder.RequireRole("Admin"); // Also check role claim
        policyBuilder.RequireAuthenticatedUser();
    });
});

Wzorzec 5. Tworzenie zasad z konfiguracji

Zachowaj specyficzne dla środowiska zasady poprzez ładowanie zakresów z konfiguracji.

var requiredScopes = builder.Configuration["AzureAd:Scopes"]?.Split(' ');

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("ApiAccessPolicy", policyBuilder =>
    {
        if (requiredScopes != null)
        {
            policyBuilder.RequireScope(requiredScopes);
        }
    });
});

Filtrowanie żądań według najemcy

Ogranicz dostęp interfejsu API do tokenów z określonych dzierżaw Microsoft Entra. Jest to przydatne, gdy interfejs API dla wielu dzierżawców powinien akceptować tylko żądania od zatwierdzonych dzierżawców klientów.

Ograniczenie dostępu tylko dla dozwolonych dzierżawców

Zdefiniuj zasady sprawdzające oświadczenie identyfikatora dzierżawy względem listy dozwolonych:

builder.Services.AddAuthorization(options =>
{
    string[] allowedTenants =
    {
        "14c2f153-90a7-4689-9db7-9543bf084dad", // Contoso tenant
        "af8cc1a0-d2aa-4ca7-b829-00d361edb652", // Fabrikam tenant
        "979f4440-75dc-4664-b2e1-2cafa0ac67d1"  // Northwind tenant
    };

    options.AddPolicy("AllowedTenantsOnly", policyBuilder =>
    {
        policyBuilder.RequireClaim(
            "http://schemas.microsoft.com/identity/claims/tenantid",
            allowedTenants
        );
    });

    // Apply to all endpoints by default
    options.DefaultPolicy = options.GetPolicy("AllowedTenantsOnly");
});

Konfiguracja filtrowania tenanta w ustawieniach

Przechowuj dozwolone identyfikatory najemców w konfiguracji, aby zarządzać nimi bez zmian w kodzie.

appsettings.json:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "ClientId": "your-api-client-id",
    "AllowedTenants": [
      "14c2f153-90a7-4689-9db7-9543bf084dad",
      "af8cc1a0-d2aa-4ca7-b829-00d361edb652"
    ]
  }
}

Przeczytaj listę dzierżaw i utwórz zasady podczas uruchamiania:

var allowedTenants = builder.Configuration.GetSection("AzureAd:AllowedTenants")
    .Get<string[]>();

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AllowedTenantsOnly", policyBuilder =>
    {
        policyBuilder.RequireClaim(
            "http://schemas.microsoft.com/identity/claims/tenantid",
            allowedTenants ?? Array.Empty<string>()
        );
    });
});

Łączenie zakresów z filtrowaniem klientów

Utwórz zasady wymagające zarówno prawidłowego zakresu, jak i zatwierdzonej dzierżawy:

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("SecureApiAccess", policyBuilder =>
    {
        // Require specific scope
        policyBuilder.RequireScope("access_as_user");

        // AND require specific tenant
        policyBuilder.RequireClaim(
            "http://schemas.microsoft.com/identity/claims/tenantid",
            allowedTenants
        );
    });
});

Postępuj zgodnie z najlepszymi rozwiązaniami

Zastosuj te zalecenia, aby utworzyć bezpieczną, konserwalną logikę autoryzacji.

Rzeczy, które należy robić

1. Zawsze paruj [Authorize] z walidacją zakresu:

[Authorize] // Authentication
[RequiredScope("access_as_user")] // Authorization
public class MyController : ControllerBase { }

2. Użyj konfiguracji dla zakresów specyficznych dla środowiska:

[RequiredScope(RequiredScopesConfigurationKey = "AzureAd:Scopes")]

3. Zastosuj najmniejsze uprawnienia:

[HttpGet]
[RequiredScope("read")] // Only read permission needed

[HttpPost]
[RequiredScope("write")] // Write permission for modifications

4. Użyj zasad do złożonej autoryzacji:

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AdminOnly", policy =>
    {
        policy.RequireScope("admin");
        policy.RequireClaim("department", "IT");
    });
});

5. Włącz szczegółowe odpowiedzi na błędy w trakcie tworzenia:

if (builder.Environment.IsDevelopment())
{
    Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true;
}

Czego nie robić

1. Nie pomijaj [Authorize] podczas używania RequiredScope:

//  Wrong - RequiredScope won't work without [Authorize]
[RequiredScope("access_as_user")]
public class MyController : ControllerBase { }

//  Correct
[Authorize]
[RequiredScope("access_as_user")]
public class MyController : ControllerBase { }

2. Nie koduj identyfikatorów najemców w środowisku produkcyjnym:

//  Wrong
policyBuilder.RequireClaim("tid", "14c2f153-90a7-4689-9db7-9543bf084dad");

//  Better - use configuration
var tenants = Configuration.GetSection("AllowedTenants").Get<string[]>();
policyBuilder.RequireClaim("tid", tenants);

3. Nie należy mylić zakresów z rolami:

//  Wrong - This checks roles claim, not scopes
[RequiredScope("Admin")] // "Admin" is typically a role, not a scope

//  Correct
[RequiredScope("access_as_user")] // Scope
[Authorize(Roles = "Admin")] // Role

4. Nie ujawniaj poufnych informacji o zakresie w komunikatach o błędach produkcyjnych:

Skonfiguruj odpowiednie poziomy rejestrowania i obsługę błędów dla środowisk produkcyjnych.


Rozwiązywanie problemów z autoryzacją

Skorzystaj z poniższych wskazówek, aby zdiagnozować typowe problemy z autoryzacją.

403 Zabronione — brak zakresu

Błąd: Interfejs API zwraca wartość 403 nawet z prawidłowym tokenem.

Diagnoza:

  1. Zdekoduj token na jwt.ms.
  2. Sprawdź scp lub scope oświadczenie.
  3. Sprawdź, czy wartość jest zgodna z atrybutem RequiredScope .

Rozwiązanie:

  • Upewnij się, że aplikacja kliencka żąda prawidłowego zakresu podczas uzyskiwania tokenu.
  • Sprawdź, czy zakres jest ujawniony w rejestracji aplikacji API w Microsoft Entra.
  • W razie potrzeby udziel zgody administratora.

RequiredScope nie działa

Objawem: Atrybut wydaje się być ignorowany.

Sprawdź:

  1. Czy dodano [Authorize] atrybut?
  2. Czy wywoływana jest app.UseAuthorization() po app.UseAuthentication()?
  3. Czy jest services.AddAuthorization() zarejestrowany?

Nie można odnaleźć klucza konfiguracji

Błąd: Sprawdzanie zakresu kończy się niepowodzeniem bez wyraźnego powiadomienia.

Sprawdź:

{
  "AzureAd": {
    "Scopes": "access_as_user" // Matches RequiredScopesConfigurationKey
  }
}

Upewnij się, że ścieżka konfiguracji jest dokładnie zgodna.