ASP Core 3 上的 JWT + SignalR 导致 401 Unauthorized
JWT + SignalR on ASP Core 3 resulting in 401 Unauthorized
如果我在 signalr 之外使用 http 调用,例如使用 postman 或 httpclient,我可以在服务器上成功验证我的令牌。当我尝试通过信号中心连接时,令牌未通过授权。
Bearer was not authenticated. Failure message: No SecurityTokenValidator available for token: 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 身份验证 header 进行初始连接,这将使用户登录身份并生成一个 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();
已解决。
问题是我覆盖了 JwtBearerHandler
的 OnMessageReceived
处理程序,然后让它自己读取传入的令牌...但是我传递给它的令牌包含前缀 Bearer
,当由上述处理程序解析时,它与现有用户的已知令牌不匹配。
简单地删除我对 OnMessageReceived
的覆盖并让 AspNetCore 的默认实现 JwtBearerHandler
完成它的工作允许令牌解析正常工作。
如果我在 signalr 之外使用 http 调用,例如使用 postman 或 httpclient,我可以在服务器上成功验证我的令牌。当我尝试通过信号中心连接时,令牌未通过授权。
Bearer was not authenticated. Failure message: No SecurityTokenValidator available for token: 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 身份验证 header 进行初始连接,这将使用户登录身份并生成一个 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();
已解决。
问题是我覆盖了 JwtBearerHandler
的 OnMessageReceived
处理程序,然后让它自己读取传入的令牌...但是我传递给它的令牌包含前缀 Bearer
,当由上述处理程序解析时,它与现有用户的已知令牌不匹配。
简单地删除我对 OnMessageReceived
的覆盖并让 AspNetCore 的默认实现 JwtBearerHandler
完成它的工作允许令牌解析正常工作。