Dela via


Säkerhet: Autentisering och auktorisering i ASP.NET webbformulär och Blazor

Dricks

Det här innehållet är ett utdrag ur e-boken, Blazor för ASP NET Web Forms Developers for Azure, tillgängligt på .NET Docs eller som en kostnadsfri nedladdningsbar PDF som kan läsas offline.

Blazor-for-ASP-NET-Web-Forms-Developers eBook cover thumbnail.

Om du migrerar från ett ASP.NET Web Forms-program till Blazor måste du nästan säkert uppdatera hur autentisering och auktorisering utförs, förutsatt att programmet har konfigurerat autentiseringen. Det här kapitlet beskriver hur du migrerar från den universella providermodellen ASP.NET Web Forms (för medlemskap, roller och användarprofiler) och hur du arbetar med ASP.NET Core Identity från Blazor appar. Även om det här kapitlet beskriver de övergripande stegen och övervägandena kan detaljerade steg och skript hittas i den refererade dokumentationen.

ASP.NET universella leverantörer

Sedan ASP.NET 2.0 har plattformen ASP.NET Web Forms stöd för en leverantörsmodell för en mängd olika funktioner, inklusive medlemskap. Den universella medlemskapsprovidern, tillsammans med den valfria rollprovidern, distribueras ofta med ASP.NET Web Forms-program. Det erbjuder ett robust och säkert sätt att hantera autentisering och auktorisering som fortsätter att fungera bra idag. Det senaste erbjudandet av dessa universella leverantörer är tillgängligt som ett NuGet-paket, Microsoft.AspNet.Providers.

Universal Providers fungerar med ett SQL-databasschema som innehåller tabeller som aspnet_Applications, aspnet_Membership, aspnet_Rolesoch aspnet_Users. När de konfigureras genom att köra kommandot aspnet_regsql.exe installerar leverantörerna tabeller och lagrade procedurer som tillhandahåller alla nödvändiga frågor och kommandon för att arbeta med underliggande data. Databasschemat och dessa lagrade procedurer är inte kompatibla med nyare ASP.NET identitets- och ASP.NET Core Identity-system, så befintliga data måste migreras till det nya systemet. Bild 1 visar ett exempeltabellschema som konfigurerats för universella leverantörer.

universal providers-schema

Den universella providern hanterar användare, medlemskap, roller och profiler. Användare tilldelas globalt unika identifierare och grundläggande information som userId, userName osv. lagras i aspnet_Users tabellen. Autentiseringsinformation, till exempel lösenord, lösenordsformat, lösenordssalt, utelåsningsräknare och information, osv. lagras i aspnet_Membership tabellen. Roller består helt enkelt av namn och unika identifierare, som tilldelas till användare via associationstabellen aspnet_UsersInRoles , vilket ger en många-till-många-relation.

Om ditt befintliga system använder roller utöver medlemskap måste du migrera användarkonton, associerade lösenord, roller och rollmedlemskap till ASP.NET Core Identity. Du behöver förmodligen också uppdatera koden där du för närvarande utför rollkontroller med hjälp av if-instruktioner för att i stället använda deklarativa filter, attribut och/eller tagghjälpmedel. Vi kommer att granska migreringsöverväganden i detalj i slutet av det här kapitlet.

Auktoriseringskonfiguration i webbformulär

Om du vill konfigurera auktoriserad åtkomst till vissa sidor i ett ASP.NET Webbformulär-program anger du vanligtvis att vissa sidor eller mappar inte är tillgängliga för anonyma användare. Den här konfigurationen görs i filen web.config:

<?xml version="1.0"?>
<configuration>
    <system.web>
      <authentication mode="Forms">
        <forms defaultUrl="~/home.aspx" loginUrl="~/login.aspx"
          slidingExpiration="true" timeout="2880"></forms>
      </authentication>

      <authorization>
        <deny users="?" />
      </authorization>
    </system.web>
</configuration>

Konfigurationsavsnittet authentication konfigurerar formulärautentiseringen för programmet. Avsnittet authorization används för att neka anonyma användare för hela programmet. Du kan dock tillhandahålla mer detaljerade auktoriseringsregler per plats och tillämpa rollbaserade auktoriseringskontroller.

<location path="login.aspx">
  <system.web>
    <authorization>
      <allow users="*" />
    </authorization>
  </system.web>
</location>

