Sunucu tarafı ASP.NET Core Blazor ek güvenlik senaryoları

Not

Bu, bu makalenin en son sürümü değildir. Geçerli sürüm için bu makalenin .NET 8 sürümüne bakın.

Önemli

Bu bilgiler, ticari olarak piyasaya sürülmeden önce önemli ölçüde değiştirilebilen bir yayın öncesi ürünle ilgilidir. Burada verilen bilgilerle ilgili olarak Microsoft açık veya zımni hiçbir garanti vermez.

Geçerli sürüm için bu makalenin .NET 8 sürümüne bakın.

Bu makalede, bir uygulamaya belirteç Blazor geçirme de dahil olmak üzere ek güvenlik senaryoları için sunucu tarafı Blazor yapılandırması açıklanmaktadır.

Not

Bu makaledeki kod örnekleri, .NET 6 veya sonraki sürümlerindeki ASP.NET Core'da desteklenen null atanabilir başvuru türlerini (NTS) ve .NET derleyici null durum statik analizini benimser. Core 5.0 veya önceki ASP.NET hedeflerken, makalenin örneklerindeki , , ve türlerinden string?null tür atamasını (?) kaldırın.IEnumerable<GitHubBranch>?WeatherForecast[]?TodoItem[]?

Belirteçleri sunucu tarafı Blazor uygulamasına geçirme

Web Apps için Blazor bu bölümün güncelleştirilmesi, Web Apps'te Blazor belirteç geçirmeyle ilgili Güncelleştirme bölümünü bekliyor (dotnet/AspNetCore.Docs #31691). Daha fazla bilgi için bkz . Etkileşimli Sunucu modunda HttpClient'a Erişim Belirteci sağlama sorunu (dotnet/aspnetcore #52390).

içinBlazor Server, bu makale bölümünün 7.0 sürümünü görüntüleyin.

Sunucu tarafı Blazor uygulamasındaki bileşenlerin dışında Razor bulunan belirteçler, bu bölümde açıklanan yaklaşımla bileşenlere geçirilebilir. Bu bölümdeki örnek, uygulamaya erişim, yenileme ve istek sahteciliği önleme (XSRF) belirteci belirteçlerinin geçirilmesine Blazor odaklanır, ancak yaklaşım diğer HTTP bağlam durumu için geçerlidir.

Not

XSRF belirtecinin bileşenlere Razor geçirilmesi, bileşenlerin POST'un doğrulama gerektiren veya diğer uç noktalara Identity iletildiği senaryolarda kullanışlıdır. Uygulamanız yalnızca erişim ve yenileme belirteçleri gerektiriyorsa, aşağıdaki örnekten XSRF belirteç kodunu kaldırabilirsiniz.

Normal Razor Sayfalar veya MVC uygulamasıyla yaptığınız gibi uygulamanın kimliğini doğrula. Belirteçleri sağlayın ve kimlik doğrulamasına cookiekaydedin.

Program dosyasında:

using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;

...

builder.Services.Configure<OpenIdConnectOptions>(
    OpenIdConnectDefaults.AuthenticationScheme, options =>
{
    options.ResponseType = OpenIdConnectResponseType.Code;
    options.SaveTokens = true;
    options.Scope.Add(OpenIdConnectScope.OfflineAccess);
});

Startup.cs içinde:

using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;

...

services.Configure<OpenIdConnectOptions>(
    OpenIdConnectDefaults.AuthenticationScheme, options =>
{
    options.ResponseType = OpenIdConnectResponseType.Code;
    options.SaveTokens = true;
    options.Scope.Add(OpenIdConnectScope.OfflineAccess);
});

Startup.cs içinde:

using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;

...

services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
    options.ResponseType = OpenIdConnectResponseType.Code;
    options.SaveTokens = true;
    options.Scope.Add(OpenIdConnectScope.OfflineAccess);
});

İsteğe bağlı olarak, ek kapsamlar ile options.Scope.Add("{SCOPE}");eklenir; burada yer tutucu {SCOPE} ek eklenecek ek kapsamdır.

