Hero Image
[.NET Core] ASP .NET Core 3.1 驗證與授權(三)-Cookie 驗證實例

前兩篇介紹了驗證、授權在 .NET Core 當中的基本的概念,本節實作 Cookie 驗證的設定、簽發、登出… Configuration 在 Startup.ConfigureServices 方法中設置驗證方案, 並且可以在 AddCookie 當中設置 CookieAuthenticationOptions(見前一節) // 設置 cookie 驗證作為應用程式預設的驗證方案 services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) // 將 cookie 服務添加到服務容器當中 .AddCookie(); 在 Startup.Configure 方法中,呼叫 UseAuthentication、UseAuthorization, 啟用驗證中間件並設置 HttpContext.User 屬性, UseAuthentication 必須在 UseAuthorization 之前,且兩者都必須在 UseEndpoints 之前被呼叫: app.UseAuthentication(); // 驗證 app.UseAuthorization(); // 授權 // 端點對應 app.UseEndpoints(endpoints => { endpoints.MapControllers(); endpoints.MapRazorPages(); }); Cookie Policy Middleware 在中間件當中設置的驗證政策會作用於全域(每個請求), 舉例來說,最常用的就是限制應用程式所有 Cookie 的 SameSite 屬性, 所有 Controller 簽發的 Cookie.SamSite 屬性會被限縮為較嚴格(不比 MinimumSameSitePolicy 寬鬆)的設置: app.UseCookiePolicy(new CookiePolicyOptions { // 所有 Cookie.SamSite 設置都會被提升為 Strict MinimumSameSitePolicy = SameSiteMode.Strict, // Cookie.SamSite 設置為 None 的話會被提升為 Lax //MinimumSameSitePolicy = SameSiteMode.Lax, // MinimumSameSitePolicy 設置為最寬鬆,因此不會影響 Cookie.SamSite //MinimumSameSitePolicy = SameSiteMode.None, }); Create an authentication cookie .NET Core 利用 ClaimsPrincipal 將序列化的使用者資訊儲存在 Cookie 當中 而 ClaimsPrincipal 可包含很多 ClaimsIdentity(但通常只有一個); ClaimsIdentity 可以且通常包含很多 Claims(聲明), 而每個 Claims 是包含型別(ClaimType)、值(ClaimValue)。 因此為登入使用者建立 Cookie 驗證的步驟如下:

Hero Image
[.NET Core] ASP .NET Core 3.1 驗證與授權(一)-驗證與授權

在進入 ASP .NET Core 3.1 中驗證(Authentication)與授權(Authorization)的作用流程前,應當對兩者有抽象概念上的認識,以及了解兩者的差異。 驗證(Authentication) 驗證是確認用戶識別碼(User Identity)的程序,通過驗證的用戶可具有一或多個用戶識別碼, 因此驗證服務本身就是使用者識別碼提供者 (User Identity Provider), ASP.NET Core 3.1 當中以依賴注入(DI; Dependency Injection)將驗證服務注入服務容器 (Service Container), 使應用程式驗證簽發時能夠取用。 授權(Authorization) 授權的作用是界定用戶可存取資源範圍,作用描述如下: 限制所存取的資源是否需要驗證。 已獲得驗證的特定用戶、特定腳色方能存取特定資源。 所存取的資源需要以何種授權政策(Authorizaton Policy)、即驗證方案(Authencation Scheme)。 挑戰和禁止 有些名詞需要先解釋: 驗證方案(Authentication Scheme)當中設置了挑戰(Chellange)與禁止(Forbid)應該進行的動作,這些註冊於驗證方案的動作動作由授權叫用。 挑戰(Challenge) 未驗證使用者要存取需驗證才能存取的資源時, 授權服務會叫用 IAuthenticationService.ChallengeAsync 發起 challenge, challenge 被發起後所伴隨採取的行動稱為 challenge action, 且 challenge action 應讓使用者知道應該以哪一種驗證機制取得授權,常見的具體範例有: cookie 驗證方案將使用者轉址到登入頁面。 JWT 回傳 401 Unauthorized 狀態碼,並在 Header 帶入 www-authenticate: bearer。 禁止(Forbid) 已驗證的使用者要存取授權之外的資源時, 授權會叫用 IAuthenticationService.ForbidAsync 發起 Forbid, Forbid 發起後所伴隨採取的行動稱為 Forbid action, Forbid action 的目的是要讓使用者知道自己已通過認證、且不具權限訪問所請求的資源, 常見的具體範例有:

Hero Image
[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 當中添加對該類別的引用: