Delen via


ASP.NET Core Blazor beveiligde browseropslag

Voor tijdelijke gegevens die de gebruiker actief maakt, is een veelgebruikte opslaglocatie de localStorage en sessionStorage verzamelingen van de browser:

  • localStorage is beperkt tot het exemplaar van de browser. Als de gebruiker de pagina opnieuw laadt of de browser sluit en opnieuw opent, blijft de status behouden. Als de gebruiker meerdere browsertabbladen opent, wordt de status gedeeld op de tabbladen. Gegevens blijven bewaard in localStorage totdat ze expliciet zijn gewist. De localStorage gegevens voor een document dat is geladen in een sessie 'privé browsen' of 'incognito' wordt gewist wanneer het laatste tabblad 'privé' wordt gesloten.
  • sessionStorage is gericht op het browsertabblad. Als de gebruiker het tabblad opnieuw laadt, blijft de status behouden. Als de gebruiker het tabblad of de browser sluit, gaat de status verloren. Als de gebruiker meerdere browsertabbladen opent, heeft elk tabblad een eigen onafhankelijke versie van de gegevens.

Over het algemeen is sessionStorage veiliger te gebruiken. sessionStorage het risico voorkomt dat een gebruiker meerdere tabbladen opent en het volgende tegenkomt:

  • Fouten in statusopslag op tabbladen.
  • Verwarrend gedrag wanneer een tabblad de status van andere tabbladen overschrijft.

localStorage is de betere keuze als de app de status moet behouden bij het sluiten en opnieuw openen van de browser.

Opmerkingen bij het gebruik van browseropslag:

  • Net als bij het gebruik van een database aan de serverzijde zijn het laden en opslaan van gegevens asynchroon.
  • De aangevraagde pagina bestaat niet in de browser tijdens het prerendering, dus lokale opslag is niet beschikbaar tijdens het prerendering.
  • De opslag van een paar kilobytes aan gegevens is redelijk om aan te houden voor server-side Blazor apps. Na een paar kilobytes moet u rekening houden met de gevolgen voor de prestaties omdat de gegevens worden geladen en opgeslagen in het netwerk.
  • Gebruikers kunnen de gegevens bekijken of manipuleren. ASP.NET Core Data Protection kan het risico beperken. ASP.NET Core Protected Browser Storage maakt bijvoorbeeld gebruik van ASP.NET Core Data Protection.

NuGet-pakketten van derden bieden API's voor het werken met localStorage en sessionStorage. Het is de moeite waard om een pakket te kiezen dat transparant gebruikmaakt van ASP.NET Core Data Protection. Data Protection versleutelt opgeslagen gegevens en vermindert het potentiële risico op manipulatie met opgeslagen gegevens. Als JSON-geserialiseerde gegevens worden opgeslagen in tekst zonder opmaak, kunnen gebruikers de gegevens zien met hulpprogramma's voor browserontwikkelaars en de opgeslagen gegevens ook wijzigen. Het beveiligen van triviale gegevens is geen probleem. Het lezen of wijzigen van de opgeslagen kleur van een UI-element is bijvoorbeeld geen aanzienlijk beveiligingsrisico voor de gebruiker of de organisatie. Voorkom dat gebruikers gevoelige gegevens kunnen inspecteren of manipuleren.

ASP.NET Core Beschermde Browseropslag

ASP.NET Core Protected Browser Storage maakt gebruik van ASP.NET Core Data Protection- voor localStorage en sessionStorage.

Opmerking

Beveiligde browseropslag is afhankelijk van ASP.NET Core Data Protection en wordt alleen ondersteund voor Blazor-apps aan de serverzijde.

Waarschuwing

Microsoft.AspNetCore.ProtectedBrowserStorage is een niet-ondersteund, experimenteel pakket dat niet is bedoeld voor productiegebruik.

Het pakket is alleen beschikbaar voor gebruik in ASP.NET Core 3.1-apps.

Configuratie

  1. Voeg een pakketreferentie toe aan Microsoft.AspNetCore.ProtectedBrowserStorage.

    Opmerking

    Zie voor richtlijnen over het toevoegen van pakketten aan .NET-apps de artikelen onder Pakketten installeren en beheren in de Package consumption workflow (NuGet-documentatie). Bevestig de juiste pakketversies op NuGet.org.

  2. Voeg in het bestand _Host.cshtml het volgende script toe in de afsluitende </body>-tag:

    <script src="_content/Microsoft.AspNetCore.ProtectedBrowserStorage/protectedBrowserStorage.js"></script>
    
  3. Roep in Startup.ConfigureServicesAddProtectedBrowserStorage aan om localStorage en sessionStorage services toe te voegen aan de serviceverzameling:

    services.AddProtectedBrowserStorage();
    