Bağımlılık ekleme (DI) belirteçlerini çözümlemek için uygulama içinde Blazor kullanılabilecek kapsamlı bir belirteç sağlayıcısı hizmeti tanımlayın.

TokenProvider.cs:

public class TokenProvider
{
    public string? AccessToken { get; set; }
    public string? RefreshToken { get; set; }
    public string? XsrfToken { get; set; }
}

Program dosyasına şu hizmetler için hizmetler ekleyin:

  • IHttpClientFactory: Erişim belirteci olan bir WeatherForecastService sunucu API'sinden hava durumu verileri alan bir sınıfta kullanılır.
  • TokenProvider: Erişim ve yenileme belirteçlerini tutar.
builder.Services.AddHttpClient();
builder.Services.AddScoped<TokenProvider>();

içinde Startup.ConfigureServicesStartup.cs, için hizmetler ekleyin:

  • IHttpClientFactory: Erişim belirteci olan bir WeatherForecastService sunucu API'sinden hava durumu verileri alan bir sınıfta kullanılır.
  • TokenProvider: Erişim ve yenileme belirteçlerini tutar.
services.AddHttpClient();
services.AddScoped<TokenProvider>();

Erişim ve yenileme belirteçleriyle ilk uygulama durumunu geçirmek için bir sınıf tanımlayın.

InitialApplicationState.cs:

public class InitialApplicationState
{
    public string? AccessToken { get; set; }
    public string? RefreshToken { get; set; }
    public string? XsrfToken { get; set; }
}

Pages/_Host.cshtml dosyasında öğesini oluşturun ve örneğini InitialApplicationState oluşturun ve bunu uygulamaya parametre olarak geçirin:

Pages/_Layout.cshtml dosyasında öğesini oluşturun ve örneğini InitialApplicationState oluşturun ve bunu uygulamaya parametre olarak geçirin:

Pages/_Host.cshtml dosyasında öğesini oluşturun ve örneğini InitialApplicationState oluşturun ve bunu uygulamaya parametre olarak geçirin:

@using Microsoft.AspNetCore.Authentication
@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf

...

@{
    var tokens = new InitialApplicationState
    {
        AccessToken = await HttpContext.GetTokenAsync("access_token"),
        RefreshToken = await HttpContext.GetTokenAsync("refresh_token"),
        XsrfToken = Xsrf.GetAndStoreTokens(HttpContext).RequestToken
    };
}

<component ... param-InitialState="tokens" ... />

() bileşeninde AppApp.razorhizmeti çözümleyin ve parametresindeki verilerle başlatın:

@inject TokenProvider TokenProvider

...

@code {
    [Parameter]
    public InitialApplicationState? InitialState { get; set; }

    protected override Task OnInitializedAsync()
    {
        TokenProvider.AccessToken = InitialState?.AccessToken;
        TokenProvider.RefreshToken = InitialState?.RefreshToken;
        TokenProvider.XsrfToken = InitialState?.XsrfToken;

        return base.OnInitializedAsync();
    }
}

Not

Önceki örnekte'ye TokenProvider ilk durumu atamanın alternatifi, verileri uygulama genelinde kullanılmak üzere içindeki OnInitializedAsync kapsamlı bir hizmete kopyalamaktır.

NuGet paketi için Microsoft.AspNet.WebApi.Client uygulamaya bir paket başvurusu ekleyin.

Not

.NET uygulamalarına paket ekleme hakkında yönergeler için, Paket tüketimi iş akışında (NuGet belgeleri)paketleri yüklemek ve yönetmek altındaki makalelere bakın. NuGet.org'da doğru paket sürümlerini onaylayın.

Güvenli API isteğinde bulunan hizmete belirteç sağlayıcısını ekleyin ve API isteği için belirteci alın:

WeatherForecastService.cs:

using System;
using System.Net.Http;
using System.Threading.Tasks;

public class WeatherForecastService
{
    private readonly HttpClient http;
    private readonly TokenProvider tokenProvider;

