【问题标题】:JWT + SignalR on ASP Core 3 resulting in 401 UnauthorizedASP Core 3 上的 JWT + SignalR 导致 401 Unauthorized
【发布时间】:2019-05-28 13:35:16
【问题描述】:

如果我在 signalr 之外使用 http 调用,例如使用 postman 或 httpclient,我可以在服务器上成功验证我的令牌。当我尝试通过我的信号器集线器连接时,令牌没有通过授权。

承载未通过身份验证。失败消息:没有可用于令牌的 SecurityTokenValidator:Bearer MyTokenFooBar

我的服务设置是:

public void ConfigureServices(IServiceCollection services)
{
    services.AddRouting();
    services.AddControllers();
    services.AddHealthChecks();
    services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(builder => { builder.ConnectionString = _configuration.GetConnectionString("DefaultConnection"); }));
    services.AddIdentity<ApplicationUser, IdentityRole>(setup =>
    {
        // foo
    }).AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();

    services.AddAuthentication(options =>
        {
            options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        })
        .AddJwtBearer(options =>
        {
            options.RequireHttpsMetadata = false;
            options.SaveToken = true;
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidIssuer = _configuration["Jwt:Issuer"],
                ValidAudience = _configuration["Jwt:Audience"],
                ValidateIssuer = false,
                ValidateAudience = false,
                ValidateIssuerSigningKey = false,
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"])),
                ValidateLifetime = false
            };

            options.Events = new JwtBearerEvents
            {
                OnMessageReceived = context =>
                {
                    var path = context.HttpContext.Request.Path;
                    if (!path.StartsWithSegments("/chat")) return Task.CompletedTask;
                    var accessToken = context.Request.Headers[HeaderNames.Authorization];
                    if (!string.IsNullOrWhiteSpace(accessToken) && context.Scheme.Name == JwtBearerDefaults.AuthenticationScheme)
                    {
                        context.Token = accessToken;
                    }

                    return Task.CompletedTask;
                }
            };
        });

    services.AddAuthorization();

    services.AddSignalR(options => { options.EnableDetailedErrors = true; });
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseRouting();
    app.UseAuthentication();
    app.UseAuthorization();
    app.UseEndpoints(options =>
    {
        options.MapHealthChecks("/health");
        options.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
    });
    app.UseSignalR(options => { options.MapHub<ChatHub>("/chat"); });
}

我对初始连接使用基本的 http auth 标头,这将使用户登录身份并生成 jwt 令牌作为响应以供将来调用。

[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> Login()
{
    var (headerUserName, headerPassword) = GetAuthLoginInformation(HttpContext);

    var signInResult = await _signInManager.PasswordSignInAsync(headerUserName, headerPassword, false, false);
    if (!signInResult.Succeeded)
    {
        return Unauthorized();
    }

    var signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("SuperTopSecretKeyThatYouDoNotGiveOutEver!"));
    var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256);
    var jwt = new JwtSecurityToken(signingCredentials: signingCredentials);
    var handler = new JwtSecurityTokenHandler();
    var token = handler.WriteToken(jwt);
    return new OkObjectResult(token);
}

我的客户端(一个控制台应用程序)已设置为缓存此令牌并在未来的信号器调用中使用它:

获取令牌:

_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(encoding.GetBytes($"{userName}:{password}")));
var response = await _client.SendAsync(request); // this goes to the login action posted above
_token = await response.Content.ReadAsStringAsync();

...

_hubConnection = new HubConnectionBuilder()
    .WithUrl(new Uri(_baseAddress, "chat"),
        options => { options.AccessTokenProvider = () => Task.FromResult(_token); }) // send the cached token back with every request
    .Build();

// here is where the error occurs. 401 unauthorized comes back from this call.
await _hubConnection.StartAsync();

【问题讨论】:

标签: asp.net asp.net-core signalr asp.net-identity


【解决方案1】:

已解决。

问题是我覆盖了JwtBearerHandlerOnMessageReceived 处理程序,然后让它自己读取传入的令牌......但是我传递给它的令牌包含前缀Bearer,当它被解析时上述处理程序与现有用户的已知令牌不匹配。

只需删除我对 OnMessageReceived 的覆盖并让 AspNetCore 对 JwtBearerHandler 的默认实现完成其工作,即可使令牌解析正常工作。

【讨论】:

  • 对于未来的人来说,如果你按照信号器的当前指令使用 app.UseEndpoints 你最终会得到 401。解决方案是现在使用 app.UseSignalR,即使它被标记为已弃用。
  • 这对我来说也适用于 app.UseSignalR,但 UseEndpoints 没有运气。作为一个在这个问题上花了几个小时的人,我想指出对 UseSignalR 的调用应该在调用 app.UseFileServer(); 之后进行。 app.UseRouting();和 app.UseAuthorization();
  • 我做了同样的事情,从 auth 标头中取出令牌并分配给 context.token。花了几天时间试图解决这个问题然后来到这里。谢谢
猜你喜欢
  • 2019-06-20
  • 2021-06-17
  • 2017-03-29
  • 2020-01-01
  • 2020-07-02
  • 2022-10-20
  • 2015-03-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多