Gegevens in een onderdeel opslaan en laden

Gebruik de @inject instructie om een exemplaar van een van de volgende items te injecteren in een van de volgende onderdelen waarvoor het laden of opslaan van gegevens in de browseropslag is vereist:

  • ProtectedLocalStorage
  • ProtectedSessionStorage

De keuze is afhankelijk van de opslaglocatie van de browser die u wilt gebruiken. In het volgende voorbeeld wordt sessionStorage gebruikt:

@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inject ProtectedSessionStorage ProtectedSessionStore
@using Microsoft.AspNetCore.ProtectedBrowserStorage
@inject ProtectedSessionStorage ProtectedSessionStore

De @using instructie kan in het _Imports.razor-bestand van de app worden geplaatst in plaats van in het onderdeel. Gebruik van het _Imports.razor-bestand maakt de naamruimte beschikbaar voor grotere segmenten van de app of de hele app.

Als u de currentCount waarde in het Counter onderdeel van een app wilt behouden op basis van de Blazor projectsjabloon, wijzigt u de IncrementCount methode om ProtectedSessionStore.SetAsyncte gebruiken:

private async Task IncrementCount()
{
    currentCount++;
    await ProtectedSessionStore.SetAsync("count", currentCount);
}

In grotere, realistischere apps is de opslag van afzonderlijke velden een onwaarschijnlijk scenario. Apps slaan waarschijnlijk hele modelobjecten op die complexe status bevatten. ProtectedSessionStore serialiseert en deserialiseert automatisch JSON-gegevens om complexe statusobjecten op te slaan.

In het voorgaande codevoorbeeld worden de currentCount gegevens opgeslagen als sessionStorage['count'] in de browser van de gebruiker. De gegevens worden niet opgeslagen in tekst zonder opmaak, maar worden beveiligd met ASP.NET Core Data Protection. De versleutelde gegevens kunnen worden gecontroleerd als sessionStorage['count'] wordt geëvalueerd in de ontwikkelaarsconsole van de browser.

Als u de currentCount gegevens wilt herstellen als de gebruiker later terugkeert naar het Counter onderdeel, inclusief als de gebruiker zich op een nieuw circuit bevindt, gebruikt u ProtectedSessionStore.GetAsync:

protected override async Task OnInitializedAsync()
{
    var result = await ProtectedSessionStore.GetAsync<int>("count");
    currentCount = result.Success ? result.Value : 0;
}
protected override async Task OnInitializedAsync()
{
    currentCount = await ProtectedSessionStore.GetAsync<int>("count");
}

Als de parameters van het onderdeel de navigatiestatus bevatten, roept u ProtectedSessionStore.GetAsync aan en wijst u een niet-null resultaat toe aan OnParametersSetAsync, niet OnInitializedAsync. OnInitializedAsync wordt slechts eenmaal aangeroepen wanneer het onderdeel voor het eerst wordt geïnstantieerd. OnInitializedAsync wordt later niet opnieuw aangeroepen als de gebruiker naar een andere URL navigeert terwijl deze op dezelfde pagina blijft. Zie ASP.NET Core Razor levenscyclus van onderdelen voor meer informatie.

Waarschuwing

De voorbeelden in deze sectie werken alleen als de server geen prerendering heeft ingeschakeld. Als prerendering is ingeschakeld, wordt er een fout gegenereerd waarin wordt uitgelegd dat JavaScript-interopaanroepen niet kunnen worden uitgegeven omdat het onderdeel wordt voorbereid.

Schakel prerendering uit of voeg extra code toe om te werken met prerendering. Zie de sectie Handle prerendering voor meer informatie over het schrijven van code die werkt met prerendering.

De laadstatus afhandelen

Omdat browseropslag asynchroon wordt geopend via een netwerkverbinding, is er altijd een periode voordat de gegevens worden geladen en beschikbaar zijn voor een onderdeel. Voor de beste resultaten wordt een bericht weergegeven terwijl het laden wordt uitgevoerd in plaats van lege of standaardgegevens weer te geven.

Een methode is om bij te houden of de gegevens nullzijn, wat betekent dat de gegevens nog steeds worden geladen. In het standaardonderdeel Counter wordt het aantal in een intbewaard. currentCount nullable maken door een vraagteken (?) toe te voegen aan het type (int):

private int? currentCount;

In plaats van het aantal en de Increment-knop onvoorwaardelijk weer te geven, worden deze elementen alleen getoond als de gegevens zijn geladen door HasValuete controleren.

@if (currentCount.HasValue)
{
    <p>Current count: <strong>@currentCount</strong></p>
    <button @onclick="IncrementCount">Increment</button>
}
else
{
    <p>Loading...</p>
}

Prerendering afhandelen

