diff --git a/README.md b/README.md index 2cca5c3..878bd81 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,35 @@ # CLK.AspNetCoreLab -## [ASP.NET Core] Authenticate/Authorization 程式範例 +## [ASP.NET Core] Cookie/JwtBearer並存的身分驗證範例 -本篇範例使用自定義的Middleware/Handler,展示如何在ASP.NET Core裡進行身分認證/身分授權。 +本篇範例展示如何在ASP.NET Core裡,同時使用Cookie及JwtBearer身分驗證機制。 範例原碼:https://github.com/Clark159/CLK.AspNetCoreLab -範例專案:AuthLab +範例專案:CookieOrJwtBearerAuthenticationLab + +測試步驟: + +- Account.Login、狀態:未登入 + 1. 點擊GetTokenByPassword按鈕,將會使用Username、Password進行身分驗證,並發放Token回傳。 + 2. 點擊GetUserByToken按鈕,將會使用步驟1取得的Token進行身分驗證,並回傳目前登入的User資料。(authenticationType="JwtBearer") + 3. 點擊Login按鈕,將會使用Username、Password進行身分驗證,並發放Cookie後轉頁至Home頁面。 + +- Home.Index、狀態:未登入 + 1. 進入頁面後,會先顯示目前登入的User資料。(authenticationType="Cookies") + 2. 點擊GetTokenByCookie按鈕,將會使用Cookie進行身分驗證,並發放Token回傳。 + 3. 點擊GetUserByToken按鈕,將會使用步驟1取得的Token進行身分驗證,並回傳目前登入的User資料。(authenticationType="JwtBearer") + 4. 點擊GetUserByToken按鈕,將會使用Cookie進行身分驗證,並回傳目前登入的User資料。(authenticationType="Cookies") + 5. 點擊Logout按鈕,將會刪除Cookie,並轉頁至Login頁面。 + + +## [ASP.NET Core] Authenticate/Authorization 步驟流程範例 + +本篇範例使用自定義的Middleware/Handler,展示在ASP.NET Core裡進行身分認證/身分授權的步驟流程。 + +範例原碼:https://github.com/Clark159/CLK.AspNetCoreLab + +範例專案:KernelAuthenticationLab 執行結果: - ![AuthLab執行結果](https://raw.githubusercontent.com/Clark159/CLK.AspNetCoreLab/master/doc/AuthLab/%E5%9F%B7%E8%A1%8C%E7%B5%90%E6%9E%9C.png) diff --git a/src/FacebookAuthenticationLab/wwwroot/favicon.ico b/src/ApiOutputLab/wwwroot/favicon.ico similarity index 100% rename from src/FacebookAuthenticationLab/wwwroot/favicon.ico rename to src/ApiOutputLab/wwwroot/favicon.ico diff --git a/src/JwtBearerAuthenticationLab/wwwroot/favicon.ico b/src/ApiVersionLab/wwwroot/favicon.ico similarity index 100% rename from src/JwtBearerAuthenticationLab/wwwroot/favicon.ico rename to src/ApiVersionLab/wwwroot/favicon.ico diff --git a/src/CLK.AspNetCoreLab.sln b/src/CLK.AspNetCoreLab.sln index a3dc691..b1a9536 100644 --- a/src/CLK.AspNetCoreLab.sln +++ b/src/CLK.AspNetCoreLab.sln @@ -5,8 +5,6 @@ VisualStudioVersion = 16.0.30804.86 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AreaLab", "AreaLab\AreaLab.csproj", "{3312D580-D3DD-4F94-8607-8D7D298F272F}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JwtAuthLab", "JwtAuthLab\JwtAuthLab.csproj", "{6FAE42CB-B825-4612-ABFF-452465CC2907}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PermissionLab", "PermissionLab\PermissionLab.csproj", "{0172F3CF-2704-4621-821F-16EDC38CE995}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WildcardMatchLab", "WildcardMatchLab\WildcardMatchLab.csproj", "{BAB117E8-9CA4-4242-B2F5-9225EF7A5758}" @@ -45,14 +43,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "04.Authentication", "04.Aut EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CookieAuthenticationLab", "CookieAuthenticationLab\CookieAuthenticationLab.csproj", "{F473924C-4A62-497E-BF11-EBC229A9944F}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JwtBearerAuthenticationLab", "JwtBearerAuthenticationLab\JwtBearerAuthenticationLab.csproj", "{A1631F3F-0D4E-4BEE-A36F-569919474121}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FacebookAuthenticationLab", "FacebookAuthenticationLab\FacebookAuthenticationLab.csproj", "{45D0015A-4AA1-42AC-A7DC-5BB7875539F0}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Facebook2RegisterAuthenticationLab", "Facebook2RegisterAuthenticationLab\Facebook2RegisterAuthenticationLab.csproj", "{7F617D94-7575-4682-8AED-896A59EFA24B}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Old", "Old", "{F2740F15-B2C6-42A9-9594-7D514613B6E8}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "03.Module", "03.Module", "{0E686910-AD0B-4AF4-B344-CFF4AF819DA3}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "01.Setting", "01.Setting", "{E09476D2-16DD-4898-829B-0917EFCF4B0C}" @@ -63,7 +55,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "05.Authorization", "05.Auth EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "99.Other", "99.Other", "{2EF4759E-2FE8-4521-B4AC-ED3730283B37}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KernelAuthenticationLab", "KernelAuthenticationLab\KernelAuthenticationLab.csproj", "{D7579E02-831D-42A6-BA4F-C4D65BEA90AD}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KernelAuthenticationLab", "KernelAuthenticationLab\KernelAuthenticationLab.csproj", "{D7579E02-831D-42A6-BA4F-C4D65BEA90AD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CookieOrJwtBearerAuthenticationLab", "CookieOrJwtBearerAuthenticationLab\CookieOrJwtBearerAuthenticationLab.csproj", "{B928E9BA-B63F-4726-84C0-6E87BC18A828}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -75,10 +69,6 @@ Global {3312D580-D3DD-4F94-8607-8D7D298F272F}.Debug|Any CPU.Build.0 = Debug|Any CPU {3312D580-D3DD-4F94-8607-8D7D298F272F}.Release|Any CPU.ActiveCfg = Release|Any CPU {3312D580-D3DD-4F94-8607-8D7D298F272F}.Release|Any CPU.Build.0 = Release|Any CPU - {6FAE42CB-B825-4612-ABFF-452465CC2907}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6FAE42CB-B825-4612-ABFF-452465CC2907}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6FAE42CB-B825-4612-ABFF-452465CC2907}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6FAE42CB-B825-4612-ABFF-452465CC2907}.Release|Any CPU.Build.0 = Release|Any CPU {0172F3CF-2704-4621-821F-16EDC38CE995}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0172F3CF-2704-4621-821F-16EDC38CE995}.Debug|Any CPU.Build.0 = Debug|Any CPU {0172F3CF-2704-4621-821F-16EDC38CE995}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -139,14 +129,6 @@ Global {F473924C-4A62-497E-BF11-EBC229A9944F}.Debug|Any CPU.Build.0 = Debug|Any CPU {F473924C-4A62-497E-BF11-EBC229A9944F}.Release|Any CPU.ActiveCfg = Release|Any CPU {F473924C-4A62-497E-BF11-EBC229A9944F}.Release|Any CPU.Build.0 = Release|Any CPU - {A1631F3F-0D4E-4BEE-A36F-569919474121}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A1631F3F-0D4E-4BEE-A36F-569919474121}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A1631F3F-0D4E-4BEE-A36F-569919474121}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A1631F3F-0D4E-4BEE-A36F-569919474121}.Release|Any CPU.Build.0 = Release|Any CPU - {45D0015A-4AA1-42AC-A7DC-5BB7875539F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {45D0015A-4AA1-42AC-A7DC-5BB7875539F0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {45D0015A-4AA1-42AC-A7DC-5BB7875539F0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {45D0015A-4AA1-42AC-A7DC-5BB7875539F0}.Release|Any CPU.Build.0 = Release|Any CPU {7F617D94-7575-4682-8AED-896A59EFA24B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7F617D94-7575-4682-8AED-896A59EFA24B}.Debug|Any CPU.Build.0 = Debug|Any CPU {7F617D94-7575-4682-8AED-896A59EFA24B}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -155,13 +137,16 @@ Global {D7579E02-831D-42A6-BA4F-C4D65BEA90AD}.Debug|Any CPU.Build.0 = Debug|Any CPU {D7579E02-831D-42A6-BA4F-C4D65BEA90AD}.Release|Any CPU.ActiveCfg = Release|Any CPU {D7579E02-831D-42A6-BA4F-C4D65BEA90AD}.Release|Any CPU.Build.0 = Release|Any CPU + {B928E9BA-B63F-4726-84C0-6E87BC18A828}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B928E9BA-B63F-4726-84C0-6E87BC18A828}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B928E9BA-B63F-4726-84C0-6E87BC18A828}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B928E9BA-B63F-4726-84C0-6E87BC18A828}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {3312D580-D3DD-4F94-8607-8D7D298F272F} = {89E18C8E-CF82-43F1-AC8A-DD0F89409EA2} - {6FAE42CB-B825-4612-ABFF-452465CC2907} = {F2740F15-B2C6-42A9-9594-7D514613B6E8} {0172F3CF-2704-4621-821F-16EDC38CE995} = {82CBD2E0-D94E-4985-95EE-2807FF8F2423} {BAB117E8-9CA4-4242-B2F5-9225EF7A5758} = {2EF4759E-2FE8-4521-B4AC-ED3730283B37} {D48B0AA3-DD0B-45FE-B4F9-9A8C46302561} = {0E686910-AD0B-4AF4-B344-CFF4AF819DA3} @@ -178,15 +163,13 @@ Global {79FD7C8F-73E0-4A58-B62B-81B8393B7700} = {0E686910-AD0B-4AF4-B344-CFF4AF819DA3} {4619D157-98EB-4180-A492-2EE070EDE1DA} = {89E18C8E-CF82-43F1-AC8A-DD0F89409EA2} {F473924C-4A62-497E-BF11-EBC229A9944F} = {4619D157-98EB-4180-A492-2EE070EDE1DA} - {A1631F3F-0D4E-4BEE-A36F-569919474121} = {4619D157-98EB-4180-A492-2EE070EDE1DA} - {45D0015A-4AA1-42AC-A7DC-5BB7875539F0} = {4619D157-98EB-4180-A492-2EE070EDE1DA} {7F617D94-7575-4682-8AED-896A59EFA24B} = {4619D157-98EB-4180-A492-2EE070EDE1DA} - {F2740F15-B2C6-42A9-9594-7D514613B6E8} = {4619D157-98EB-4180-A492-2EE070EDE1DA} {0E686910-AD0B-4AF4-B344-CFF4AF819DA3} = {89E18C8E-CF82-43F1-AC8A-DD0F89409EA2} {E09476D2-16DD-4898-829B-0917EFCF4B0C} = {89E18C8E-CF82-43F1-AC8A-DD0F89409EA2} {19FFF031-5998-4B25-A4EB-A60F975920F2} = {89E18C8E-CF82-43F1-AC8A-DD0F89409EA2} {39291E0E-6BAF-4A9E-9C37-552F31077C43} = {89E18C8E-CF82-43F1-AC8A-DD0F89409EA2} {D7579E02-831D-42A6-BA4F-C4D65BEA90AD} = {4619D157-98EB-4180-A492-2EE070EDE1DA} + {B928E9BA-B63F-4726-84C0-6E87BC18A828} = {4619D157-98EB-4180-A492-2EE070EDE1DA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {CBB7D75C-988E-4A62-8EB5-C26F026248F5} diff --git a/src/CookieAuthenticationLab/Controllers/AccountController.cs b/src/CookieAuthenticationLab/Controllers/AccountController.cs index d3488d5..f5b68f2 100644 --- a/src/CookieAuthenticationLab/Controllers/AccountController.cs +++ b/src/CookieAuthenticationLab/Controllers/AccountController.cs @@ -21,7 +21,7 @@ public async Task Login(string username = null, string returnUrl = if (string.IsNullOrEmpty(returnUrl) == true) returnUrl = @"/"; if (this.User.Identity.IsAuthenticated == true) return this.Redirect(returnUrl); - // Check + // Validate // ... // ClaimsPrincipal diff --git a/src/CookieAuthenticationLab/Views/Home/Index.cshtml b/src/CookieAuthenticationLab/Views/Home/Index.cshtml index 4d62dd2..9b6532a 100644 --- a/src/CookieAuthenticationLab/Views/Home/Index.cshtml +++ b/src/CookieAuthenticationLab/Views/Home/Index.cshtml @@ -16,7 +16,8 @@
- Hello @User.Identity.Name!
+ Username= @User.Identity.Name
+ AuthType= @User.Identity.AuthenticationType