Ovanstående konfiguration skulle i kombination med den första tillåta anonyma användare att komma åt inloggningssidan, vilket åsidosätter den webbplatsomfattande begränsningen för icke-autentiserade användare.

<location path="/admin">
  <system.web>
    <authorization>
      <allow roles="Administrators" />
      <deny users="*" />
    </authorization>
  </system.web>
</location>

Ovanstående konfiguration, när den kombineras med de andra, begränsar åtkomsten till mappen och alla resurser i den /admin till medlemmar i rollen "Administratörer". Den här begränsningen kan också tillämpas genom att placera en separat web.config fil i mapproten /admin .

Auktoriseringskod i webbformulär

Förutom att konfigurera åtkomst med kan web.configdu även programmatiskt konfigurera åtkomst och beteende i webbformulärprogrammet. Du kan till exempel begränsa möjligheten att utföra vissa åtgärder eller visa vissa data baserat på användarens roll.

Den här koden kan användas både i kod bakom logik och på själva sidan:

<% if (HttpContext.Current.User.IsInRole("Administrators")) { %>
  <a href="/admin">Go To Admin</a>
<% } %>

Förutom att kontrollera medlemskap i användarrollen kan du även avgöra om de autentiseras (även om detta ofta är bättre med hjälp av den platsbaserade konfiguration som beskrivs ovan). Nedan visas ett exempel på den här metoden.

protected void Page_Load(object sender, EventArgs e)
{
    if (!User.Identity.IsAuthenticated)
    {
        FormsAuthentication.RedirectToLoginPage();
    }
    if (!Roles.IsUserInRole(User.Identity.Name, "Administrators"))
    {
        MessageLabel.Text = "Only administrators can view this.";
        SecretPanel.Visible = false;
    }
}

I koden ovan används rollbaserad åtkomstkontroll (RBAC) för att avgöra om vissa element på sidan, till exempel en SecretPanel, är synliga baserat på den aktuella användarens roll.

Vanligtvis konfigurerar ASP.NET Web Forms-program säkerhet i web.config filen och lägger sedan till ytterligare kontroller där det behövs på .aspx sidor och deras relaterade .aspx.cs kod bakom filer. De flesta program utnyttjar den universella medlemskapsprovidern, ofta med den extra rollprovidern.

ASP.NET Kärnidentitet

Även om den fortfarande har till uppgift att auktorisera och auktorisera använder ASP.NET Core Identity en annan uppsättning abstraktioner och antaganden jämfört med de universella leverantörerna. Den nya identitetsmodellen stöder till exempel autentisering från tredje part, vilket gör det möjligt för användare att autentisera med ett konto för sociala medier eller någon annan betrodd autentiseringsprovider. ASP.NET Core Identity har stöd för användargränssnittet för vanliga sidor som inloggning, utloggning och registrering. Den utnyttjar EF Core för sin dataåtkomst och använder EF Core-migreringar för att generera det schema som krävs för att stödja datamodellen. Den här introduktionen till Identity på ASP.NET Core ger en bra översikt över vad som ingår i ASP.NET Core Identity och hur du kommer igång med att arbeta med den. Om du inte redan har konfigurerat ASP.NET Core Identity i ditt program och dess databas hjälper det dig att komma igång.

Roller, anspråk och principer

Både de universella leverantörerna och ASP.NET Core Identity stöder begreppet roller. Du kan skapa roller för användare och tilldela användare till roller. Användare kan tillhöra valfritt antal roller och du kan verifiera rollmedlemskap som en del av din auktoriseringsimplementering.

Förutom roller stöder ASP.NET Core-identiteten begreppen anspråk och principer. Även om en roll specifikt ska motsvara en uppsättning resurser som en användare i den rollen ska kunna komma åt, är ett anspråk helt enkelt en del av en användares identitet. Ett anspråk är ett namnvärdepar som representerar vad ämnet är, inte vad ämnet kan göra.

Det är möjligt att granska en användares anspråk direkt och avgöra utifrån dessa värden om en användare ska få åtkomst till en resurs. Sådana kontroller är dock ofta repetitiva och utspridda i hela systemet. En bättre metod är att definiera en princip.

En auktoriseringsprincip består av ett eller flera krav. Principer registreras som en del av auktoriseringstjänstens konfiguration i ConfigureServices metoden Startup.csför . Följande kodfragment konfigurerar till exempel en princip med namnet "CanadiansOnly", som har kravet att användaren har country-anspråket med värdet "Kanada".

