[SignalR] Websocket 即時聊天程式(三) - 後端 Token 認證
安裝套件 要進行 Token 的認證,需要先安裝 Microsoft.AspNetCore.Authentication.JwtBearer 套件:
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
註冊認證服務 新增一個檔案 DependencyInjection.cs,在當中製作 IServiceCollection 的擴充方法來自定義 JWT token 認證服務, 在裡面設置 Token 的認證規則、使用者識別碼對應、使用者群組對應, 而 SignalR 抓取使用者識別碼 (UserIdentifier) 的介面方法是 IUserIdProvider.GetUserId, 因此我們需要另外新增一個實作 IUserProvider 的類別注入服務容器給 SignalR 使用 ,該檔案程式碼如下:
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using System.Diagnostics.CodeAnalysis;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Threading.Tasks;
namespace SignalR.Extensions.DependencyInjection
{
public static class MyAddConfig
{
public static IServiceCollection AddMyJWTAuth(
[NotNull] this IServiceCollection services,
IConfiguration config
)
{
services.AddAuthentication(options =>
{
// Identity 預設是使用 Cookie authentication,必須手動設置為 JWT Bearer Auth:
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
// [注意]先解除 MapInboundClaims ,否則會因為套件中某些為向前相容而保留的 legacy code 使得 RoleClaimType 無法生效
// https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/1214
if (options.SecurityTokenValidators.FirstOrDefault() is JwtSecurityTokenHandler jwtSecurityTokenHandler)
jwtSecurityTokenHandler.MapInboundClaims = false;
// 設置 Token 在授權後是否要儲存於 AuthenticationProperties options.SaveToken = true;
// 設置各認證參數
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "userId", // 設置 Http 請求的 User.Identity.Name、Hub 中 UserIdentifier 取值的 Claim 是 userId
RoleClaimType = "roles", // 設置使用者的腳色從 type="roles" 的 claims 對應
ValidateLifetime = true, // 認證 Token 有效期間
ValidateIssuerSigningKey = true, //驗證 token 中的 key
IssuerSigningKey = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(config.GetValue<string>("JWT:SignKey"))), // SignKey
ValidateIssuer = false, // 不驗證簽發者
ValidateAudience = false // 不驗證 Audience (Token接收方)
};
options.Events = new JwtBearerEvents
{
// 設定當 OnMessageReceived 事件被觸發時,獲取認證用的 access_token OnMessageReceived = context =>
{
// 不同於標準夾帶於 header 的 http token,signalr 會透過網址參數發送 access token // ASP .NET Core 預設會將請求的 URL 皆做成 Log 紀錄,如果不想要網址列的 Token 被 Log 記錄下來必須參考
// https://docs.microsoft.com/aspnet/core/signalr/security#access-token-logging
string accessToken = context.Request.Query["access_token"];
// 檢查請求路徑是否為 chathub
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) &&
(path.StartsWithSegments("/chathub")))
{
// 把 token 丟進 MessageReceiveContext 當中
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
services.AddSingleton<IUserIdProvider, NameUserIdProvider>();
// 如果是使用 email claim 作為 user identifier 用下面這行並實作 EmailBasedUserIdProvider
// services.AddSingleton<IUserIdProvider, EmailBasedUserIdProvider>();
// NameUserIdProvider 和 EmailBasedUserIdProvider 無法同時使用!!
return services;
}
}
// 實作 SignalR 抓取使用者 Identity 的方法 IUserIdProvider.GetUserId
// 提供服務容器注入給 SignalR 使用
public class NameUserIdProvider : IUserIdProvider
{
public string GetUserId(HubConnectionContext connection)
{
// 認證設定時設置好 NameClaimType,這裡直接回傳 User.Identity.Name 即可
return connection.User?.Identity?.Name;
}
}
}
然後在 Startup.cs 當中添加對該類別的引用: