
1. 設定 Server 可透過 URL 查詢參數來驗證 JWT Token
當 SignalR 使用 WebSockets 和 Server-Sent Events 的傳輸型態時,SignalR 會以 URL 參數的方式來將 Token 傳遞給伺服器,因此,我們要先在專案的 Startup.cs 的 ConfigureServices
方法裡加上以下程式碼(這邊只列出主要的參數設定)
public void ConfigureServices(IServiceCollection services)
{
/* 省略不相關的程式碼 */
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
// NameClaimType 和 RoleClaimType 需與建立 Token 中的 ClaimType 一致,否則會造成 User.Identity.Name(或Roles)為 null
NameClaimType = ClaimTypes.NameIdentifier,
RoleClaimType = ClaimTypes.Role,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes("Your Signing Key")),
.....
/* 省略不相關的程式碼 */
};
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
// SignalR 會將 Token 以參數名稱 access_token 的方式放在 URL 查詢參數裡
var accessToken = context.Request.Query["access_token"];
// 連線網址為 Hubs 相關路徑才檢查
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/hubs"))
{
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
services.AddSignalR();
/* 省略不相關的程式碼 */
}
2. 設定客戶端(Client)在建立連線時,附上 Token 給伺服端(Server)做驗證
客戶端為 Javascript 時
this.connection = new signalR.HubConnectionBuilder()
.withUrl("/hubs/myhub", { accessTokenFactory: () => "Your JWT Token" }) // e.g. () => "eyJhbGciOiJIUzI1NiIsInR5cCI6I......"
.build();
客戶端為 C# 時
var connection = new HubConnectionBuilder()
.WithUrl("https://example.com/hubs/myhub", options =>
{
options.AccessTokenProvider = () => Task.FromResult("Your JWT Token"); // e.g. () => Task.FromResult("eyJhbGciOiJIUzI1NiIsInR5cCI6I......")
})
.Build();
當使用 Javascript 時,可以透過 Chrome 瀏覽器的開發者工具看到 SignalR 確實會將我們置放的 Token 透過網址參數access_token
傳遞給後端
3. 在 Hub 裡正確取得用戶名稱即完成
這邊以覆寫從 Hub 繼承來的 OnConnectedAsync
方法為例子
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
namespace MyWeb.Hubs
{
[Authorize]
public class MyHub : Hub
{
public override Task OnConnectedAsync()
{
//取得用戶名稱
string name = Context.User.Identity.Name;
/* 執行你拿到用戶名稱後想幹的任何事XD */
return base.OnConnectedAsync();
}
/* 其它程式碼... */
}
}
補充: 在 Stackoverflow 上看到另一種截取 Token 的方式
使用此種方式可移除第一步驟裡 OnMessageReceived
這段程式碼
services.AddAuthentication(options =>
{
/* 省略程式碼... */
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
/* 省略程式碼... */
};
options.Events = new JwtBearerEvents
{
/* 以下這整段可拿掉 */
// OnMessageReceived = context =>
// {
// // SignalR 會將 Token 以參數名稱 access_token 的方式放在 URL 查詢參數裡
// var accessToken = context.Request.Query["access_token"];
// // 連線網址為 Hubs 相關路徑才檢查
// var path = context.HttpContext.Request.Path;
// if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/hubs"))
// {
// context.Token = accessToken;
// }
// return Task.CompletedTask;
// }
};
});
接著在專案的 Startup.cs 的 Configure
方法裡加上以下程式碼
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
/* 省略其它程式碼... */
// 加上這一區段
app.Use(async (context, next) =>
{
if (context.Request.Path.Value.StartsWith("/hubs"))
{
var bearerToken = context.Request.Query["access_token"].ToString();
if (!String.IsNullOrEmpty(bearerToken))
context.Request.Headers.Add("Authorization", "Bearer " + bearerToken);
}
await next();
});
app.UseAuthentication();
app.UseAuthorization();
/* 省略其它程式碼... */
}
參考資料
[Microsoft] Authentication and authorization in ASP.NET Core SignalR
[Microsoft] ASP.NET Core SignalR configuration
[The Will Will Web] 如何在 ASP.NET Core 3 使用 Token-based 身分驗證與授權