Authentification Windows Blazor avec revendications personnalisées

la programmation


Salut tout le monde,

Je ne sais pas si je me trompe complètement, mais ce que j’essaie de réaliser, c’est d’héberger une application Blazor Server sur mon IIS avec l’authentification Windows et une customClaim supplémentaire pour empêcher tout le monde d’y accéder, mais seulement une poignée de personnes.

Jusqu’à présent, je me suis dirigé vers le code collé ci-dessous, et le fait est qu’il fonctionne avec mon studio visuel lors du débogage. Tout va bien là-bas et fait le travail comme prévu, mais dès que je le publie sur le serveur IIS, cela restreint mon accès.

Si quelqu’un pouvait me guider dans la bonne direction, je serais très heureux.

Merci d’avance!

Ce que j’ai essayé :

Code pour autoriser les utilisateurs (program.cs)

using System.Security.Claims;
using System.Security.Principal;
using ImsServerMonitor.Data;
using Microsoft.AspNetCore.Authentication.Negotiate;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
    .AddNegotiate(options =>
    {
        options.Events = new NegotiateEvents
        {
            OnAuthenticated = context =>
            {
                if (context.Principal.Identity is WindowsIdentity windowsIdentity)
                {
                    string loginName = windowsIdentity.Name;
                    
                    if (loginName.Contains("User1")
                        || loginName.Contains("User2")
                        || loginName.Contains("User3")
                        || loginName.Contains("User4"))
                    {
                        var claims = new List<Claim>
                        {
                            new Claim("CustomClaim", "Admin")
                        };

                        context.Principal.AddIdentity(new ClaimsIdentity(claims));
                    }
                }

                return Task.CompletedTask;
            }
        };
    });
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AdminOnly", policyBuilder => policyBuilder.RequireClaim("CustomClaim", "Admin"));
    // By default, all incoming requests will be authorized according to the default policy.
    //options.FallbackPolicy = options.DefaultPolicy;
    options.FallbackPolicy = options.GetPolicy("AdminOnly");
});

builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddSingleton<MonitorEngine>();
builder.Services.AddDevExpressBlazor();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    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");

app.Run();

De plus, sur IIS ofc, WindowsAuth est activé pour ce site et j’ai vérifié que le fournisseur de négociation est en haut de la liste.

Mais je ne comprends vraiment pas quel est le problème, même ChatGpt m’a échoué… ou je n’ai pas réussi à poser la bonne question.

Solution 1

Ce qu’il fallait réellement faire… après une recherche chanceuse et la vérification de votre débogage trois fois :

program.cs doit pointer vers un authentificateur personnalisé avec un schéma personnalisé->

builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme = "CustomWindowsAuthentication";
}).AddScheme<CustomWindowsAuthenticationOptions, CustomWindowsAuthenticationHandler>("CustomWindowsAuthentication", null);

Ensuite, vous avez effectivement besoin d’une classe supplémentaire qui gère ce genre de choses :

public class CustomWindowsAuthenticationHandler : AuthenticationHandler<CustomWindowsAuthenticationOptions>
    {
        public CustomWindowsAuthenticationHandler(
            IOptionsMonitor<CustomWindowsAuthenticationOptions> options,
            ILoggerFactory logger,
            UrlEncoder encoder,
            ISystemClock clock)
            : base(options, logger, encoder, clock)
        {
        }

        protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
        {
            if (!Context.User.Identity.IsAuthenticated || !(Context.User.Identity is WindowsIdentity windowsIdentity))
            {
                return AuthenticateResult.NoResult();
            }

            var loginName = windowsIdentity.Name;
            if (loginName.Contains("User1")
                || loginName.Contains("User2")
                || loginName.Contains("User3")
                || loginName.Contains("User4"))
            {
                var claims = new List<Claim>
                {
                    new Claim("CustomClaim", "Admin")
                };

                var identity = new ClaimsIdentity(claims, Scheme.Name);
                var principal = new ClaimsPrincipal(identity);

                var ticket = new AuthenticationTicket(principal, Scheme.Name);
                return AuthenticateResult.Success(ticket);
            }

            return AuthenticateResult.Fail("Custom authentication failed.");
        }
    }

Et par souci d’exhaustivité, les “CustomWindowsAuthenticationOptions” car vous en avez aussi besoin, bien qu’il soit vide puisque je n’ai pas besoin d’options super spéciales.

public class CustomWindowsAuthenticationOptions : AuthenticationSchemeOptions
    {
        
    }

Solution 4

Merci pour cette question et cette solution. Fonctionne très bien. Je suis sur .NET 8 et j’ai dû apporter une petite modification au code ci-dessous à partir de

var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return AuthenticateResult.Success(ticket);

à

Context.User.AddIdentity(identity);
var ticket = new AuthenticationTicket(Context.User, Scheme.Name);
return AuthenticateResult.Success(ticket);

Sinon, vous ne pouvez pas accéder au nom d’utilisateur ailleurs dans le code.
Dans le code ci-dessous, _authMessage renvoie null si nous utilisons le code d’origine.

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

    protected override async Task OnInitializedAsync()
    {
        if (AuthenticationState is not null)
        {
            var authState = await AuthenticationState;
            var user = authState.User;

            if (user?.Identity is not null && user.Identity.IsAuthenticated)
            {
                _authMessage = user.Identity.Name;
            }
        }
    }

コメント

タイトルとURLをコピーしました