集线器方法中的异步登录

时间:2019-12-09 22:49:32

标签: c# asp.net-core .net-core asp.net-core-signalr

让我们使用netcoreapp3.1从Visual Studio模板中获取Web应用程序。 它使用asp网络身份,例如单击“登录”按钮后,页面会刷新。

我想要实现的是拥有这样的SignalR Core集线器方法

 [HttpGet]
 [AllowAnonymous]
 [ValidateAntiForgeryToken]
 public async Task<bool> Login(string email, string password)
 {
     var result = await _signInManager.PasswordSignInAsync(email,
                    password, true, lockoutOnFailure: false).ConfigureAwait(false);

      if (result.Succeeded)
      {
         return true;
      }
 ....
 ....
 }

很不幸,由于我的幼稚尝试,我会得到InvalidOperationException: Headers are read-only, response has already started. 堆栈跟踪以可怕的长结尾

   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpHeaders.ThrowHeadersReadOnlyException()
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpHeaders.Microsoft.AspNetCore.Http.IHeaderDictionary.set_Item(String key, StringValues value)
   at Microsoft.AspNetCore.Http.ResponseCookies.Append(String key, String value, CookieOptions options)
   at Microsoft.AspNetCore.CookiePolicy.ResponseCookiesWrapper.Append(String key, String value, CookieOptions options)
   at Microsoft.AspNetCore.Authentication.Cookies.ChunkingCookieManager.AppendResponseCookie(HttpContext context, String key, String value, CookieOptions options)
   at Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler.<HandleSignInAsync>d__25.MoveNext()

我发现,在类似的用例中,与HttpContext进行交互是很常见的,但是由于ApplicationSignInManager似乎相对独立,因此我无法找到如何在这种情况下发挥作用的方法。

我意识到从概念的角度来看,我很可能会遗漏一些东西,因此欢迎大家提供有关如何更接近预期目标的想法。

这里github issue似乎要进行描述,所以我可能需要考虑重新设计。

2 个答案:

答案 0 :(得分:2)

如果切换到Bearer Token身份验证,则可以实现无控制器模型。

以下所有示例和代码均来自Authentication and authorization in ASP.NET Core SignalR

打字稿连接

// Connect, using the token we got.
this.connection = new signalR.HubConnectionBuilder()
    .withUrl("/hubs/chat", { accessTokenFactory: () => this.loginToken })
    .build();

C#集线器生成器

var connection = new HubConnectionBuilder()
    .WithUrl("https://example.com/myhub", options =>
    { 
        options.AccessTokenProvider = () => Task.FromResult(_myAccessToken);
    })
    .Build();
  
    

您提供的访问令牌功能在SignalR发出的每个HTTP请求之前被调用。如果您需要更新令牌以保持连接处于活动状态(因为它可能在连接期间过期),请从此函数内进行操作并返回更新的令牌。

  
     

在标准Web API中,承载令牌在HTTP标头中发送。但是,使用某些传输时,SignalR无法在浏览器中设置这些标头。使用WebSockets和服务器发送的事件时,令牌将作为查询字符串参数传输。要在服务器上支持此功能,需要其他配置:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

    services.AddIdentity<ApplicationUser, IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();

    services.AddAuthentication(options =>
        {
            // Identity made Cookie authentication the default.
            // However, we want JWT Bearer Auth to be the default.
            options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        })
        .AddJwtBearer(options =>
        {
            // Configure the Authority to the expected value for your authentication provider
            // This ensures the token is appropriately validated
            options.Authority = /* TODO: Insert Authority URL here */;

            // We have to hook the OnMessageReceived event in order to
            // allow the JWT authentication handler to read the access
            // token from the query string when a WebSocket or 
            // Server-Sent Events request comes in.

            // Sending the access token in the query string is required due to
            // a limitation in Browser APIs. We restrict it to only calls to the
            // SignalR hub in this code.
            // See https://docs.microsoft.com/aspnet/core/signalr/security#access-token-logging
            // for more information about security considerations when using
            // the query string to transmit the access token.
            options.Events = new JwtBearerEvents
            {
                OnMessageReceived = context =>
                {
                    var accessToken = context.Request.Query["access_token"];

                    // If the request is for our hub...
                    var path = context.HttpContext.Request.Path;
                    if (!string.IsNullOrEmpty(accessToken) &&
                        (path.StartsWithSegments("/hubs/chat")))
                    {
                        // Read the token out of the query string
                        context.Token = accessToken;
                    }
                    return Task.CompletedTask;
                }
            };
        });

    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

    services.AddSignalR();

    // Change to use Name as the user identifier for SignalR
    // WARNING: This requires that the source of your JWT token 
    // ensures that the Name claim is unique!
    // If the Name claim isn't unique, users could receive messages 
    // intended for a different user!
    services.AddSingleton<IUserIdProvider, NameUserIdProvider>();

    // Change to use email as the user identifier for SignalR
    // services.AddSingleton<IUserIdProvider, EmailBasedUserIdProvider>();

    // WARNING: use *either* the NameUserIdProvider *or* the 
    // EmailBasedUserIdProvider, but do not use both. 
}

答案 1 :(得分:0)

似乎尝试在hub方法内执行登录没有任何意义。 将Controller添加到项目中并在其中执行登录操作比在SignalR Hub中执行更为方便。

我的动机是避免在项目内部使用Controller,因为不需要它们进行适当的应用程序设计,但这不能弥补随之而来的困难。

仅遵循this link并使用fetch(...)就实现了我的目标。