Аутентификация Signal R показывает, что сведения о контексте имеют нулевое значение

Я пытаюсь получить своего вошедшего в систему пользователя и идентификатор соединения, чтобы сопоставить пользователя соответствующим образом.

Ниже представлен мой файл program.cs:

Ниже представлен мой ItemAccessHub:

Ниже представлен мой code Middleware:

Однако мой контекст всегда равен null:

OnDisconnected

OnConnection

Я просмотрел всю документацию Microsoft, но не смог заставить ее работать. Мне нужны данные контекстной информации (Авторизованный пользователь) для отслеживания и управления идентификатором подключения.


var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddSignalR();

// Add services to the container.
builder.Services.AddRazorComponents().AddInteractiveServerComponents();

builder.Services.AddCascadingAuthenticationState();
builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
builder.Services.AddScoped<IdentityUserAccessor>();
builder.Services.AddScoped<IdentityRedirectManager>();
builder.Services.TryAddScoped<AuthenticationStateProvider, IdentityRevalidatingAuthenticationStateProvider>();
builder.Services.AddAuthorizationCore();
builder.Services.AddSingleton<IAuthorizationMiddlewareResultHandler, AuthorizationMiddlewareResultHandler>();
builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme = IdentityConstants.ApplicationScheme;
    options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
    options.RequireAuthenticatedSignIn = true;
}).AddIdentityCookies();

//Database Connection
DatabaseConnection connectionString = new(builder.Configuration.GetConnectionString("OffDatabase") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found."));

builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(DatabaseConnection.ConnectionString), ServiceLifetime.Transient);
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddHttpContextAccessor();
builder.Services.AddIdentityCore<ApplicationUser>(options =>
{
    options.Password.RequireDigit = false;
    options.Password.RequireLowercase = false;
    options.Password.RequireUppercase = false;
    options.Password.RequiredLength = 5;
    options.Password.RequireNonAlphanumeric = false;
    options.SignIn.RequireConfirmedEmail = true;
    options.SignIn.RequireConfirmedAccount = true;
}).AddEntityFrameworkStores<ApplicationDbContext>()
  .AddSignInManager()
  .AddDefaultTokenProviders()
  .AddSignInManager<SignInManager<ApplicationUser>>();

builder.Services.AddCors();

builder.Services.ConfigureApplicationCookie(options =>
{
    options.LoginPath = "/Account/Login";
    options.LogoutPath = "/Account/Logout";
    options.AccessDeniedPath = "/Account/AccessDenied";
    options.ExpireTimeSpan = TimeSpan.FromDays(7);
    options.ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter;
    options.SlidingExpiration = true;
});

builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();
builder.WebHost.UseStaticWebAssets();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseExceptionHandler("/Error", createScopeForErrors: true);
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseStaticFiles();
app.UseRouting();
app.UseMiddleware<BlazorCookieLoginMiddleware>();
app.UseAuthentication();
app.UseAuthorization();
app.UseAntiforgery();
app.MapRazorComponents<App>()
    .AddAdditionalAssemblies(typeof(AuthenticationLibrary._Imports).Assembly)
    .AddInteractiveServerRenderMode();

app.MapAdditionalIdentityEndpoints();

app.MapControllers();
app.MapHub<ItemAccessHub>("/itemaccesshub");

app.Run();

using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.SignalR;
using System.Collections.Concurrent;
using System.Security.Claims;
using System.Web.Mvc;

namespace POSFlow_Admin.Hubs
{
    [Authorize]
    public class ItemAccessHub : Hub
    {
        private static readonly ConcurrentDictionary<string, (string UserEmail, string Module, string UserName)> RowLocks = new();
        private static readonly ConcurrentDictionary<string, string> UserConnections = new();