services.AddAuthorization(options =>
{
    options.AddPolicy("CanadiansOnly", policy => policy.RequireClaim(ClaimTypes.Country, "Canada"));
});

Du kan lära dig mer om hur du skapar anpassade principer i dokumentationen.

Oavsett om du använder principer eller roller kan du ange att en viss sida i ditt Blazor program kräver den [Authorize] rollen eller principen med attributet som tillämpas med @attribute direktivet.

Kräver en roll:

@attribute [Authorize(Roles ="administrators")]

Krav på att en princip ska uppfyllas:

@attribute [Authorize(Policy ="CanadiansOnly")]

Om du behöver åtkomst till en användares autentiseringstillstånd, roller eller anspråk i koden finns det två huvudsakliga sätt att uppnå den här funktionen. Den första är att ta emot autentiseringstillståndet som en sammanhängande parameter. Den andra är att komma åt tillståndet med hjälp av en inmatad AuthenticationStateProvider. Information om var och en av dessa metoder beskrivs i säkerhetsdokumentationenBlazor.

Följande kod visar hur du tar emot parametern AuthenticationState som en sammanhängande parameter:

[CascadingParameter]
private Task<AuthenticationState> authenticationStateTask { get; set; }

Med den här parametern på plats kan du hämta användaren med den här koden:

var authState = await authenticationStateTask;
var user = authState.User;

Följande kod visar hur du AuthenticationStateProvidermatar in :

@using Microsoft.AspNetCore.Components.Authorization
@inject AuthenticationStateProvider AuthenticationStateProvider

Med providern på plats kan du få åtkomst till användaren med följande kod:

AuthenticationState authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
ClaimsPrincipal user = authState.User;

if (user.Identity.IsAuthenticated)
{
  // work with user.Claims and/or user.Roles
}

Obs! KomponentenAuthorizeView, som beskrivs senare i det här kapitlet, ger ett deklarativt sätt att styra vad en användare ser på en sida eller komponent.

För att arbeta med användare och anspråk (i Blazor serverprogram) kan du också behöva mata in en UserManager<T> (använd IdentityUser som standard) som du kan använda för att räkna upp och ändra anspråk för en användare. Mata först in typen och tilldela den till en egenskap:

@inject UserManager<IdentityUser> MyUserManager

Använd den sedan för att arbeta med användarens anspråk. Följande exempel visar hur du lägger till och bevarar ett anspråk på en användare:

private async Task AddCountryClaim()
{
    var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
    var user = authState.User;
    var identityUser = await MyUserManager.FindByNameAsync(user.Identity.Name);

    if (!user.HasClaim(c => c.Type == ClaimTypes.Country))
    {
        // stores the claim in the cookie
        ClaimsIdentity id = new ClaimsIdentity();
        id.AddClaim(new Claim(ClaimTypes.Country, "Canada"));
        user.AddIdentity(id);

        // save the claim in the database
        await MyUserManager.AddClaimAsync(identityUser, new Claim(ClaimTypes.Country, "Canada"));
    }
}

Om du behöver arbeta med roller följer du samma metod. Du kan behöva mata in en RoleManager<T> (används IdentityRole som standardtyp) för att visa och hantera själva rollerna.

Obs! I Blazor WebAssembly-projekt måste du ange server-API:er för att utföra dessa åtgärder (i stället för att använda UserManager<T> eller RoleManager<T> direkt). Ett Blazor WebAssembly-klientprogram skulle hantera anspråk och/eller roller genom att anropa API-slutpunkter som exponeras för detta ändamål på ett säkert sätt.

Migreringsguide

Att migrera från ASP.NET webbformulär och universella leverantörer till ASP.NET Core Identity kräver flera steg:

  1. Skapa ASP.NET Core Identity-databasschema i måldatabasen
  2. Migrera data från universalproviderschema till ASP.NET Core Identity-schema
  3. Migrera konfigurationen web.config från till mellanprogram och tjänster, vanligtvis i Program.cs (eller en Startup klass)
  4. Uppdatera enskilda sidor med hjälp av kontroller och villkor för att använda tagghjälpare och nya identitets-API:er.

Vart och ett av dessa steg beskrivs ingående i följande avsnitt.