diff --git a/src/CookieOrJwtBearerAuthenticationLab/Controllers/AccountController.cs b/src/CookieOrJwtBearerAuthenticationLab/Controllers/AccountController.cs new file mode 100644 index 0000000..5dcbe73 --- /dev/null +++ b/src/CookieOrJwtBearerAuthenticationLab/Controllers/AccountController.cs @@ -0,0 +1,212 @@ +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; + +namespace CookieOrJwtBearerAuthenticationLab +{ + [AllowAnonymous] + public partial class AccountController : Controller + { + // Fields + private readonly SecurityTokenFactory _tokenFactory; + + + // Constructors + public AccountController(SecurityTokenFactory tokenFactory) + { + #region Contracts + + if (tokenFactory == null) throw new ArgumentException(nameof(tokenFactory)); + + #endregion + + // Default + _tokenFactory = tokenFactory; + } + + + // Methods + public async Task Login(string username = null, string password = null, string returnUrl = @"/") + { + // Require + if (string.IsNullOrEmpty(username) == true) return View(); + if (string.IsNullOrEmpty(returnUrl) == true) returnUrl = @"/"; + if (this.User.Identity.IsAuthenticated == true) return this.Redirect(returnUrl); + + // Validate + // ... + + // ClaimsPrincipal + var claimIdentity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme); + claimIdentity.AddClaim(new Claim(ClaimTypes.Name, username)); + var claimsPrincipal = new ClaimsPrincipal(claimIdentity); + + // SignIn + await this.HttpContext.SignInAsync(claimsPrincipal); + + // Redirect + return this.Redirect(returnUrl); + } + + public async Task Logout() + { + // Require + if (this.User.Identity.IsAuthenticated == false) return this.Redirect(@"/"); + + // SignIn + await this.HttpContext.SignOutAsync(); + + // Redirect + return this.Redirect(@"/"); + } + } + + // GetUser + public partial class AccountController : Controller + { + // Methods + [Authorize] + public ActionResult GetUser([FromBody] GetUserActionModel actionModel) + { + #region Contracts + + if (actionModel == null) throw new ArgumentException(nameof(actionModel)); + + #endregion + + // UserModel + var user = new UserModel(); + user.Username = this.User.Identity.Name; + user.AuthenticationType = this.User.Identity.AuthenticationType; + + // Return + return (new GetUserResultModel() + { + User = user + }); + } + + + // Class + public class GetUserActionModel + { + // Properties + + } + + public class GetUserResultModel + { + // Properties + public UserModel User { get; set; } + } + + public class UserModel + { + // Properties + public string Username { get; set; } + + public string AuthenticationType { get; set; } + } + } + + // GetToken + public partial class AccountController : Controller + { + // Methods + [Authorize] + public ActionResult GetToken([FromBody] GetTokenActionModel actionModel) + { + #region Contracts + + if (actionModel == null) throw new ArgumentException(nameof(actionModel)); + + #endregion + + // ClaimIdentity + var claimIdentity = this.User.Identity as ClaimsIdentity; + if (claimIdentity == null) throw new InvalidOperationException($"{nameof(claimIdentity)}=null"); + + // TokenString + var tokenString = _tokenFactory.CreateEncodedJwt(claimIdentity); + if (string.IsNullOrEmpty(tokenString) == true) throw new InvalidOperationException($"{nameof(tokenString)}=null"); + + // Return + return (new GetTokenResultModel() + { + Token = tokenString + }); + } + + + // Class + public class GetTokenActionModel + { + // Properties + + } + + public class GetTokenResultModel + { + // Properties + public string Token { get; set; } + } + } + + // GetTokenByPassword + public partial class AccountController : Controller + { + // Methods + [AllowAnonymous] + public ActionResult GetTokenByPassword([FromBody] GetTokenByPasswordActionModel actionModel) + { + #region Contracts + + if (actionModel == null) throw new ArgumentException(nameof(actionModel)); + + #endregion + + // Require + if (string.IsNullOrEmpty(actionModel.Username) == true) throw new InvalidOperationException($"{nameof(actionModel.Username)}=null"); + if (string.IsNullOrEmpty(actionModel.Password) == true) throw new InvalidOperationException($"{nameof(actionModel.Password)}=null"); + + // Validate + // ... + + // ClaimIdentity + var claimIdentity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme); + claimIdentity.AddClaim(new Claim(ClaimTypes.Name, actionModel.Username)); + + // TokenString + var tokenString = _tokenFactory.CreateEncodedJwt(claimIdentity.Claims); + if (string.IsNullOrEmpty(tokenString) == true) throw new InvalidOperationException($"{nameof(tokenString)}=null"); + + // Return + return (new GetTokenByPasswordResultModel() + { + Token = tokenString + }); + } + + + // Class + public class GetTokenByPasswordActionModel + { + // Properties + public string Username { get; set; } + + public string Password { get; set; } + } + + public class GetTokenByPasswordResultModel + { + // Properties + public string Token { get; set; } + } + } +} diff --git a/src/CookieOrJwtBearerAuthenticationLab/Controllers/HomeController.cs b/src/CookieOrJwtBearerAuthenticationLab/Controllers/HomeController.cs new file mode 100644 index 0000000..b7e68cc --- /dev/null +++ b/src/CookieOrJwtBearerAuthenticationLab/Controllers/HomeController.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace CookieOrJwtBearerAuthenticationLab +{ + public class HomeController : Controller + { + // Methods + [Authorize] + public ActionResult Index() + { + // Return + return View(); + } + } +} diff --git a/src/CookieOrJwtBearerAuthenticationLab/CookieOrJwtBearerAuthenticationLab.csproj b/src/CookieOrJwtBearerAuthenticationLab/CookieOrJwtBearerAuthenticationLab.csproj new file mode 100644 index 0000000..4e9d5da --- /dev/null +++ b/src/CookieOrJwtBearerAuthenticationLab/CookieOrJwtBearerAuthenticationLab.csproj @@ -0,0 +1,11 @@ + + + + net5.0 + + + + + + + diff --git a/src/JwtBearerAuthenticationLab/Program.cs b/src/CookieOrJwtBearerAuthenticationLab/Program.cs similarity index 93% rename from src/JwtBearerAuthenticationLab/Program.cs rename to src/CookieOrJwtBearerAuthenticationLab/Program.cs index 58a9821..383b2d3 100644 --- a/src/JwtBearerAuthenticationLab/Program.cs +++ b/src/CookieOrJwtBearerAuthenticationLab/Program.cs @@ -7,7 +7,7 @@ using System.Linq; using System.Threading.Tasks; -namespace JwtBearerAuthenticationLab +namespace CookieOrJwtBearerAuthenticationLab { public class Program { diff --git a/src/JwtBearerAuthenticationLab/Properties/launchSettings.json b/src/CookieOrJwtBearerAuthenticationLab/Properties/launchSettings.json similarity index 83% rename from src/JwtBearerAuthenticationLab/Properties/launchSettings.json rename to src/CookieOrJwtBearerAuthenticationLab/Properties/launchSettings.json index e2d5555..a869f31 100644 --- a/src/JwtBearerAuthenticationLab/Properties/launchSettings.json +++ b/src/CookieOrJwtBearerAuthenticationLab/Properties/launchSettings.json @@ -3,8 +3,8 @@ "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { - "applicationUrl": "http://localhost:14111", - "sslPort": 44320 + "applicationUrl": "http://localhost:16422", + "sslPort": 44323 } }, "profiles": { @@ -15,7 +15,7 @@ "ASPNETCORE_ENVIRONMENT": "Development" } }, - "JwtBearerAuthenticationLab": { + "CookieOrJwtBearerAuthenticationLab": { "commandName": "Project", "dotnetRunMessages": "true", "launchBrowser": true, diff --git a/src/CookieOrJwtBearerAuthenticationLab/Startup.cs b/src/CookieOrJwtBearerAuthenticationLab/Startup.cs new file mode 100644 index 0000000..1cc25c2 --- /dev/null +++ b/src/CookieOrJwtBearerAuthenticationLab/Startup.cs @@ -0,0 +1,145 @@ +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CookieOrJwtBearerAuthenticationLab +{ + public class Startup + { + // Methods + public void ConfigureServices(IServiceCollection services) + { + #region Contracts + + if (services == null) throw new ArgumentException(nameof(services)); + + #endregion + + // Config + var issuer = "AuthenticationLab"; + var signKey = "12345678901234567890123456789012"; + + // Mvc + services.AddMvc(); + + // Authentication + services.AddAuthentication(options => + { + // DefaultScheme + options.DefaultScheme = PolicyAuthenticationDefaults.AuthenticationScheme; + }) + .AddPolicy(options => + { + // DefaultScheme + options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; + + // AuthenticateSchemePolicy + options.AuthenticateSchemePolicy = context => + { + // JwtBearer + var authorization = context.Request.Headers["Authorization"].FirstOrDefault(); + if (string.IsNullOrEmpty(authorization) == false && authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase) == true) + { + return JwtBearerDefaults.AuthenticationScheme; + } + + // Cookie + return CookieAuthenticationDefaults.AuthenticationScheme; + }; + }) + .AddCookie(options => + { + // Action + options.LoginPath = new PathString("/Account/Login"); + options.LogoutPath = new PathString("/Account/Logout"); + }) + .AddJwtBearer(options => + { + // Validation + options.TokenValidationParameters = new TokenValidationParameters + { + // Setting + AuthenticationType = "JwtBearer", + + // Issuer + ValidateIssuer = true, + ValidIssuer = issuer, + + // Audience + ValidateAudience = false, + ValidAudience = null, + + // Lifetime + ValidateLifetime = true, + ClockSkew = TimeSpan.Zero, + + // Signing + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(signKey)), + }; + }); + + // Service + services.AddSingleton(new SecurityTokenFactory(issuer, signKey)); + } + + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + #region Contracts + + if (app == null) throw new ArgumentException(nameof(app)); + if (env == null) throw new ArgumentException(nameof(env)); + + #endregion + + // Development + if (env.IsDevelopment() == true) + { + app.UseDeveloperExceptionPage(); + } + + // StaticFile + app.UseStaticFiles(); + + // Authentication + app.UseAuthentication(); + + // Routing + app.UseRouting(); + { + + } + + // Authorization + app.UseAuthorization(); + + // Endpoints + app.UseEndpoints(endpoints => + { + // Default + endpoints.MapControllerRoute + ( + name: "Default", + pattern: "{controller=Home}/{action=Index}" + ); + + // Area + endpoints.MapControllerRoute + ( + name: "Area", + pattern: "{area:exists}/{controller=Home}/{action=Index}" + ); + }); + } + } +} diff --git a/src/CookieOrJwtBearerAuthenticationLab/Utilities/PolicyAuthenticationDefaults.cs b/src/CookieOrJwtBearerAuthenticationLab/Utilities/PolicyAuthenticationDefaults.cs new file mode 100644 index 0000000..08ef7f4 --- /dev/null +++ b/src/CookieOrJwtBearerAuthenticationLab/Utilities/PolicyAuthenticationDefaults.cs @@ -0,0 +1,29 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace CookieOrJwtBearerAuthenticationLab +{ + public partial class PolicyAuthenticationDefaults + { + // Properties + public const string AuthenticationScheme = "Policy"; + } + + public partial class PolicyAuthenticationDefaults + { + // Properties + internal const string AuthenticatePolicyScheme = "__AuthenticatePolicy"; + + internal const string ChallengePolicyScheme = "__ChallengePolicy"; + + internal const string ForbidPolicyScheme = "__ForbidPolicy"; + + internal const string SignInPolicyScheme = "__SignInPolicy"; + + internal const string SignOutPolicyScheme = "__SignOutPolicy"; + } +} diff --git a/src/CookieOrJwtBearerAuthenticationLab/Utilities/PolicyAuthenticationExtensions.cs b/src/CookieOrJwtBearerAuthenticationLab/Utilities/PolicyAuthenticationExtensions.cs new file mode 100644 index 0000000..5f32e48 --- /dev/null +++ b/src/CookieOrJwtBearerAuthenticationLab/Utilities/PolicyAuthenticationExtensions.cs @@ -0,0 +1,100 @@ +using Microsoft.AspNetCore.Authentication; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace CookieOrJwtBearerAuthenticationLab +{ + public static class PolicyAuthenticationExtensions + { + // Methods + public static AuthenticationBuilder AddPolicy(this AuthenticationBuilder builder, Action configureOptions) + { + #region Contracts + + if (builder == null) throw new ArgumentException(nameof(builder)); + if (configureOptions == null) throw new ArgumentException(nameof(configureOptions)); + + #endregion + + // Options + var options = new PolicyAuthenticationOptions(); + { + // Configure + configureOptions(options); + } + + // Require + if (string.IsNullOrEmpty(options.DefaultScheme) == true) throw new InvalidOperationException($"{nameof(options.DefaultScheme)}=null"); + if (options.DefaultScheme == PolicyAuthenticationDefaults.AuthenticationScheme) throw new InvalidOperationException($"{nameof(options.DefaultScheme)}={PolicyAuthenticationDefaults.AuthenticationScheme}"); + + // MainPolicy + builder.AddPolicyScheme(PolicyAuthenticationDefaults.AuthenticationScheme, null, policySchemeOptions => + { + // Default + policySchemeOptions.ForwardDefault = options.DefaultScheme; + + // SchemePolicy + if (options.AuthenticateSchemePolicy != null) policySchemeOptions.ForwardAuthenticate = PolicyAuthenticationDefaults.AuthenticatePolicyScheme; + if (options.ChallengeSchemePolicy != null) policySchemeOptions.ForwardChallenge = PolicyAuthenticationDefaults.ChallengePolicyScheme; + if (options.ForbidSchemePolicy != null) policySchemeOptions.ForwardForbid = PolicyAuthenticationDefaults.ForbidPolicyScheme; + if (options.SignInSchemePolicy != null) policySchemeOptions.ForwardSignIn = PolicyAuthenticationDefaults.SignInPolicyScheme; + if (options.SignOutSchemePolicy != null) policySchemeOptions.ForwardSignOut = PolicyAuthenticationDefaults.SignOutPolicyScheme; + }); + + // AuthenticateSchemePolicy + if (options.AuthenticateSchemePolicy != null) + { + builder.AddPolicyScheme(PolicyAuthenticationDefaults.AuthenticatePolicyScheme, null, policySchemeOptions => + { + // Selector + policySchemeOptions.ForwardDefaultSelector = context => options.AuthenticateSchemePolicy(context); + }); + } + + // ChallengeSchemePolicy + if (options.ChallengeSchemePolicy != null) + { + builder.AddPolicyScheme(PolicyAuthenticationDefaults.ChallengePolicyScheme, null, policySchemeOptions => + { + // Selector + policySchemeOptions.ForwardDefaultSelector = context => options.ChallengeSchemePolicy(context); + }); + } + + // ForbidSchemePolicy + if (options.ForbidSchemePolicy != null) + { + builder.AddPolicyScheme(PolicyAuthenticationDefaults.ForbidPolicyScheme, null, policySchemeOptions => + { + // Selector + policySchemeOptions.ForwardDefaultSelector = context => options.ForbidSchemePolicy(context); + }); + } + + // SignInSchemePolicy + if (options.SignInSchemePolicy != null) + { + builder.AddPolicyScheme(PolicyAuthenticationDefaults.SignInPolicyScheme, null, policySchemeOptions => + { + // Selector + policySchemeOptions.ForwardDefaultSelector = context => options.SignInSchemePolicy(context); + }); + } + + // SignOutSchemePolicy + if (options.SignOutSchemePolicy != null) + { + builder.AddPolicyScheme(PolicyAuthenticationDefaults.SignOutPolicyScheme, null, policySchemeOptions => + { + // Selector + policySchemeOptions.ForwardDefaultSelector = context => options.SignOutSchemePolicy(context); + }); + } + + // Return + return builder; + } + } +} diff --git a/src/CookieOrJwtBearerAuthenticationLab/Utilities/PolicyAuthenticationOptions.cs b/src/CookieOrJwtBearerAuthenticationLab/Utilities/PolicyAuthenticationOptions.cs new file mode 100644 index 0000000..bdb0f86 --- /dev/null +++ b/src/CookieOrJwtBearerAuthenticationLab/Utilities/PolicyAuthenticationOptions.cs @@ -0,0 +1,26 @@ +#nullable enable + +using Microsoft.AspNetCore.Http; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace CookieOrJwtBearerAuthenticationLab +{ + public class PolicyAuthenticationOptions + { + // Properties + public string? DefaultScheme { get; set; } + + public Func? AuthenticateSchemePolicy { get; set; } + + public Func? ChallengeSchemePolicy { get; set; } + + public Func? ForbidSchemePolicy { get; set; } + + public Func? SignInSchemePolicy { get; set; } + + public Func? SignOutSchemePolicy { get; set; } + } +} diff --git a/src/CookieOrJwtBearerAuthenticationLab/Utilities/SecurityTokenFactory.cs b/src/CookieOrJwtBearerAuthenticationLab/Utilities/SecurityTokenFactory.cs new file mode 100644 index 0000000..7126c06 --- /dev/null +++ b/src/CookieOrJwtBearerAuthenticationLab/Utilities/SecurityTokenFactory.cs @@ -0,0 +1,107 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace CookieOrJwtBearerAuthenticationLab +{ + public class SecurityTokenFactory + { + // Fields + private readonly JwtSecurityTokenHandler _tokenHandler = null; + + private readonly string _signKey = null; + + private readonly string _issuer = null; + + private readonly int _expireMinutes = 30; + + + // Constructors + public SecurityTokenFactory(string issuer, string signKey, int expireMinutes = 30) + { + #region Contracts + + if (string.IsNullOrEmpty(issuer) == true) throw new ArgumentException(nameof(issuer)); + if (string.IsNullOrEmpty(signKey) == true) throw new ArgumentException(nameof(signKey)); + + #endregion + + // Default + _tokenHandler = new JwtSecurityTokenHandler(); + _issuer = issuer; + _signKey = signKey; + _expireMinutes = expireMinutes; + } + + + // Methods + public string CreateEncodedJwt(ClaimsIdentity identity) + { + #region Contracts + + if (identity == null) throw new ArgumentException(nameof(identity)); + + #endregion + + // ClaimList + var claimList = new List(identity.Claims); + claimList.Add(new Claim(ClaimTypes.Name, identity.Name)); // Username + + // CreateEncodedJwt + return this.CreateEncodedJwt(claimList); + } + + public string CreateEncodedJwt(IEnumerable claims) + { + #region Contracts + + if (claims == null) throw new ArgumentException(nameof(claims)); + + #endregion + + // ClaimList + var claimList = new List(claims); + { + // JWT ID + claimList.RemoveAll(claim => claim.Type == JwtRegisteredClaimNames.Jti); + claimList.Add(new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())); + + // Issuer + claimList.RemoveAll(claim => claim.Type == JwtRegisteredClaimNames.Iss); + claimList.Add(new Claim(JwtRegisteredClaimNames.Iss, _issuer)); + } + + // TokenDescriptor + var tokenDescriptor = new SecurityTokenDescriptor + { + // Claim + Subject = new ClaimsIdentity(claimList), + + // Lifetime + IssuedAt = DateTime.Now, // 建立時間 + NotBefore = DateTime.Now, // 在此之前不可用時間 + Expires = DateTime.Now.AddMinutes(_expireMinutes), // 逾期時間 + + // Signing + SigningCredentials = new SigningCredentials + ( + key: new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_signKey)), + algorithm: SecurityAlgorithms.HmacSha256Signature + ), + }; + + // TokenString + var tokenString = _tokenHandler.CreateEncodedJwt(tokenDescriptor); + if (string.IsNullOrEmpty(tokenString) == true) throw new InvalidOperationException($"{nameof(tokenString)}=null"); + + // Return + return tokenString; + } + } +} diff --git a/src/CookieOrJwtBearerAuthenticationLab/Views/Account/Login.cshtml b/src/CookieOrJwtBearerAuthenticationLab/Views/Account/Login.cshtml new file mode 100644 index 0000000..792e52f --- /dev/null +++ b/src/CookieOrJwtBearerAuthenticationLab/Views/Account/Login.cshtml @@ -0,0 +1,99 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@{ + +} + + + + + + + + + + + + +

Account.Login

+
+ + +
+
+ Username:
+
+
+
+ + +
+ Username:
+

+
+ + +
+

+
+ + + \ No newline at end of file diff --git a/src/CookieOrJwtBearerAuthenticationLab/Views/Home/Index.cshtml b/src/CookieOrJwtBearerAuthenticationLab/Views/Home/Index.cshtml new file mode 100644 index 0000000..2d984d7 --- /dev/null +++ b/src/CookieOrJwtBearerAuthenticationLab/Views/Home/Index.cshtml @@ -0,0 +1,126 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@{ + +} + + + + + + + + + + + + +

Home.Index

+
+ + + Username= @User.Identity.Name
+ AuthenticationType= @User.Identity.AuthenticationType
+
+ + +
+
+
+
+
+ + +
+

+
+ + +
+

+
+ + +
+

+
+ + + diff --git a/src/FacebookAuthenticationLab/appsettings.Development.json b/src/CookieOrJwtBearerAuthenticationLab/appsettings.Development.json similarity index 100% rename from src/FacebookAuthenticationLab/appsettings.Development.json rename to src/CookieOrJwtBearerAuthenticationLab/appsettings.Development.json diff --git a/src/FacebookAuthenticationLab/appsettings.json b/src/CookieOrJwtBearerAuthenticationLab/appsettings.json similarity index 100% rename from src/FacebookAuthenticationLab/appsettings.json rename to src/CookieOrJwtBearerAuthenticationLab/appsettings.json diff --git a/src/FacebookAuthenticationLab/wwwroot/clk.lab.js b/src/CookieOrJwtBearerAuthenticationLab/wwwroot/clk.lab.js similarity index 100% rename from src/FacebookAuthenticationLab/wwwroot/clk.lab.js rename to src/CookieOrJwtBearerAuthenticationLab/wwwroot/clk.lab.js diff --git a/src/CookieOrJwtBearerAuthenticationLab/wwwroot/favicon.ico b/src/CookieOrJwtBearerAuthenticationLab/wwwroot/favicon.ico new file mode 100644 index 0000000..26252a8 Binary files /dev/null and b/src/CookieOrJwtBearerAuthenticationLab/wwwroot/favicon.ico differ diff --git a/src/FacebookAuthenticationLab/Controllers/HomeController.cs b/src/FacebookAuthenticationLab/Controllers/HomeController.cs deleted file mode 100644 index f05d5af..0000000 --- a/src/FacebookAuthenticationLab/Controllers/HomeController.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace FacebookAuthenticationLab -{ - public class HomeController : Controller - { - // Methods - public ActionResult Index() - { - return "Hello World!"; - } - } -} diff --git a/src/FacebookAuthenticationLab/FacebookAuthenticationLab.csproj b/src/FacebookAuthenticationLab/FacebookAuthenticationLab.csproj deleted file mode 100644 index d4f4797..0000000 --- a/src/FacebookAuthenticationLab/FacebookAuthenticationLab.csproj +++ /dev/null @@ -1,7 +0,0 @@ - - - - net5.0 - - - diff --git a/src/FacebookAuthenticationLab/Program.cs b/src/FacebookAuthenticationLab/Program.cs deleted file mode 100644 index 26087c0..0000000 --- a/src/FacebookAuthenticationLab/Program.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace FacebookAuthenticationLab -{ - public class Program - { - // Methods - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults((webBuilder) => - { - webBuilder.UseStartup(); - }); - } -} diff --git a/src/FacebookAuthenticationLab/Properties/launchSettings.json b/src/FacebookAuthenticationLab/Properties/launchSettings.json deleted file mode 100644 index 38dad4a..0000000 --- a/src/FacebookAuthenticationLab/Properties/launchSettings.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:33474", - "sslPort": 44349 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "FacebookAuthenticationLab": { - "commandName": "Project", - "dotnetRunMessages": "true", - "launchBrowser": true, - "applicationUrl": "https://localhost:5001;http://localhost:5000", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} diff --git a/src/FacebookAuthenticationLab/Startup.cs b/src/FacebookAuthenticationLab/Startup.cs deleted file mode 100644 index 05879bf..0000000 --- a/src/FacebookAuthenticationLab/Startup.cs +++ /dev/null @@ -1,71 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace FacebookAuthenticationLab -{ - public class Startup - { - // Methods - public void ConfigureServices(IServiceCollection services) - { - #region Contracts - - if (services == null) throw new ArgumentException(nameof(services)); - - #endregion - - // Mvc - services.AddMvc(); - } - - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - #region Contracts - - if (app == null) throw new ArgumentException(nameof(app)); - if (env == null) throw new ArgumentException(nameof(env)); - - #endregion - - // Development - if (env.IsDevelopment() == true) - { - app.UseDeveloperExceptionPage(); - } - - // StaticFile - app.UseStaticFiles(); - - // Routing - app.UseRouting(); - { - - } - - // Endpoints - app.UseEndpoints(endpoints => - { - // Default - endpoints.MapControllerRoute - ( - name: "Default", - pattern: "{controller=Home}/{action=Index}" - ); - - // Area - endpoints.MapControllerRoute - ( - name: "Area", - pattern: "{area:exists}/{controller=Home}/{action=Index}" - ); - }); - } - } -} diff --git a/src/GlobalRouteAttributeLab/wwwroot/favicon.ico b/src/GlobalRouteAttributeLab/wwwroot/favicon.ico new file mode 100644 index 0000000..26252a8 Binary files /dev/null and b/src/GlobalRouteAttributeLab/wwwroot/favicon.ico differ diff --git a/src/JwtAuthLab/Controllers/TokenController.cs b/src/JwtAuthLab/Controllers/TokenController.cs deleted file mode 100644 index 90c05b4..0000000 --- a/src/JwtAuthLab/Controllers/TokenController.cs +++ /dev/null @@ -1,139 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; -using System.Security.Principal; -using System.Threading.Tasks; - -namespace JwtAuthLab -{ - public partial class TokenController : Controller - { - // Fields - private readonly JwtHelper _jwtHelper; - - - // Constructors - public TokenController(JwtHelper jwtHelper) - { - _jwtHelper = jwtHelper; - } - } - - // GetToken - public partial class TokenController : Controller - { - // Methods - [AllowAnonymous] - public ActionResult GetToken([FromBody] GetTokenActionModel actionModel) - { - #region Contracts - - if (actionModel == null) throw new ArgumentException(nameof(actionModel)); - - #endregion - - // GetToken - return (new GetTokenResultModel() - { - Token = _jwtHelper.CreateToken(actionModel.UserId, actionModel.UserName, new List() { "Admin", "User" }) - }); - } - - - // Class - public class GetTokenActionModel - { - // Properties - public string UserId { get; set; } - - public string UserName { get; set; } - } - - public class GetTokenResultModel - { - // Properties - public string Token { get; set; } - } - } - - - // GetUser - public partial class TokenController : Controller - { - // Methods - //[Authorize] - public ActionResult GetUser([FromBody] GetUserActionModel actionModel) - { - #region Contracts - - if (actionModel == null) throw new ArgumentException(nameof(actionModel)); - - #endregion - - // MyUser - var user = new MyUser(); - user.IsAuthenticated = this.User.Identity.IsAuthenticated; - user.AuthenticationType = this.User.Identity.AuthenticationType; - user.UserId = (this.User.Identity as ClaimsIdentity)?.Claims.Where(x => x.Type == ClaimTypes.NameIdentifier).FirstOrDefault()?.Value; - user.UserName = (this.User.Identity as ClaimsIdentity)?.Name; - user.RoleList = (this.User.Identity as ClaimsIdentity)?.Claims.Where(x => x.Type == ClaimTypes.Role).Select(y=> y.Value).ToList(); - - // GetUser - return (new GetUserResultModel() - { - User = user - }) ; - } - - - // Class - public class GetUserActionModel - { - // Properties - - } - - public class GetUserResultModel - { - // Properties - public MyUser User { get; set; } - } - - public class MyUser - { - // Properties - public bool IsAuthenticated { get; set; } - - public string? AuthenticationType { get; set; } - - public string? UserId { get; set; } - - public string? UserName { get; set; } - - public List RoleList { get; set; } - - - // Methods - public bool IsInRole(string role) - { - #region Contracts - - if (string.IsNullOrEmpty(role) == true) throw new ArgumentException(nameof(role)); - - #endregion - - // Contains - if (RoleList.Contains(role) == true) - { - return true; - } - - // Return - return false; - } - } - } -} diff --git a/src/JwtAuthLab/JwtAuthLab.csproj b/src/JwtAuthLab/JwtAuthLab.csproj deleted file mode 100644 index 90fcee3..0000000 --- a/src/JwtAuthLab/JwtAuthLab.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - net5.0 - - - - ;NU1605 - 1701;1702;CS8632 - - - - - - - - - - - diff --git a/src/JwtAuthLab/Program.cs b/src/JwtAuthLab/Program.cs deleted file mode 100644 index 4a298de..0000000 --- a/src/JwtAuthLab/Program.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace JwtAuthLab -{ - public class Program - { - // Methods - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults((webBuilder) => - { - webBuilder.UseStartup(); - }); - } -} diff --git a/src/JwtAuthLab/Properties/launchSettings.json b/src/JwtAuthLab/Properties/launchSettings.json deleted file mode 100644 index a2985bb..0000000 --- a/src/JwtAuthLab/Properties/launchSettings.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:51951", - "sslPort": 44323 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "index.html", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "JwtAuthLab": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "dotnetRunMessages": "true", - "applicationUrl": "https://localhost:5001;http://localhost:5000" - } - } -} \ No newline at end of file diff --git a/src/JwtAuthLab/Startup.cs b/src/JwtAuthLab/Startup.cs deleted file mode 100644 index add8747..0000000 --- a/src/JwtAuthLab/Startup.cs +++ /dev/null @@ -1,121 +0,0 @@ -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.IdentityModel.Tokens; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; -using System.Text; -using System.Text.Encodings.Web; -using System.Text.Json.Serialization; -using System.Text.Unicode; -using System.Threading.Tasks; - -namespace JwtAuthLab -{ - public class Startup - { - // Methods - public void ConfigureServices(IServiceCollection services) - { - #region Contracts - - if (services == null) throw new ArgumentException(nameof(services)); - - #endregion - - // Services - services.AddSingleton(); - - // Mvc - var mvcBuilder = services.AddMvc(); - { - // JsonOptions - mvcBuilder.AddJsonOptions(options => - { - // Encoder - options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.BasicLatin, UnicodeRanges.CjkUnifiedIdeographs); - }); - } - - // Authentication - var authenticationBuilder = services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme); - { - // Config - var issuer = "JwtAuthLab"; - var signKey = "12345678901234567890123456789012"; - - // JwtBearer - authenticationBuilder.AddJwtBearer(options => - { - options.TokenValidationParameters = new TokenValidationParameters - { - // Setting - AuthenticationType = "JWT", - - // Issuer - ValidateIssuer = true, - ValidIssuer = issuer, - - // Audience - ValidateAudience = false, - ValidAudience = null, - - // Lifetime - ValidateLifetime = true, - ClockSkew = TimeSpan.Zero, - - // Signing - ValidateIssuerSigningKey = true, - IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(signKey)), - }; - }); - } - } - - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - #region Contracts - - if (app == null) throw new ArgumentException(nameof(app)); - if (env == null) throw new ArgumentException(nameof(env)); - - #endregion - - // Development - if (env.IsDevelopment() == true) - { - app.UseDeveloperExceptionPage(); - } - - // StaticFile - app.UseStaticFiles(); - - // Routing - app.UseRouting(); - { - - } - - // Auth - app.UseAuthentication(); - app.UseAuthorization(); - - // Endpoints - app.UseEndpoints(endpoints => - { - // Default - endpoints.MapControllerRoute - ( - name: "Default", - pattern: "{controller=Token}/{action=SignIn}" - ); - }); - } - } -} diff --git a/src/JwtAuthLab/Utilities/Helpers/JwtHelper.cs b/src/JwtAuthLab/Utilities/Helpers/JwtHelper.cs deleted file mode 100644 index c1ed3b6..0000000 --- a/src/JwtAuthLab/Utilities/Helpers/JwtHelper.cs +++ /dev/null @@ -1,77 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.IdentityModel.Tokens; -using System; -using System.Collections.Generic; -using System.IdentityModel.Tokens.Jwt; -using System.Linq; -using System.Security.Claims; -using System.Text; -using System.Threading.Tasks; - -namespace JwtAuthLab -{ - // Class - public class JwtHelper - { - // Methods - public string CreateToken(string userId, string userName, List roleList, int expireMinutes = 30) - { - #region Contracts - - if (string.IsNullOrEmpty(userId) == true) throw new ArgumentException(nameof(userId)); - if (string.IsNullOrEmpty(userName) == true) throw new ArgumentException(nameof(userName)); - if (roleList == null) throw new ArgumentException(nameof(roleList)); - - #endregion - - // Config - var issuer = "JwtAuthLab"; - var signKey = "12345678901234567890123456789012"; - - // Claim - var claims = new List(); - { - // JwtRegisteredClaimNames - claims.Add(new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())); // JWT ID - claims.Add(new Claim(JwtRegisteredClaimNames.Iss, issuer)); // Issuer - - // ClaimTypes - claims.Add(new Claim(ClaimTypes.NameIdentifier, userId)); // UserId - claims.Add(new Claim(ClaimTypes.Name, userName)); // UserName - { - // Role - foreach(var role in roleList) - { - claims.Add(new Claim(ClaimTypes.Role, role)); - } - } - } - - // TokenDescriptor - var tokenDescriptor = new SecurityTokenDescriptor - { - // Claim - Subject = new ClaimsIdentity(claims), - - // Lifetime - IssuedAt = DateTime.Now, // 建立時間 - NotBefore = DateTime.Now, // 在此之前不可用時間 - Expires = DateTime.Now.AddMinutes(expireMinutes), // 逾期時間 - - // Signing - SigningCredentials = new SigningCredentials - ( - key: new SymmetricSecurityKey(Encoding.UTF8.GetBytes(signKey)), - algorithm: SecurityAlgorithms.HmacSha256Signature - ), - }; - - // SerializeToken - var tokenHandler = new JwtSecurityTokenHandler(); - var serializeToken = tokenHandler.CreateEncodedJwt(tokenDescriptor); - - // Return - return serializeToken; - } - } -} diff --git a/src/JwtAuthLab/appsettings.Development.json b/src/JwtAuthLab/appsettings.Development.json deleted file mode 100644 index 8983e0f..0000000 --- a/src/JwtAuthLab/appsettings.Development.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } - } -} diff --git a/src/JwtAuthLab/appsettings.json b/src/JwtAuthLab/appsettings.json deleted file mode 100644 index d9d9a9b..0000000 --- a/src/JwtAuthLab/appsettings.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } - }, - "AllowedHosts": "*" -} diff --git a/src/JwtAuthLab/wwwroot/Index.html b/src/JwtAuthLab/wwwroot/Index.html deleted file mode 100644 index 63afe2b..0000000 --- a/src/JwtAuthLab/wwwroot/Index.html +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - - - - - -

JwtAuthLab.Index

-
- - -
-

-
- - -
-

-
- - - \ No newline at end of file diff --git a/src/JwtAuthLab/wwwroot/clk.lab.js b/src/JwtAuthLab/wwwroot/clk.lab.js deleted file mode 100644 index 4922a23..0000000 --- a/src/JwtAuthLab/wwwroot/clk.lab.js +++ /dev/null @@ -1,64 +0,0 @@ - -// PostRequst -function postRequst(url, body, headers, method) { - - // Headers - if (headers == null) headers = {}; - if (headers["Content-Type"] == null) headers["Content-Type"] = "application/json"; - if (headers["Accept"] == null) headers["Accept"] = "application/json"; - - // Method - if (method == null) method = "POST"; - - // Post - var task = fetch(url, { - method: method, - headers: headers, - body: JSON.stringify(body) - }) - - // Response - .then(function (response) { - return response.text().then(function (text) { - - // Json - try { - var content = JSON.parse(text); - if (typeof content == 'object' && content) { - return { - statusCode: response.status, - content: content - }; - } - } catch (e) { } - - // Null - if (!text && text != 0) { - return { - statusCode: response.status, - content: "No Content" - }; - } - - // Text - return { - statusCode: response.status, - content: text.replace(/^\"|\"$/g, '').replace(/\\\"/g, '"') - }; - }) - }) - - // Error - .catch(function (error) { - if (error instanceof TypeError == true) { - throw { - statusCode: 600, - content: "Connection Failed" - }; - }; - throw error; - }); - - // Return - return task; -} \ No newline at end of file diff --git a/src/JwtBearerAuthenticationLab/Controllers/HomeController.cs b/src/JwtBearerAuthenticationLab/Controllers/HomeController.cs deleted file mode 100644 index d50248b..0000000 --- a/src/JwtBearerAuthenticationLab/Controllers/HomeController.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace JwtBearerAuthenticationLab -{ - public class HomeController : Controller - { - // Methods - public ActionResult Index() - { - return "Hello World!"; - } - } -} diff --git a/src/JwtBearerAuthenticationLab/JwtBearerAuthenticationLab.csproj b/src/JwtBearerAuthenticationLab/JwtBearerAuthenticationLab.csproj deleted file mode 100644 index d4f4797..0000000 --- a/src/JwtBearerAuthenticationLab/JwtBearerAuthenticationLab.csproj +++ /dev/null @@ -1,7 +0,0 @@ - - - - net5.0 - - - diff --git a/src/JwtBearerAuthenticationLab/Startup.cs b/src/JwtBearerAuthenticationLab/Startup.cs deleted file mode 100644 index ffef5e3..0000000 --- a/src/JwtBearerAuthenticationLab/Startup.cs +++ /dev/null @@ -1,71 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace JwtBearerAuthenticationLab -{ - public class Startup - { - // Methods - public void ConfigureServices(IServiceCollection services) - { - #region Contracts - - if (services == null) throw new ArgumentException(nameof(services)); - - #endregion - - // Mvc - services.AddMvc(); - } - - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - #region Contracts - - if (app == null) throw new ArgumentException(nameof(app)); - if (env == null) throw new ArgumentException(nameof(env)); - - #endregion - - // Development - if (env.IsDevelopment() == true) - { - app.UseDeveloperExceptionPage(); - } - - // StaticFile - app.UseStaticFiles(); - - // Routing - app.UseRouting(); - { - - } - - // Endpoints - app.UseEndpoints(endpoints => - { - // Default - endpoints.MapControllerRoute - ( - name: "Default", - pattern: "{controller=Home}/{action=Index}" - ); - - // Area - endpoints.MapControllerRoute - ( - name: "Area", - pattern: "{area:exists}/{controller=Home}/{action=Index}" - ); - }); - } - } -} diff --git a/src/JwtBearerAuthenticationLab/appsettings.Development.json b/src/JwtBearerAuthenticationLab/appsettings.Development.json deleted file mode 100644 index 5173757..0000000 --- a/src/JwtBearerAuthenticationLab/appsettings.Development.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "DetailedErrors": true, - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } - } -} diff --git a/src/JwtBearerAuthenticationLab/appsettings.json b/src/JwtBearerAuthenticationLab/appsettings.json deleted file mode 100644 index d9d9a9b..0000000 --- a/src/JwtBearerAuthenticationLab/appsettings.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } - }, - "AllowedHosts": "*" -} diff --git a/src/JwtBearerAuthenticationLab/wwwroot/clk.lab.js b/src/JwtBearerAuthenticationLab/wwwroot/clk.lab.js deleted file mode 100644 index 4922a23..0000000 --- a/src/JwtBearerAuthenticationLab/wwwroot/clk.lab.js +++ /dev/null @@ -1,64 +0,0 @@ - -// PostRequst -function postRequst(url, body, headers, method) { - - // Headers - if (headers == null) headers = {}; - if (headers["Content-Type"] == null) headers["Content-Type"] = "application/json"; - if (headers["Accept"] == null) headers["Accept"] = "application/json"; - - // Method - if (method == null) method = "POST"; - - // Post - var task = fetch(url, { - method: method, - headers: headers, - body: JSON.stringify(body) - }) - - // Response - .then(function (response) { - return response.text().then(function (text) { - - // Json - try { - var content = JSON.parse(text); - if (typeof content == 'object' && content) { - return { - statusCode: response.status, - content: content - }; - } - } catch (e) { } - - // Null - if (!text && text != 0) { - return { - statusCode: response.status, - content: "No Content" - }; - } - - // Text - return { - statusCode: response.status, - content: text.replace(/^\"|\"$/g, '').replace(/\\\"/g, '"') - }; - }) - }) - - // Error - .catch(function (error) { - if (error instanceof TypeError == true) { - throw { - statusCode: 600, - content: "Connection Failed" - }; - }; - throw error; - }); - - // Return - return task; -} \ No newline at end of file diff --git a/src/PermissionLab/Controllers/HomeController.cs b/src/PermissionLab/Controllers/HomeController.cs index 45af20e..f4d3e16 100644 --- a/src/PermissionLab/Controllers/HomeController.cs +++ b/src/PermissionLab/Controllers/HomeController.cs @@ -51,7 +51,7 @@ private GetUserResultModel GetUser(GetUserActionModel actionModel) user.IsAuthenticated = this.User.Identity.IsAuthenticated; user.AuthenticationType = this.User.Identity.AuthenticationType; user.UserId = (this.User.Identity as ClaimsIdentity)?.Claims.Where(x => x.Type == ClaimTypes.NameIdentifier).FirstOrDefault()?.Value; - user.UserName = (this.User.Identity as ClaimsIdentity)?.Name; + user.Username = (this.User.Identity as ClaimsIdentity)?.Name; user.RoleList = (this.User.Identity as ClaimsIdentity)?.Claims.Where(x => x.Type == ClaimTypes.Role).Select(y => y.Value).ToList(); // GetUser @@ -84,7 +84,7 @@ public class MyUser public string? UserId { get; set; } - public string? UserName { get; set; } + public string? Username { get; set; } public List RoleList { get; set; } diff --git a/src/PermissionLab/Controllers/TokenController.cs b/src/PermissionLab/Controllers/TokenController.cs index 2655c85..58ae738 100644 --- a/src/PermissionLab/Controllers/TokenController.cs +++ b/src/PermissionLab/Controllers/TokenController.cs @@ -38,7 +38,7 @@ public ActionResult GetToken([FromBody] GetTokenActionModel // GetToken return (new GetTokenResultModel() { - Token = _jwtHelper.CreateToken(actionModel.UserId, actionModel.UserName, new List() { "Admin", "User" }) + Token = _jwtHelper.CreateToken(actionModel.UserId, actionModel.Username, new List() { "Admin", "User" }) }); } @@ -49,7 +49,7 @@ public class GetTokenActionModel // Properties public string UserId { get; set; } - public string UserName { get; set; } + public string Username { get; set; } } public class GetTokenResultModel diff --git a/src/PermissionLab/Utilities/Helpers/JwtHelper.cs b/src/PermissionLab/Utilities/Helpers/JwtHelper.cs index 5297ac4..8369165 100644 --- a/src/PermissionLab/Utilities/Helpers/JwtHelper.cs +++ b/src/PermissionLab/Utilities/Helpers/JwtHelper.cs @@ -37,7 +37,7 @@ public string CreateToken(string userId, string userName, List roleList, // ClaimTypes claims.Add(new Claim(ClaimTypes.NameIdentifier, userId)); // UserId - claims.Add(new Claim(ClaimTypes.Name, userName)); // UserName + claims.Add(new Claim(ClaimTypes.Name, userName)); // Username { // Role foreach(var role in roleList) diff --git a/src/PermissionLab/wwwroot/favicon.ico b/src/PermissionLab/wwwroot/favicon.ico new file mode 100644 index 0000000..26252a8 Binary files /dev/null and b/src/PermissionLab/wwwroot/favicon.ico differ