    public WeatherForecastService(IHttpClientFactory clientFactory, 
        TokenProvider tokenProvider)
    {
        http = clientFactory.CreateClient();
        this.tokenProvider = tokenProvider;
    }

    public async Task<WeatherForecast[]> GetForecastAsync()
    {
        var token = tokenProvider.AccessToken;
        var request = new HttpRequestMessage(HttpMethod.Get, 
            "https://localhost:5003/WeatherForecast");
        request.Headers.Add("Authorization", $"Bearer {token}");
        var response = await http.SendAsync(request);
        response.EnsureSuccessStatusCode();

        return await response.Content.ReadFromJsonAsync<WeatherForecast[]>() ?? 
            Array.Empty<WeatherForecast>();
    }
}

Bir bileşene geçirilen bir XSRF belirteci için öğesini ekleyin TokenProvider ve POST isteğine XSRF belirtecini ekleyin. Aşağıdaki örnek belirteci bir oturumu kapatma uç noktası POST'a ekler. Aşağıdaki örnek için senaryo, korunması gereken normal bir oturumu kapatma işlemine ek olarak bazı eylemler gerçekleştirdiğinden, oturumu kapatma uç noktasının (Areas/Identity/Pages/Account/Logout.cshtmluygulamaya iskelesi oluşturulmuş) bir IgnoreAntiforgeryTokenAttribute (@attribute [IgnoreAntiforgeryToken]) belirtmemesidir. Uç nokta, isteği başarıyla işlemek için geçerli bir XSRF belirteci gerektirir.

Yetkili kullanıcılara Oturumu Kapat düğmesi sunan bir bileşende:

@inject TokenProvider TokenProvider

...

<AuthorizeView>
    <Authorized>
        <form action="/Identity/Account/Logout?returnUrl=%2F" method="post">
            <button class="nav-link btn btn-link" type="submit">Logout</button>
            <input name="__RequestVerificationToken" type="hidden" 
                value="@TokenProvider.XsrfToken">
        </form>
    </Authorized>
    <NotAuthorized>
        ...
    </NotAuthorized>
</AuthorizeView>

Kimlik doğrulama düzenini ayarlama

Birden fazla Kimlik Doğrulama Ara Yazılımı kullanan ve dolayısıyla birden fazla kimlik doğrulama şemasına sahip olan bir uygulama için, kullanan Blazor düzen açıkça dosyanın uç nokta yapılandırmasında Program ayarlanabilir. Aşağıdaki örnek OpenID Bağlan (OIDC) düzenini ayarlar:

Birden fazla Kimlik Doğrulama Ara Yazılımı kullanan ve bu nedenle birden fazla kimlik doğrulama şemasına sahip olan bir uygulama için, kullanan Blazor düzen açıkça uç nokta yapılandırmasında Startup.csayarlanabilir. Aşağıdaki örnek OpenID Bağlan (OIDC) düzenini ayarlar:

using Microsoft.AspNetCore.Authentication.OpenIdConnect;

...

app.MapRazorComponents<App>().RequireAuthorization(
    new AuthorizeAttribute
    {
        AuthenticationSchemes = OpenIdConnectDefaults.AuthenticationScheme
    })
    .AddInteractiveServerRenderMode();
using Microsoft.AspNetCore.Authentication.OpenIdConnect;

...

app.MapBlazorHub().RequireAuthorization(
    new AuthorizeAttribute 
    {
        AuthenticationSchemes = OpenIdConnectDefaults.AuthenticationScheme
    });

Birden fazla Kimlik Doğrulama Ara Yazılımı kullanan ve bu nedenle birden fazla kimlik doğrulama şemasına sahip olan bir uygulama için, kullanan Blazor düzen açıkça uç nokta yapılandırmasında Startup.Configureayarlanabilir. Aşağıdaki örnek, Microsoft Entra ID düzenini ayarlar:

endpoints.MapBlazorHub().RequireAuthorization(
    new AuthorizeAttribute 
    {
        AuthenticationSchemes = AzureADDefaults.AuthenticationScheme
    });