Tijdens het voorrenderen:

  • Er bestaat geen interactieve verbinding met de browser van de gebruiker.
  • De browser heeft nog geen pagina waarin JavaScript-code kan worden uitgevoerd.

localStorage of sessionStorage zijn niet beschikbaar tijdens het prerendering. Als het onderdeel probeert te communiceren met opslag, wordt er een fout gegenereerd waarin wordt uitgelegd dat JavaScript-interoperabiliteitsaanroepen niet kunnen worden uitgegeven omdat het onderdeel wordt voorbereid.

Een manier om de fout op te lossen, is door prerendering uit te schakelen. Dit is meestal de beste keuze als de app intensief gebruik maakt van op browsers gebaseerde opslag. Prerendering voegt complexiteit toe en profiteert niet van de app, omdat de app pas nuttige inhoud kan prerenren als localStorage of sessionStorage beschikbaar zijn.

Als u prerendering wilt uitschakelen, geeft u de weergavemodus aan met de parameter prerender ingesteld op false op het hoogste niveau in de componenthiërarchie van de app die geen hoofdonderdeel is.

Opmerking

Het interactief maken van een hoofdonderdeel, zoals het App-onderdeel, wordt niet ondersteund. Daarom kan prerendering niet rechtstreeks worden uitgeschakeld door het App onderdeel.

Voor apps op basis van de Blazor Web App projectsjabloon wordt het prerendering meestal uitgeschakeld wanneer het Routes onderdeel wordt gebruikt in het App-onderdeel (Components/App.razor):

<Routes @rendermode="new InteractiveServerRenderMode(prerender: false)" />

Schakel ook het prerendering uit voor het HeadOutlet-onderdeel:

<HeadOutlet @rendermode="new InteractiveServerRenderMode(prerender: false)" />

Zie Prerender ASP.NET Core-onderdelen Razorvoor meer informatie.

Als u het prerendering wilt uitschakelen, opent u het _Host.cshtml bestand en wijzigt u het kenmerk render-mode van de Component Tag Helper in Server:

<component type="typeof(App)" render-mode="Server" />

Wanneer prerendering is uitgeschakeld, wordt prerendering van <head> inhoud uitgeschakeld.

Prerendering kan nuttig zijn voor andere pagina's die geen localStorage of sessionStoragegebruiken. Als u het prerendering wilt behouden, moet u de laadbewerking uitstellen totdat de browser is verbonden met het circuit. Hier volgt een voorbeeld voor het opslaan van een tellerwaarde:

@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inject ProtectedLocalStorage ProtectedLocalStore

@if (isConnected)
{
    <p>Current count: <strong>@currentCount</strong></p>
    <button @onclick="IncrementCount">Increment</button>
}
else
{
    <p>Loading...</p>
}

@code {
    private int currentCount;
    private bool isConnected;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            isConnected = true;
            await LoadStateAsync();
            StateHasChanged();
        }
    }

    private async Task LoadStateAsync()
    {
        var result = await ProtectedLocalStore.GetAsync<int>("count");
        currentCount = result.Success ? result.Value : 0;
    }

    private async Task IncrementCount()
    {
        currentCount++;
        await ProtectedLocalStore.SetAsync("count", currentCount);
    }
}
@using Microsoft.AspNetCore.ProtectedBrowserStorage
@inject ProtectedLocalStorage ProtectedLocalStore

@if (isConnected)
{
    <p>Current count: <strong>@currentCount</strong></p>
    <button @onclick="IncrementCount">Increment</button>
}
else
{
    <p>Loading...</p>
}

@code {
    private int currentCount = 0;
    private bool isConnected = false;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            isConnected = true;
            await LoadStateAsync();
            StateHasChanged();
        }
    }

    private async Task LoadStateAsync()
    {
        currentCount = await ProtectedLocalStore.GetAsync<int>("count");
    }

    private async Task IncrementCount()
    {
        currentCount++;
        await ProtectedLocalStore.SetAsync("count", currentCount);
    }
}

Splits toestandbeheer uit naar een gemeenschappelijke provider

Als veel onderdelen afhankelijk zijn van opslag op basis van een browser, maakt het implementeren van statusprovidercode vaak codeduplicatie. Een optie om codeduplicatie te vermijden is om een oudercomponent te maken dat de logica van de statusprovider inkapselt. Deelcomponenten kunnen werken met persistente gegevens, zonder rekening te houden met het mechanisme voor statuspersistentie.

In het volgende voorbeeld van een CounterStateProvider-onderdeel worden tellergegevens opgeslagen in sessionStorage, en wordt de laadfase verwerkt door de inhoud van het kind pas weer te geven nadat het laden van de status is voltooid.

Het CounterStateProvider onderdeel heeft betrekking op het prerenderen door de status pas te laden nadat het onderdeel wordt weergegeven in de OnAfterRenderAsync levenscyclusmethode, die niet wordt uitgevoerd tijdens het genereren.