Skapa ASP.NET Core Identity-schemat

Det finns flera sätt att skapa den nödvändiga tabellstrukturen som används för ASP.NET Core Identity. Det enklaste är att skapa ett nytt ASP.NET Core-webbprogram. Välj Webbprogram och ändra sedan autentiseringstyp för att använda enskilda konton.

nytt projekt med enskilda konton

Från kommandoraden kan du göra samma sak genom att köra dotnet new webapp -au Individual. När appen har skapats kör du den och registrerar den på webbplatsen. Du bör utlösa en sida som den som visas nedan:

tillämpa migreringssidan

Klicka på knappen "Tillämpa migreringar" så skapas de nödvändiga databastabellerna åt dig. Dessutom bör migreringsfilerna visas i projektet enligt följande:

migreringsfiler

Du kan köra migreringen själv utan att köra webbprogrammet med hjälp av det här kommandoradsverktyget:

dotnet ef database update

Om du hellre kör ett skript för att tillämpa det nya schemat på en befintlig databas kan du skripta migreringarna från kommandoraden. Kör det här kommandot för att generera skriptet:

dotnet ef migrations script -o auth.sql

Kommandot ovan skapar ett SQL-skript i utdatafilen auth.sql, som sedan kan köras mot vilken databas du vill. Om du har problem med att köra dotnet ef kommandon kontrollerar du att EF Core-verktygen är installerade på systemet.

Om du har ytterligare kolumner i källtabellerna måste du identifiera den bästa platsen för dessa kolumner i det nya schemat. I allmänhet bör kolumner som finns i aspnet_Membership tabellen mappas till AspNetUsers tabellen. Kolumner på aspnet_Roles ska mappas till AspNetRoles. Eventuella ytterligare kolumner i aspnet_UsersInRoles tabellen läggs till i AspNetUserRoles tabellen.

Det är också värt att överväga att placera ytterligare kolumner i separata tabeller. Så att framtida migreringar inte behöver ta hänsyn till sådana anpassningar av standardidentitetsschemat.

Migrera data från universella leverantörer till ASP.NET Core Identity

När du har schemat för måltabellen på plats är nästa steg att migrera dina användar- och rollposter till det nya schemat. En fullständig lista över schemaskillnaderna, inklusive vilka kolumner som mappar till vilka nya kolumner, finns här.

Om du vill migrera dina användare från medlemskap till de nya identitetstabellerna bör du följa stegen som beskrivs i dokumentationen. När du har följt de här stegen och skriptet måste användarna ändra sitt lösenord nästa gång de loggar in.

Det är möjligt att migrera användarlösenord, men processen är mycket mer involverad. Att kräva att användarna uppdaterar sina lösenord som en del av migreringsprocessen och uppmuntrar dem att använda nya unika lösenord kommer sannolikt att förbättra programmets övergripande säkerhet.

Migrera säkerhetsinställningar från web.config till appstart

Som nämnts ovan konfigureras ASP.NET medlemskap och rollprovidrar i programmets web.config fil. Eftersom ASP.NET Core-appar inte är kopplade till IIS och använder ett separat system för konfiguration måste dessa inställningar konfigureras någon annanstans. För det mesta konfigureras ASP.NET Core Identity i filen Program.cs . Öppna webbprojektet som skapades tidigare (för att generera schemat för identitetstabellen) och granska dess Program.cs -fil (eller Startup.cs).

Den här koden lägger till stöd för EF Core och identitet:

// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options =>
    options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();

Tilläggsmetoden AddDefaultIdentity används för att konfigurera identitet att använda standard ApplicationDbContext - och ramverkets IdentityUser typ. Om du använder en anpassad IdentityUsermåste du ange dess typ här. Om dessa tilläggsmetoder inte fungerar i ditt program kontrollerar du att du har rätt using direktiv och att du har nödvändiga NuGet-paketreferenser. Projektet bör till exempel ha en viss version av paketen Microsoft.AspNetCore.Identity.EntityFrameworkCore och Microsoft.AspNetCore.Identity.UI refererade till.

Även i Program.cs bör du se nödvändiga mellanprogram som konfigurerats för webbplatsen. UseAuthentication Mer specifikt, och UseAuthorization bör konfigureras och på rätt plats.

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseStaticFiles();

app.UseRouting();

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

//app.MapControllers();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");