        public override Task OnConnectedAsync()
        {
            var userEmail = Context.User?.FindFirst(ClaimTypes.Email)?.Value;
            var userName = Context.User?.Identity?.Name; // Assuming the username is stored in the Name claim

            if (!string.IsNullOrEmpty(userEmail))
            {
                UserConnections[userEmail] = Context.ConnectionId;
            }

            return base.OnConnectedAsync();
        }

        public override Task OnDisconnectedAsync(Exception exception)
        {
            var userEmail = Context.User?.FindFirst(ClaimTypes.Email)?.Value;

            if (!string.IsNullOrEmpty(userEmail) && UserConnections.ContainsKey(userEmail))
            {
                // Remove the user's connection ID
                UserConnections.TryRemove(userEmail, out _);
                var lockedRows = RowLocks.Where(x => x.Value.UserEmail == userEmail).Select(x => x.Key).ToList();
                foreach (var rowId in lockedRows)
                {
                    RowLocks.TryRemove(rowId, out _);
                    Clients.Others.SendAsync("RowUnlocked", rowId);
                }
            }

            return base.OnDisconnectedAsync(exception);
        }

        public Task LockRow(string rowId, string module)
        {
            var userEmail = Context.User?.FindFirst(ClaimTypes.Email)?.Value;
            var userName = Context.User?.Identity?.Name;

            if (!string.IsNullOrEmpty(userEmail) && !RowLocks.ContainsKey(rowId))
            {
                RowLocks[rowId] = (userEmail, module, userName);
                return Clients.Others.SendAsync("RowLocked", rowId, userName);
            }

            return Task.CompletedTask;
        }

        public Task UnlockRow(string rowId)
        {
            var userEmail = Context.User?.FindFirst(ClaimTypes.Email)?.Value;

            if (!string.IsNullOrEmpty(userEmail) && RowLocks.ContainsKey(rowId))
            {
                if (RowLocks.TryGetValue(rowId, out var lockInfo) && lockInfo.UserEmail == userEmail)
                {
                    RowLocks.TryRemove(rowId, out _);
                    return Clients.Others.SendAsync("RowUnlocked", rowId);
                }
            }

            return Task.CompletedTask;
        }
    }
}

public class BlazorCookieLoginMiddleware
{
    public static IDictionary<Guid, LoginInfo> Logins { get; private set; }
        = new ConcurrentDictionary<Guid, LoginInfo>();


    private readonly RequestDelegate _next;

    public BlazorCookieLoginMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context, SignInManager<ApplicationUser> signInMgr)
    {
        // Skip Blazor-related paths
        if (context.Request.Path.Value.StartsWith("/_blazor") || context.Request.Path.Value.StartsWith("/_framework"))
        {
            await _next(context);
            return;
        }

        if (context.Request.Path == "/login" && context.Request.Query.ContainsKey("key"))
        {
            var key = Guid.Parse(context.Request.Query["key"]);
            var info = Logins[key];

            var result = await signInMgr.PasswordSignInAsync(info.Email, info.Password, false, lockoutOnFailure: true);
            info.Password = null;
            if (result.Succeeded)
            {
                Logins.Remove(key);
                context.Response.Redirect("/");
                return;
            }
            else if (result.RequiresTwoFactor)
            {
                //TODO: redirect to 2FA razor component
                context.Response.Redirect("/loginwith2fa/" + key);
                return;
            }
            else
            {
                //TODO: Proper error handling
                context.Response.Redirect("/loginfailed");
                return;
            }
        }
        else if (context.Request.Path == "/logout" && context.Request.Query.ContainsKey("key"))
        {
            var key = Guid.Parse(context.Request.Query["key"]);
            await signInMgr.SignOutAsync();
            Logins.Remove(key);
            context.Response.Redirect("/");
            return;

        }
        else
        {
            await _next.Invoke(context);
            return;
        }
    }
}
Ким
Вопрос задан7 августа 2024 г.

1 Ответ

2
Силантий
Ответ получен4 сентября 2024 г.

Ваш ответ

Загрузить файл.