De methode in deze sectie is niet geschikt voor het activeren van rerendering van meerdere geabonneerde onderdelen op dezelfde pagina. Als een geabonneerd onderdeel de staat verandert, wordt het opnieuw gerenderd en kan het de bijgewerkte staat weergeven. Echter, een ander onderdeel op dezelfde pagina dat die staat toont, geeft verouderde gegevens weer totdat het zichzelf opnieuw renderen. Daarom is de methode die in deze sectie wordt beschreven, het meest geschikt voor het gebruik van de status in één onderdeel op de pagina.

CounterStateProvider.razor:

@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inject ProtectedSessionStorage ProtectedSessionStore

@if (isLoaded)
{
    <CascadingValue Value="this">
        @ChildContent
    </CascadingValue>
}
else
{
    <p>Loading...</p>
}

@code {
    private bool isLoaded;

    [Parameter]
    public RenderFragment? ChildContent { get; set; }

    public int CurrentCount { get; set; }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            isLoaded = true;
            await LoadStateAsync();
            StateHasChanged();
        }
    }

    private async Task LoadStateAsync()
    {
        var result = await ProtectedSessionStore.GetAsync<int>("count");
        CurrentCount = result.Success ? result.Value : 0;
        isLoaded = true;
    }

    public async Task IncrementCount()
    {
        CurrentCount++;
        await ProtectedSessionStore.SetAsync("count", CurrentCount);
    }
}
@using Microsoft.AspNetCore.ProtectedBrowserStorage
@inject ProtectedSessionStorage ProtectedSessionStore

@if (isLoaded)
{
    <CascadingValue Value="this">
        @ChildContent
    </CascadingValue>
}
else
{
    <p>Loading...</p>
}

@code {
    private bool isLoaded;

    [Parameter]
    public RenderFragment ChildContent { get; set; }

    public int CurrentCount { get; set; }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            isLoaded = true;
            await LoadStateAsync();
            StateHasChanged();
        }
    }

    private async Task LoadStateAsync()
    {
        CurrentCount = await ProtectedSessionStore.GetAsync<int>("count");
        isLoaded = true;
    }

    public async Task IncrementCount()
    {
        CurrentCount++;
        await ProtectedSessionStore.SetAsync("count", CurrentCount);
    }
}

Opmerking

Zie Razorvoor meer informatie over .

Als u de status toegankelijk wilt maken voor alle onderdelen in een app, verpakt u het CounterStateProvider onderdeel rond de Router (<Router>...</Router>) in het Routes onderdeel met globale interactieve serverzijdeweergave (interactieve SSR).

In het onderdeel App (Components/App.razor):

<Routes @rendermode="InteractiveServer" />

In het onderdeel Routes (Components/Routes.razor):

Als u het CounterStateProvider-onderdeel wilt gebruiken, verpakt u een exemplaar van het onderdeel rond elk ander onderdeel waarvoor toegang tot de tellerstatus is vereist. Als u de status toegankelijk wilt maken voor alle onderdelen in een app, verpakt u het CounterStateProvider onderdeel rond de Router in het App-onderdeel (App.razor):

<CounterStateProvider>
    <Router ...>
        ...
    </Router>
</CounterStateProvider>

Opmerking

Met de release van .NET 5.0.1 en voor eventuele extra releases van 5.x bevat het Router onderdeel de PreferExactMatches parameter die is ingesteld op @true. Zie Migreren van ASP.NET Core 3.1 naar .NET 5 voor meer informatie.

Verpakte onderdelen ontvangen en kunnen de persistente tellerstatus wijzigen. Met het volgende Counter onderdeel wordt het patroon geïmplementeerd:

@page "/counter"

<p>Current count: <strong>@CounterStateProvider?.CurrentCount</strong></p>
<button @onclick="IncrementCount">Increment</button>

@code {
    [CascadingParameter]
    private CounterStateProvider? CounterStateProvider { get; set; }

    private async Task IncrementCount()
    {
        if (CounterStateProvider is not null)
        {
            await CounterStateProvider.IncrementCount();
        }
    }
}

Het voorgaande onderdeel is niet vereist om te communiceren met ProtectedBrowserStorage, noch wordt er een 'laadfase' uitgevoerd.

Over het algemeen wordt het patroon van de -toestandprovider bij de bovenliggende component aanbevolen:

  • Om de status over veel componenten te gebruiken.
  • Als er slechts één statusobject op het hoogste niveau is om vast te houden.

Als u veel verschillende statusobjecten wilt behouden en verschillende subsets van objecten op verschillende plaatsen wilt gebruiken, is het beter om permanente status wereldwijd te voorkomen.