OpenID Bağlan (OIDC) v2.0 uç noktalarını kullanma

ASP.NET Core'un 5.0 öncesi sürümlerinde, kimlik doğrulama kitaplığı ve Blazor şablonları OpenID Bağlan (OIDC) v1.0 uç noktalarını kullanır. v2.0 uç noktasını 5.0'ın öncesinde ASP.NET Core sürümleriyle kullanmak için içindeki OpenIdConnectOptionsseçeneğini yapılandırınOpenIdConnectOptions.Authority:

services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, 
    options =>
    {
        options.Authority += "/v2.0";
    }

Alternatif olarak, ayar uygulama ayarları (appsettings.json) dosyasında yapılabilir:

{
  "AzureAd": {
    "Authority": "https://login.microsoftonline.com/common/oauth2/v2.0/",
    ...
  }
}

Bir segmenti yetkiliye eklemek, uygulamanın OIDC sağlayıcısı için uygun değilse (me kimliği olmayan sağlayıcılarda olduğu gibi) özelliği doğrudan ayarlayın Authority . özelliğini OpenIdConnectOptions veya uygulama ayarları dosyasında anahtarıyla Authority ayarlayın.

Kod değişiklikleri

  • Kimlik belirtecindeki taleplerin listesi v2.0 uç noktaları için değişir. Değişikliklerle ilgili Microsoft belgeleri kullanımdan kaldırılmıştır, ancak kimlik belirtecindeki taleplere ilişkin yönergeler kimlik belirteci talep başvurusunda sağlanır.

  • Kaynaklar v2.0 uç noktaları için kapsam URI'lerinde belirtildiğinden içindeki özellik ayarını OpenIdConnectOptionskaldırınOpenIdConnectOptions.Resource:

    services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options => 
        {
            ...
            options.Resource = "...";    // REMOVE THIS LINE
            ...
        }
    

Uygulama Kimliği URI'si

  • v2.0 uç noktalarını kullanırken, API'ler API için benzersiz bir tanımlayıcıyı temsil eden bir App ID URItanımlar.
  • Tüm kapsamlar ön ek olarak Uygulama Kimliği URI'sini içerir ve v2.0 uç noktaları hedef kitle olarak Uygulama Kimliği URI'si ile erişim belirteçleri yayar.
  • V2.0 uç noktalarını kullanırken, Sunucu API'sinde yapılandırılan istemci kimliği API Uygulama Kimliği'nden (İstemci Kimliği) Uygulama Kimliği URI'sine dönüşür.

appsettings.json:

{
  "AzureAd": {
    ...
    "ClientId": "https://{TENANT}.onmicrosoft.com/{PROJECT NAME}"
    ...
  }
}

Kullanılacak Uygulama Kimliği URI'sini OIDC sağlayıcısı uygulama kayıt açıklamasında bulabilirsiniz.

Özel hizmetler için kullanıcıları yakalamak için devre işleyicisi

CircuitHandler'den AuthenticationStateProvider bir kullanıcı yakalamak ve kullanıcıyı bir hizmette ayarlamak için kullanın. Kullanıcıyı güncelleştirmek istiyorsanız, yeni kullanıcıyı almak ve hizmeti güncelleştirmek için öğesine Task bir geri AuthenticationStateChanged çağırma kaydedin ve kuyruğa alın. Aşağıdaki örnekte yaklaşımı gösterilmektedir.

Aşağıdaki örnekte:

  • OnConnectionUpAsync , bağlantı hattı her yeniden bağlandığında çağrılır ve kullanıcı bağlantının ömrü boyunca ayarlanır. OnConnectionUpAsync Kimlik doğrulama değişiklikleri için bir işleyici aracılığıyla güncelleştirmeler uygulamadığınız sürece (AuthenticationChangedaşağıdaki örnekte) yalnızca yöntemi gereklidir.
  • OnCircuitOpenedAsync , kullanıcıyı güncelleştirmek için kimlik doğrulaması değiştirilmiş işleyicisini AuthenticationChangedeklemek için çağrılır.
  • Görev catch bloğu UpdateAuthentication , özel durumlarda hiçbir eylem gerçekleştirmez çünkü bu noktada bunları kod yürütmede bildirmenin bir yolu yoktur. Görevden bir özel durum oluşturulursa, özel durum uygulamanın başka bir yerinde bildirilir.

