Autenticación de Blazor Windows con notificaciones personalizadas

programación


Hola a todos,

No sé si lo estoy haciendo del todo mal, pero lo que intento lograr es alojar una aplicación de servidor Blazor en mi IIS con autenticación de Windows y una reclamación personalizada adicional para evitar que todos accedan, pero solo un puñado de personas.

Hasta ahora, llegué al código pegado a continuación y la cuestión es que funciona con mi estudio visual durante la depuración. Todo está bien y hace el trabajo como se esperaba, pero tan pronto como lo publico en el servidor IIS, restringe mi acceso.

Si alguien pudiera guiarme en la dirección correcta, estaría muy feliz.

¡Gracias de antemano!

Lo que he probado:

Código para autorizar a los usuarios (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();

Además, en la oficina de IIS, tengo WindowsAuth habilitado para este sitio y verifiqué que Negotiate Provider está en la parte superior de la lista.

Pero realmente no entiendo cuál es el problema, incluso ChatGpt me falló… o no pude hacer la pregunta correcta.

Solución 4

Gracias por esta pregunta y solución. Funciona genial. Estoy en .NET 8 y tuve que hacer un pequeño cambio en el siguiente código de

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

a

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

De lo contrario, no podrá acceder al nombre de usuario en ninguna otra parte del código.
En el siguiente código, _authMessage devuelve nulo si usamos el código original.

@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;
            }
        }
    }

Solución 1

Lo que realmente había que hacer… después de una investigación afortunada y verificar su depuración tres veces:

program.cs necesita apuntar a un autenticador personalizado con un esquema personalizado->

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

Entonces realmente necesitas una clase adicional que maneje esas cosas:

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.");
        }
    }

Y para completar, las “Opciones de autenticación de Windows personalizadas” porque también las necesitas, aunque está vacía ya que no necesito ninguna opción súper especial.

public class CustomWindowsAuthenticationOptions : AuthenticationSchemeOptions
    {
        
    }

コメント

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