Autorizzazione basata su attestazioni in ASP.NET Core MVC

Quando viene creata un'identità per un utente dell'app al momento dell'accesso a un'app, il provider di identità può assegnare una o più attestazioni all'identità dell'utente. Un'attestazione è una coppia di valori del nome che rappresenta l'oggetto (un utente, un'app o un servizio o un dispositivo/computer) e non ciò che può fare l'oggetto. Un'attestazione può essere valutata dall'app per determinare i diritti di accesso ai dati e altre risorse protette durante il processo di autorizzazione e può essere usata anche per prendere o esprimere decisioni di autenticazione su un oggetto. Un'identità può contenere più attestazioni con più valori e può contenere più attestazioni dello stesso tipo. Questo articolo illustra come aggiungere verifiche delle attestazioni per l'autorizzazione in un'app ASP.NET Core.

Questo articolo usa esempi MVC e si concentra sugli scenari MVC. Per la guida alle pagine di Blazor e Razor, vedere le risorse seguenti:

  • Autorizzazione basata su dichiarazioni in ASP.NET Core
  • Autorizzazione Basata su Dichiarazione in ASP.NET Core Pages

Esempio di app

L'app di esempio per questo articolo è l'app di esempio WebAll (repository dotnet/AspNetCore.Docs.Samples GitHub) (come scaricare). Per altre informazioni, vedere il file README dell'esempio (README.md).

Aggiungere controlli delle rivendicazioni

I controlli di autorizzazione basati sulle attestazioni sono dichiarativi e applicati ai controller o alle azioni all'interno di un controller.

Le attestazioni nel codice specificano le attestazioni che l'utente corrente deve possedere e, facoltativamente, il valore che l'attestazione deve contenere per accedere alla risorsa richiesta. I requisiti delle dichiarazioni sono basati su politiche. Lo sviluppatore deve creare e registrare una politica che definisce i requisiti delle attestazioni.

Il tipo più semplice di politica di reclamo cerca la presenza di un reclamo e non controlla il valore.

Compilare e registrare la politica e chiamare UseAuthorization (effettuare questa chiamata dopo la riga che chiama UseAuthentication). La registrazione dei criteri avviene come parte della configurazione del servizio di autorizzazione, in genere nel Program file :

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

builder.Services.AddAuthorizationBuilder()
    .AddPolicy("EmployeeOnly", policy => policy.RequireClaim("EmployeeNumber"));

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthentication();
app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

Compilare e registrare la politica e chiamare UseAuthorization (effettuare questa chiamata dopo la riga che chiama UseAuthentication). La registrazione dei criteri avviene come parte della configurazione del servizio di autorizzazione, in genere nel Program file :

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

builder.Services.AddAuthorization(options =>
{
   options.AddPolicy("EmployeeOnly", policy => policy.RequireClaim("EmployeeNumber"));
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthentication();
app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

Compilare e registrare i criteri in Startup.ConfigureServices (Startup.cs) nella configurazione del servizio di autorizzazione:

services.AddAuthorization(options =>
{
    options.AddPolicy("EmployeeOnly", 
        policy => policy.RequireClaim("EmployeeNumber"));
});

Chiamare UseAuthorization in Startup.Configure (Startup.cs) immediatamente dopo l'invocazione di UseAuthentication.

app.UseAuthorization();

Applicare il criterio usando la Policy proprietà sull'attributo [Authorize] per specificare il nome del criterio. Nell'esempio seguente, la policy controlla EmployeeOnly la presenza di un'attestazione EmployeeNumber nell'identità corrente:

[Authorize(Policy = "EmployeeOnly")]
public IActionResult VacationBalance()
{
    return View();
}

L'attributo [Authorize] può essere applicato a un intero controller, nel qual caso solo le identità corrispondenti ai criteri sono consentite l'accesso a qualsiasi azione nel controller:

[Authorize(Policy = "EmployeeOnly")]
public class VacationController : Controller
{
    public IActionResult Index()
    {
        return View();
    }

    public ActionResult VacationBalance()
    {
        return View();
    }

    [AllowAnonymous]
    public ActionResult VacationPolicy()
    {
        return View();
    }
}

Se si dispone di un controller protetto dall'attributo [Authorize] ma si vuole consentire l'accesso anonimo a una determinata azione, applicare l'attributo[AllowAnonymous] :

[Authorize(Policy = "EmployeeOnly")]
public class VacationController : Controller
{
    public IActionResult Index()
    {
        return View();
    }

    public ActionResult VacationBalance()
    {
        return View();
    }

    [AllowAnonymous]
    public ActionResult VacationPolicy()
    {
        return View();
    }
}

È possibile specificare un elenco di valori consentiti durante la creazione di un criterio. Il criterio seguente passa solo per i dipendenti il cui numero di dipendente è 1, 2, 3, 4 o 5:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

builder.Services.AddAuthorizationBuilder()
    .AddPolicy("Founders", policy =>
        policy.RequireClaim("EmployeeNumber", "1", "2", "3", "4", "5"));

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthentication();
app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("Founders", policy =>
                      policy.RequireClaim("EmployeeNumber", "1", "2", "3", "4", "5"));
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthentication();
app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();
services.AddAuthorization(options =>
{
    options.AddPolicy("Founder", policy =>
        policy.RequireClaim("EmployeeNumber", "1", "2", "3", "4", "5"));
});

Aggiungere un controllo attestazione generico

Se il valore dell'attestazione non è un singolo valore o è necessaria una logica di valutazione dell'attestazione più flessibile, ad esempio criteri di corrispondenza, controllo dell'autorità emittente dell'attestazione o analisi di valori di attestazioni complessi, usare RequireAssertion con HasClaim. Ad esempio, il criterio seguente richiede che l'attestazione dell'utente email termini con un dominio specifico:

builder.Services.AddAuthorizationBuilder()
    .AddPolicy("ContosoOnly", policy =>
        policy.RequireAssertion(context =>
            context.User.HasClaim(c =>
                c.Type == "email" &&
                c.Value.EndsWith("@contoso.com", StringComparison.OrdinalIgnoreCase))));
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("ContosoOnly", policy =>
        policy.RequireAssertion(context =>
            context.User.HasClaim(c =>
                c.Type == "email" &&
                c.Value.EndsWith("@contoso.com", StringComparison.OrdinalIgnoreCase))));
});
services.AddAuthorization(options =>
{
    options.AddPolicy("ContosoOnly", policy =>
        policy.RequireAssertion(context =>
            context.User.HasClaim(c =>
                c.Type == "email" &&
                c.Value.EndsWith("@contoso.com", StringComparison.OrdinalIgnoreCase))));
});

Per altre informazioni, vedere l'autorizzazione basata su criteri in ASP.NET Core.

Valutare più criteri

Se vengono applicati più criteri a livello di controller e azione, tutti i criteri devono essere superati prima che venga concesso l'accesso.

[Authorize(Policy = "EmployeeOnly")]
public class SalaryController : Controller
{
    public IActionResult Index()
    {
        return View();
    }

    public IActionResult Payslip()
    {
        return View();
    }

    [Authorize(Policy = "HumanResources")]
    public IActionResult UpdateSalary()
    {
        return View();
    }
}

Nell'esempio precedente, qualsiasi identità che soddisfa i EmployeeOnly criteri può accedere all'azione Payslip , in quanto tale criterio viene applicato al controller. Tuttavia, per chiamare l'azione, l'identità UpdateSalary deve soddisfare sia i EmployeeOnly criteri che i HumanResources criteri.

Se vuoi criteri più complessi, ad esempio prendere una data di nascita, calcolare l'età a partire da essa e verificare che l'età sia di 21 anni o più, devi scrivere gestori personalizzati di criteri.

Sensibilità alle maiuscole e minuscole delle richieste

I valori delle attestazioni vengono confrontati usando StringComparison.Ordinal. Ciò significa che Admin (maiuscolo A) e admin (minuscolo a) sono sempre trattati come valori della dichiarazione diversi, indipendentemente dal gestore di autenticazione che ha creato l'identità.

Separatamente, il confronto dei tipi di claim (usato per individuare i claim in base al tipo, ad esempio email) può essere case-sensitive o case-insensitive a seconda dell'implementazione ClaimsIdentity. Con Microsoft.IdentityModel in ASP.NET Core 8.0 o versione successiva (usato da AddJwtBearer, AddOpenIdConnect, AddWsFederation e AddMicrosoftIdentityWebApp/AddMicrosoftIdentityWebApi), CaseSensitiveClaimsIdentity viene generato durante la convalida del token, che usa la corrispondenza del tipo di attestazione con distinzione tra maiuscole e minuscole.

Il valore predefinito ClaimsIdentity fornito dal runtime di .NET (usato nella maggior parte dei casi, inclusi tutti i flussi basati su cookie) usa comunque la corrispondenza del tipo di attestazione senza distinzione tra maiuscole e minuscole.

In pratica, questa distinzione è raramente importante quando il tipo di attestazione viene configurato una volta durante la creazione dell'identità ed è abbinato in modo coerente. Questo vale anche per i ruoli quando vengono rappresentati come asserzioni. Utilizzare sempre una formattazione coerente per i valori delle richieste e i tipi di richiesta per evitare problemi sottili.