UserService.cs:

using System.Security.Claims;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server.Circuits;

public class UserService
{
    private ClaimsPrincipal currentUser = new(new ClaimsIdentity());

    public ClaimsPrincipal GetUser()
    {
        return currentUser;
    }

    internal void SetUser(ClaimsPrincipal user)
    {
        if (currentUser != user)
        {
            currentUser = user;
        }
    }
}

internal sealed class UserCircuitHandler : CircuitHandler, IDisposable
{
    private readonly AuthenticationStateProvider authenticationStateProvider;
    private readonly UserService userService;

    public UserCircuitHandler(
        AuthenticationStateProvider authenticationStateProvider,
        UserService userService)
    {
        this.authenticationStateProvider = authenticationStateProvider;
        this.userService = userService;
    }

    public override Task OnCircuitOpenedAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        authenticationStateProvider.AuthenticationStateChanged += 
            AuthenticationChanged;

        return base.OnCircuitOpenedAsync(circuit, cancellationToken);
    }

    private void AuthenticationChanged(Task<AuthenticationState> task)
    {
        _ = UpdateAuthentication(task);

        async Task UpdateAuthentication(Task<AuthenticationState> task)
        {
            try
            {
                var state = await task;
                userService.SetUser(state.User);
            }
            catch
            {
            }
        }
    }

    public override async Task OnConnectionUpAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        var state = await authenticationStateProvider.GetAuthenticationStateAsync();
        userService.SetUser(state.User);
    }

    public void Dispose()
    {
        authenticationStateProvider.AuthenticationStateChanged -= 
            AuthenticationChanged;
    }
}
using System.Security.Claims;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server.Circuits;

public class UserService
{
    private ClaimsPrincipal currentUser = new ClaimsPrincipal(new ClaimsIdentity());

    public ClaimsPrincipal GetUser()
    {
        return currentUser;
    }

    internal void SetUser(ClaimsPrincipal user)
    {
        if (currentUser != user)
        {
            currentUser = user;
        }
    }
}

internal sealed class UserCircuitHandler : CircuitHandler, IDisposable
{
    private readonly AuthenticationStateProvider authenticationStateProvider;
    private readonly UserService userService;

    public UserCircuitHandler(
        AuthenticationStateProvider authenticationStateProvider,
        UserService userService)
    {
        this.authenticationStateProvider = authenticationStateProvider;
        this.userService = userService;
    }

    public override Task OnCircuitOpenedAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        authenticationStateProvider.AuthenticationStateChanged += 
            AuthenticationChanged;

        return base.OnCircuitOpenedAsync(circuit, cancellationToken);
    }

    private void AuthenticationChanged(Task<AuthenticationState> task)
    {
        _ = UpdateAuthentication(task);

        async Task UpdateAuthentication(Task<AuthenticationState> task)
        {
            try
            {
                var state = await task;
                userService.SetUser(state.User);
            }
            catch
            {
            }
        }
    }

    public override async Task OnConnectionUpAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        var state = await authenticationStateProvider.GetAuthenticationStateAsync();
        userService.SetUser(state.User);
    }

    public void Dispose()
    {
        authenticationStateProvider.AuthenticationStateChanged -= 
            AuthenticationChanged;
    }
}

Program dosyasında:

using Microsoft.AspNetCore.Components.Server.Circuits;
using Microsoft.Extensions.DependencyInjection.Extensions;

...

builder.Services.AddScoped<UserService>();
builder.Services.TryAddEnumerable(
    ServiceDescriptor.Scoped<CircuitHandler, UserCircuitHandler>());

Startup.csiçindeStartup.ConfigureServices:

using Microsoft.AspNetCore.Components.Server.Circuits;
using Microsoft.Extensions.DependencyInjection.Extensions;