ASP.NET Identity konfigurerar inte anonym eller rollbaserad åtkomst till platser från Program.cs. Du måste migrera platsspecifika auktoriseringskonfigurationsdata till filter i ASP.NET Core. Anteckna vilka mappar och sidor som kräver sådana uppdateringar. Du kommer att göra dessa ändringar i nästa avsnitt.

Uppdatera enskilda sidor för att använda ASP.NET Core Identity-abstraktioner

Om du hade web.config inställningar för att neka åtkomst till vissa sidor eller mappar till anonyma användare i ditt ASP.NET webbformulär skulle du migrera dessa ändringar genom att lägga till [Authorize] attributet på sådana sidor:

@attribute [Authorize]

Om du ytterligare hade nekat åtkomst förutom de användare som tillhör en viss roll skulle du också migrera det här beteendet genom att lägga till ett attribut som anger en roll:

@attribute [Authorize(Roles ="administrators")]

Attributet [Authorize] fungerar bara på @page komponenter som nås via routern Blazor . Attributet fungerar inte med underordnade komponenter, som i stället ska använda AuthorizeView.

Om du har logik i sidmarkeringen för att avgöra om kod ska visas för en viss användare kan du ersätta den med komponenten AuthorizeView . Komponenten AuthorizeView visar selektivt användargränssnittet beroende på om användaren har behörighet att se det. Den exponerar också en context variabel som kan användas för att komma åt användarinformation.

<AuthorizeView>
    <Authorized>
        <h1>Hello, @context.User.Identity.Name!</h1>
        <p>You can only see this content if you are authenticated.</p>
    </Authorized>
    <NotAuthorized>
        <h1>Authentication Failure!</h1>
        <p>You are not signed in.</p>
    </NotAuthorized>
</AuthorizeView>

Du kan komma åt autentiseringstillståndet inom procedurlogik genom att komma åt användaren från en Task<AuthenticationState konfigurerad med [CascadingParameter] attributet. Den här konfigurationen ger dig åtkomst till användaren, vilket gör att du kan avgöra om de är autentiserade och om de tillhör en viss roll. Om du behöver utvärdera en princip procedurmässigt kan du mata in en instans av IAuthorizationService och anropa metoden på den AuthorizeAsync . Följande exempelkod visar hur du hämtar användarinformation och tillåter en behörig användare att utföra en uppgift som begränsas av content-editor principen.

@using Microsoft.AspNetCore.Authorization
@inject IAuthorizationService AuthorizationService

<button @onclick="@DoSomething">Do something important</button>

@code {
    [CascadingParameter]
    private Task<AuthenticationState> authenticationStateTask { get; set; }

    private async Task DoSomething()
    {
        var user = (await authenticationStateTask).User;

        if (user.Identity.IsAuthenticated)
        {
            // Perform an action only available to authenticated (signed-in) users.
        }

        if (user.IsInRole("admin"))
        {
            // Perform an action only available to users in the 'admin' role.
        }

        if ((await AuthorizationService.AuthorizeAsync(user, "content-editor"))
            .Succeeded)
        {
            // Perform an action only available to users satisfying the
            // 'content-editor' policy.
        }
    }
}

Den AuthenticationState första måste konfigureras som ett sammanhängande värde innan den kan bindas till en sammanhängande parameter som den här. Det görs vanligtvis med hjälp av komponenten CascadingAuthenticationState . Den här konfigurationen görs vanligtvis i App.razor:

<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(Program).Assembly">
        <Found Context="routeData">
            <AuthorizeRouteView RouteData="@routeData"
                DefaultLayout="@typeof(MainLayout)" />
        </Found>
        <NotFound>
            <LayoutView Layout="@typeof(MainLayout)">
                <p>Sorry, there's nothing at this address.</p>
            </LayoutView>
        </NotFound>
    </Router>
</CascadingAuthenticationState>

Sammanfattning

Blazor använder samma säkerhetsmodell som ASP.NET Core, som är ASP.NET Core Identity. Att migrera från universella leverantörer till ASP.NET Core Identity är relativt enkelt, förutsatt att inte för mycket anpassning tillämpades på det ursprungliga dataschemat. När data har migrerats är arbetet med autentisering och auktorisering i Blazor appar väl dokumenterat, med konfigurerbart och programmatiskt stöd för de flesta säkerhetskrav.

Referenser