ASP.Net 核心认证 SignalR 和 Cors

ASP.Net Core Authentication SignalR and Cors

在我当前的设置中,我在与 ASP.net 核心服务器不同的机器上使用 SPA (ReactJS) 运行。除身份验证外,整个客户端-服务器通信都通过 SignalR 运行。为了安全起见,我想使用基于 cookie 的身份验证。作为用户管理,我正在使用 ASP.net.Core.Identity。 设置 CORS 有效,现在我正在实施基于简单 HTTP 请求的 login/logout 机制。

这里是工作流程:

  1. 通过简单的 HTTP 向服务器发送登录请求。 (Works)
  2. 登录成功后在客户端设置cookie。 (Works)
  3. 客户端启动 SignalR 集线器连接(使用提供的 cookie)。 (Works)
  4. 客户端通过HubConnetion向服务器发送数据。 (Works)
  5. 服务器根据Claimspricipal发送数据。 (Fails)

成功验证后,客户端会收到 cookie 并在与 signalR-hubs 协商时使用它。调用其他控制器端点时,我可以访问之前经过身份验证的 ClaimsPrincipal。

现在我的问题是:访问 HubCallerContext 时未设置 ClaimsPrincipal(未设置身份或声明)。我是否也需要在此上下文中注册 ClaimsPrincipal,还是由 ASP.net 处理?有关身份验证的其他示例假设调用方和集线器在同一台服务器上 运行。我错过或误解了什么吗?

这是我的 Startup.cs:

public void ConfigureServices(IServiceCollection services)
{        
    services.AddScoped<SignInManager<ApplicationUser>, ApplicationSignInManager>();
    services.AddScoped<IAuthorizationHandler, MyRequirementHandler>();
    services.AddCors(
                options => options.AddPolicy("CorsPolicy",
                    builder =>
                    {
                        builder
                            .SetIsOriginAllowed(origin =>
                            {
                                if (string.IsNullOrEmpty(origin)) return false;
                                if (origin.ToLower().StartsWith("http://localhost")) return true;
                                return false;
                            })
                            .AllowAnyHeader()
                            .AllowCredentials();
                    })
            );
    services.AddIdentity<ApplicationUser, ApplicationRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();
    services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme);
    services.AddAuthorization(options =>
        {
            options.AddPolicy("PolicyName",
                policy => { policy.Requirements.Add(new MyRequirement()); });
        });

    services.ConfigureApplicationCookie(config =>
    {
        config.Cookie.HttpOnly = false;
        config.Cookie.Name = "my-fav-cookie";
        config.ExpireTimeSpan = TimeSpan.FromDays(14);
        config.Cookie.SameSite = SameSiteMode.None;
        config.Cookie.SecurePolicy = CookieSecurePolicy.Always;
        config.SlidingExpiration = false;
    });

    services.AddSignalR(opt => { opt.EnableDetailedErrors = true; })
        .AddMessagePackProtocol(option => { option.SerializerOptions.WithResolver(StandardResolver.Instance); })
        .AddNewtonsoftJsonProtocol(option =>
        {
            option.PayloadSerializerSettings.DateFormatHandling = DateFormatHandling.IsoDateFormat;
            option.PayloadSerializerSettings.DateParseHandling = DateParseHandling.DateTime;
        });
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerManager logger,
    ILoggerFactory loggerFactory)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseCors("CorsPolicy");
    app.UseRouting();

    app.UseHttpsRedirection();
    app.UseAuthentication();
    app.UseAuthorization();
    app.UseEndpoints(endpoints =>
    {
        ...some Hubendpoints...
    });
}

这里是MyRequirement.cs:

public class MyRequirement : IAuthorizationRequirement
{
}

这里是 MyRequirementHandler.cs:

public class
        MyRequirementHandler: AuthorizationHandler<MyRequirement,
            HubInvocationContext>
    {
        private readonly IAuthorizationHelper _authorizationHelper;
        private readonly UserManager<ApplicationUser> _userManager;

        public MyRequirementHandler(
            IAuthorizationHelper authorizationHelper,
            UserManager<ApplicationUser> userManager)
        {
            _authorizationHelper = authorizationHelper;
            _userManager = userManager;
        }

        protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context,
            MyRequirement requirement,
            HubInvocationContext resource)
        {
           var user = context.User;
           ...
        }
    }

Hub.cs:

public class Hub: Hub<ISystemClient>
{
    private readonly ISystemService _systemService;


    public Hub(ISystemService systemService)
    {
        _systemService = systemService;
    }

    [Authorize("PolicyName")]
    public async Task DoSthFancy()
    {
        var user = Context.User;
        var fancyStruff = await _systemService.GetFancyStuff(user);
        await Clients.Caller.SendFancyStuff(fancyStuff);
    }
}

感谢您的帮助!

编辑 (04-11-2022) 由于错误的复制粘贴,我 edited/fixed Startup.cs 的代码片段。谢谢指点。

在逐步实施示例后,我发现 ASP 仅当您通过添加 [Authorization] 挑战 hub-endpoint 或集线器本身时才在 HubContext 中设置 ClaimsPrincipal属性。所以缺少的是 AuthenticationHandler 的实现,配置中的注册和设置 authorization-attribute.

public class CustomAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
    {
        private readonly UserManager<ApplicationUser> _userManager;
        private readonly SignInManager<ApplicationUser> _signInManager;

        public CustomAuthenticationHandler(
            IOptionsMonitor<AuthenticationSchemeOptions> options,
            ILoggerFactory logger,
            UrlEncoder encoder,
            ISystemClock clock,
            UserManager<ApplicationUser> userManager,
            SignInManager<ApplicationUser> signInManager) : base(options, logger, encoder, clock)
        {
            _userManager = userManager;
            _signInManager = signInManager;
        }

        protected override Task<AuthenticateResult> HandleAuthenticateAsync()
        {
            if (Context.Request.Cookies.TryGetValue("my-fav-cookie", out var cookie))
            {
                var user = Context.Request.HttpContext.User;

                var ticket = new AuthenticationTicket(user, new(), CustomCookieScheme);
                return Task.FromResult(AuthenticateResult.Success(ticket));
            }

            return Task.FromResult(AuthenticateResult.Fail("my-fav-cookie"));
        }
    }

在Startup.cs中您需要注册身份验证:

     services.AddAuthentication().AddScheme<AuthenticationSchemeOptions, CustomAuthenticationHandler>(
                "MyCustomScheme", _ => { });

在中心:

namespace My.HubProject {

    [Authorize(AuthenticationScheme="MyCustomScheme")]
    public class MyHub : Hub {
        ...
    }
}

希望对大家有所帮助!