...

services.AddScoped<UserService>();
services.TryAddEnumerable(
    ServiceDescriptor.Scoped<CircuitHandler, UserCircuitHandler>());

Kullanıcıyı edinmek için bir bileşendeki hizmeti kullanın:

@inject UserService UserService

<h1>Hello, @(UserService.GetUser().Identity?.Name ?? "world")!</h1>

Kullanıcıyı MVC, Razor Sayfalar ve diğer ASP.NET Core senaryolarında ara yazılımda ayarlamak için, Kimlik Doğrulama Ara Yazılımı çalıştırıldıktan sonra özel ara yazılımda öğesini çağırın SetUserUserService veya kullanıcıyı bir IClaimsTransformation uygulamayla ayarlayın. Aşağıdaki örnek ara yazılım yaklaşımını benimser.

UserServiceMiddleware.cs:

public class UserServiceMiddleware
{
    private readonly RequestDelegate next;

    public UserServiceMiddleware(RequestDelegate next)
    {
        this.next = next ?? throw new ArgumentNullException(nameof(next));
    }

    public async Task InvokeAsync(HttpContext context, UserService service)
    {
        service.SetUser(context.User);
        await next(context);
    }
}

dosyasında çağrısından app.MapRazorComponents<App>()Program hemen önce ara yazılımı çağırın:

dosyasında çağrısından app.MapBlazorHub()Program hemen önce ara yazılımı çağırın:

çağrısından app.MapBlazorHub()Startup.ConfigureStartup.cshemen önce ara yazılımı çağırın:

app.UseMiddleware<UserServiceMiddleware>();

Giden istek ara yazılımında erişim AuthenticationStateProvider

AuthenticationStateProvider ile IHttpClientFactory oluşturulan için DelegatingHandlerHttpClient öğesine giden istek ara yazılımında bağlantı hattı etkinlik işleyicisi kullanılarak erişilebilir.

Not

ASP.NET Core uygulamalarında kullanılarak IHttpClientFactory oluşturulan örneklere göre HttpClient HTTP istekleri için temsilci işleyicileri tanımlama hakkında genel yönergeler için, ASP.NET Core'da IHttpClientFactory kullanarak HTTP istekleri oluşturma'nın aşağıdaki bölümlerine bakın:

Aşağıdaki örnek, giden isteklere kimliği doğrulanmış kullanıcılar için özel bir kullanıcı adı üst bilgisi eklemek için kullanır AuthenticationStateProvider .

İlk olarak, bağımlılık ekleme (DI) makalesinin Blazor aşağıdaki bölümünde sınıfını uygulayınCircuitServicesAccessor:

Farklı bir DI kapsamından sunucu tarafı Blazor hizmetlerine erişme

CircuitServicesAccessor uygulamasında öğesine erişmek AuthenticationStateProviderDelegatingHandler için öğesini kullanın.

AuthenticationStateHandler.cs:

public class AuthenticationStateHandler : DelegatingHandler
{
    readonly CircuitServicesAccessor circuitServicesAccessor;

    public AuthenticationStateHandler(
        CircuitServicesAccessor circuitServicesAccessor)
    {
        this.circuitServicesAccessor = circuitServicesAccessor;
    }

    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var authStateProvider = circuitServicesAccessor.Services
            .GetRequiredService<AuthenticationStateProvider>();
        var authState = await authStateProvider.GetAuthenticationStateAsync();
        var user = authState.User;

        if (user.Identity is not null && user.Identity.IsAuthenticated)
        {
            request.Headers.Add("X-USER-IDENTITY-NAME", user.Identity.Name);
        }

        return await base.SendAsync(request, cancellationToken);
    }
}

Program dosyasında öğesini kaydedin AuthenticationStateHandler ve işleyicisini örnek oluşturan HttpClient öğesine IHttpClientFactory ekleyin:

builder.Services.AddTransient<AuthenticationStateHandler>();

builder.Services.AddHttpClient("HttpMessageHandler")
    .AddHttpMessageHandler<AuthenticationStateHandler>();