diff --git a/API註解操作說明(必看).txt b/API註解操作說明(必看).txt
new file mode 100644
index 0000000..e09ee74
--- /dev/null
+++ b/API註解操作說明(必看).txt
@@ -0,0 +1,12 @@
+JWTdemo資料夾→JWTdemo.csproj 開啟
+
+
+ net6.0
+ enable
+ enable //新增這兩行
+ true //新增這兩行
+ $(NoWarn);1591
+
+
+1.true:這個設定告訴編譯器生成 XML 註解檔案。當您的專案編譯時,它會將 XML 註解嵌入到組件中,以供 Swagger 或其他工具使用。
+2.$(NoWarn);1591:這個設定用來抑制編譯器警告 1591。警告 1591 是指程式碼中的缺少 XML 註解的警告。這裡的設定的意思是告訴編譯器忽略這個特定的警告,因為您已經啟用了 XML 註解生成,而不希望因缺少註解而收到警告。
\ No newline at end of file
diff --git a/JWTdemo.sln b/JWTdemo.sln
new file mode 100644
index 0000000..31305fa
--- /dev/null
+++ b/JWTdemo.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.7.34031.279
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JWTdemo", "JWTdemo\JWTdemo.csproj", "{4C54D743-8EE0-44C9-8C9D-010A306C4AE7}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {4C54D743-8EE0-44C9-8C9D-010A306C4AE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4C54D743-8EE0-44C9-8C9D-010A306C4AE7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4C54D743-8EE0-44C9-8C9D-010A306C4AE7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4C54D743-8EE0-44C9-8C9D-010A306C4AE7}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {5537568D-28F4-41EA-A9EE-89BAF86E4808}
+ EndGlobalSection
+EndGlobal
diff --git a/JWTdemo/Authorization/AllowAnonymousAttribute.cs b/JWTdemo/Authorization/AllowAnonymousAttribute.cs
new file mode 100644
index 0000000..4d0b894
--- /dev/null
+++ b/JWTdemo/Authorization/AllowAnonymousAttribute.cs
@@ -0,0 +1,6 @@
+namespace JWTdemo.Authorization;
+
+[AttributeUsage(AttributeTargets.Method)]
+public class AllowAnonymousAttribute : Attribute
+{
+}
\ No newline at end of file
diff --git a/JWTdemo/Authorization/AuthorizeAttribute.cs b/JWTdemo/Authorization/AuthorizeAttribute.cs
new file mode 100644
index 0000000..88e34e6
--- /dev/null
+++ b/JWTdemo/Authorization/AuthorizeAttribute.cs
@@ -0,0 +1,24 @@
+using Microsoft.AspNetCore.Mvc.Filters;
+using Microsoft.AspNetCore.Mvc;
+using JWTdemo.Entities;
+
+namespace JWTdemo.Authorization;
+[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
+public class AuthorizeAttribute : Attribute, IAuthorizationFilter
+{
+ public void OnAuthorization(AuthorizationFilterContext context)
+ {
+ // skip authorization if action is decorated with [AllowAnonymous] attribute
+ var allowAnonymous = context.ActionDescriptor.EndpointMetadata.OfType().Any();
+ if (allowAnonymous)
+ return;
+
+ // authorization
+ var user = (User?)context.HttpContext.Items["User"];
+ if (user == null)
+ {
+ // not logged in or role not authorized
+ context.Result = new JsonResult(new { message = "Unauthorized" }) { StatusCode = StatusCodes.Status401Unauthorized };
+ }
+ }
+}
\ No newline at end of file
diff --git a/JWTdemo/Authorization/JwtMiddleware.cs b/JWTdemo/Authorization/JwtMiddleware.cs
new file mode 100644
index 0000000..a25ab73
--- /dev/null
+++ b/JWTdemo/Authorization/JwtMiddleware.cs
@@ -0,0 +1,23 @@
+namespace JWTdemo.Authorization;
+public class JwtMiddleware
+{
+ private readonly RequestDelegate _next;
+
+ public JwtMiddleware(RequestDelegate next)
+ {
+ _next = next;
+ }
+
+ public async Task Invoke(HttpContext context, IUserService userService, IJwtUtils jwtUtils)
+ {
+ var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
+ var userId = jwtUtils.ValidateJwtToken(token);
+ if (userId != null)
+ {
+ // attach user to context on successful jwt validation
+ context.Items["User"] = userService.GetById(userId.Value);
+ }
+ //var stop = "1";
+ await _next(context);
+ }
+}
\ No newline at end of file
diff --git a/JWTdemo/Authorization/JwtUtils.cs b/JWTdemo/Authorization/JwtUtils.cs
new file mode 100644
index 0000000..74735e9
--- /dev/null
+++ b/JWTdemo/Authorization/JwtUtils.cs
@@ -0,0 +1,106 @@
+namespace JWTdemo.Authorization;
+
+using Microsoft.Extensions.Options;
+using Microsoft.IdentityModel.Tokens;
+using System.IdentityModel.Tokens.Jwt;
+using System.Security.Claims;
+using System.Text;
+using JWTdemo.Entities;
+using JWTdemo.Helpers;
+
+
+public interface IJwtUtils
+{
+ public string GenerateJwtToken(User user);
+ public int? ValidateJwtToken(string? token);
+}
+
+public class JwtUtils : IJwtUtils
+{
+ private readonly AppSettings _appSettings;
+
+ public JwtUtils(IOptions appSettings)
+ {
+ _appSettings = appSettings.Value;
+
+ if (string.IsNullOrEmpty(_appSettings.Secret))
+ throw new Exception("JWT secret not configured");
+ }
+
+ public string GenerateJwtToken(User user)
+ {
+ // generate token that is valid for 7 days
+ var tokenHandler = new JwtSecurityTokenHandler(); //實例化JWT令牌
+ var key = Encoding.ASCII.GetBytes(_appSettings.Secret!); //從配置中獲取應用程序密鑰,用於簽名令牌以確保其完整性和安全性
+ var tokenDescriptor = new SecurityTokenDescriptor //定義令牌格式,其包含header(SigningCredentials).payload(expires和subject).signature(簽在header裡面)
+ {
+ Subject = new ClaimsIdentity(new[] { new Claim("id", user.Id.ToString()) }), //payload
+ Expires = DateTime.UtcNow.AddDays(7), //payload,令牌過期時間
+ SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) //header
+ };
+ var token = tokenHandler.CreateToken(tokenDescriptor); //創建JWT令牌
+ return tokenHandler.WriteToken(token); //將JWT令牌轉為base64編碼
+ }
+
+ public int? ValidateJwtToken(string? token)
+ {
+ if (token == null)
+ return null;
+
+ var tokenHandler = new JwtSecurityTokenHandler();
+ var key = Encoding.ASCII.GetBytes(_appSettings.Secret!);
+ try
+ {
+ tokenHandler.ValidateToken(token, new TokenValidationParameters
+ {
+ ValidateIssuerSigningKey = true,
+ IssuerSigningKey = new SymmetricSecurityKey(key),
+ ValidateIssuer = false,
+ ValidateAudience = false,
+ // set clockskew to zero so tokens expire exactly at token expiration time (instead of 5 minutes later)
+ ClockSkew = TimeSpan.Zero
+ }, out SecurityToken validatedToken);
+
+ var jwtToken = (JwtSecurityToken)validatedToken;
+ var userId = int.Parse(jwtToken.Claims.First(x => x.Type == "id").Value);
+
+ // return user id from JWT token if validation successful
+ return userId;
+ }
+ catch
+ {
+ // return null if validation fails
+ return null;
+ }
+ }
+
+
+ //0523
+ public bool ValidateToken(string token)
+ {
+ var tokenHandler = new JwtSecurityTokenHandler();
+ var jwtSecret = "your_jwt_secret"; // JWT 密钥,应与生成令牌时使用的密钥相匹配
+
+ var validationParameters = new TokenValidationParameters
+ {
+ ValidateIssuerSigningKey = true,
+ IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSecret)),
+ ValidateIssuer = false,
+ ValidateAudience = false,
+ ValidateLifetime = true,
+ ClockSkew = TimeSpan.Zero // 设置为零以确保令牌过期时立即失效
+ };
+
+ try
+ {
+ SecurityToken validatedToken;
+ tokenHandler.ValidateToken(token, validationParameters, out validatedToken);
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/JWTdemo/Controllers/UserController.cs b/JWTdemo/Controllers/UserController.cs
new file mode 100644
index 0000000..99669e3
--- /dev/null
+++ b/JWTdemo/Controllers/UserController.cs
@@ -0,0 +1,28 @@
+using JWTdemo.Services;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using JWTdemo.Models;
+using JWTdemo.Authorization;
+
+namespace JWTdemo.Controllers
+{
+ [Authorize] //有token才能使用class
+ [Route("api/[controller]")]
+ [ApiController]
+ public class UserController : ControllerBase
+ {
+ private readonly SqlContext _context;
+ public UserController(SqlContext context)
+ {
+ _context = context;
+ }
+ ///
+ /// 測試註解
+ ///
+ [HttpGet]
+ public async Task>> Getuser()
+ {
+ return await _context.chatuser.ToListAsync();
+ }
+ }
+}
diff --git a/JWTdemo/Entities/User.cs b/JWTdemo/Entities/User.cs
new file mode 100644
index 0000000..7cf5006
--- /dev/null
+++ b/JWTdemo/Entities/User.cs
@@ -0,0 +1,14 @@
+using System.Text.Json.Serialization;
+
+namespace JWTdemo.Entities
+{
+ public class User
+ {
+ public int Id { get; set; }
+ public string? Name { get; set; }
+ public string? Username { get; set; }
+
+ [JsonIgnore] //這個就是當有人要get這個資料時,會自動將其隱藏
+ public string? Password { get; set; }
+ }
+}
diff --git a/JWTdemo/Helpers/AppSettings.cs b/JWTdemo/Helpers/AppSettings.cs
new file mode 100644
index 0000000..2fd661c
--- /dev/null
+++ b/JWTdemo/Helpers/AppSettings.cs
@@ -0,0 +1,7 @@
+namespace JWTdemo.Helpers
+{
+ public class AppSettings
+ {
+ public string? Secret { get; set; }
+ }
+}
diff --git a/JWTdemo/JWTdemo.csproj b/JWTdemo/JWTdemo.csproj
new file mode 100644
index 0000000..24c2170
--- /dev/null
+++ b/JWTdemo/JWTdemo.csproj
@@ -0,0 +1,20 @@
+
+
+
+ net6.0
+ enable
+ enable
+ true
+ $(NoWarn);1591
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/JWTdemo/Models/AuthenticateRequest.cs b/JWTdemo/Models/AuthenticateRequest.cs
new file mode 100644
index 0000000..96d9389
--- /dev/null
+++ b/JWTdemo/Models/AuthenticateRequest.cs
@@ -0,0 +1,13 @@
+namespace JWTdemo.Models;
+
+using System.ComponentModel.DataAnnotations;
+
+
+public class AuthenticateRequest
+{
+ [Required]
+ public string? Username { get; set; }
+
+ [Required]
+ public string? Password { get; set; }
+}
\ No newline at end of file
diff --git a/JWTdemo/Models/AuthenticateResponse.cs b/JWTdemo/Models/AuthenticateResponse.cs
new file mode 100644
index 0000000..120d6ee
--- /dev/null
+++ b/JWTdemo/Models/AuthenticateResponse.cs
@@ -0,0 +1,20 @@
+namespace JWTdemo.Models;
+
+using JWTdemo.Entities;
+
+public class AuthenticateResponse
+{
+ public int Id { get; set; }
+ public string? Name { get; set; }
+ public string? Username { get; set; }
+ public string Token { get; set; }
+
+
+ public AuthenticateResponse(User user, string token)
+ {
+ Id = user.Id;
+ Name = user.Name;
+ Username = user.Username;
+ Token = token;
+ }
+}
\ No newline at end of file
diff --git a/JWTdemo/Program.cs b/JWTdemo/Program.cs
new file mode 100644
index 0000000..84901d6
--- /dev/null
+++ b/JWTdemo/Program.cs
@@ -0,0 +1,130 @@
+using Microsoft.AspNetCore.Authentication.JwtBearer;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+using JWTdemo.Authorization;
+using JWTdemo.Helpers;
+using System.Configuration;
+using System.Reflection;
+using JWTdemo.Services;
+using Microsoft.IdentityModel.Tokens;
+using System.Text;
+using Microsoft.OpenApi.Models;
+
+
+var builder = WebApplication.CreateBuilder(args);
+
+builder.Services.AddCors();
+builder.Services.AddControllers();
+
+// Add services to the container.
+//builder.Services.AddControllersWithViews();
+
+//*------------------------------連線PostgreSQL資料庫-----------------------------------------------
+var connectionString = "Server=localhost;UserID=postgres;Password=vip125125;Database=postgres;port=5432;";
+builder.Services.AddDbContext(opt => opt.UseNpgsql(connectionString));
+
+//*---------------------------------JWT身分驗證-------------------------------------------------------
+{
+ var services = builder.Services;
+ services.AddCors();
+ services.AddControllers();
+ services.Configure(builder.Configuration.GetSection("AppSettings"));
+ var jwtSettings = builder.Configuration.GetSection("AppSettings").Get();
+
+ services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
+ .AddJwtBearer(options =>
+ {
+ options.TokenValidationParameters = new TokenValidationParameters
+ {
+ ValidateIssuer = false,
+ ValidateAudience = false,
+ ValidateIssuerSigningKey = true,
+ //ValidIssuer = "your_issuer",
+ // ValidAudience = "your_audience",
+ ClockSkew = TimeSpan.Zero,
+ IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.Secret))
+ };
+ });
+ services.AddSwaggerGen(c =>
+ {
+ c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApi_data_value", Version = "v1" });
+
+ // Configure Swagger to use JWT authentication
+ c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
+ {
+ Description = "JWT Authorization header using the Bearer scheme",
+ Name = "Authorization",
+ In = ParameterLocation.Header,
+ Type = SecuritySchemeType.ApiKey,
+ Scheme = "Bearer"
+ });
+
+ // 将JWT令牌作为所有端点的要求添加到Swagger文档
+ c.AddSecurityRequirement(new OpenApiSecurityRequirement
+ {
+ {
+ new OpenApiSecurityScheme
+ {
+ Reference = new OpenApiReference
+ {
+ Type = ReferenceType.SecurityScheme,
+ Id = "Bearer"
+ }
+ },
+ new string[] { }
+ }
+ });
+ });
+
+ // configure DI for application services
+ services.AddScoped();
+ services.AddScoped();
+ // 注册 HttpClient 服务
+ services.AddHttpClient();
+}
+
+
+//*---------------------------創專案就有--------------------------
+builder.Services.AddEndpointsApiExplorer();
+builder.Services.AddSwaggerGen();
+
+//*---------------------------WebAPI註解設定--------------------------
+builder.Services.AddSwaggerGen(options =>
+{
+ var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
+ options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename));
+});
+var app = builder.Build();
+//*---------------------------JWT身分驗證------------------------------
+{
+ // global cors policy
+ //在 ASP.NET Core 中啟用 CORS (跨原始來源要求)
+ // Shows UseCors with CorsPolicyBuilder.
+ app.UseCors(x => x
+ .AllowAnyOrigin()
+ .AllowAnyMethod()
+ .AllowAnyHeader());
+
+ // custom jwt auth middleware
+ app.UseMiddleware();
+
+ app.MapControllers();
+}
+
+//-------------------------Swagger初始化-------------------------------------
+if (app.Environment.IsDevelopment())
+{
+ app.UseSwagger();
+ app.UseSwaggerUI(c =>
+ {
+ c.SwaggerEndpoint("/swagger/v1/swagger.json", "JWTdemo"); //API註解開啟
+ });
+}
+
+app.UseHttpsRedirection();
+
+app.UseAuthorization();
+
+app.MapControllers();
+
+app.Run();
diff --git a/JWTdemo/Properties/launchSettings.json b/JWTdemo/Properties/launchSettings.json
new file mode 100644
index 0000000..2580b31
--- /dev/null
+++ b/JWTdemo/Properties/launchSettings.json
@@ -0,0 +1,31 @@
+{
+ "$schema": "https://json.schemastore.org/launchsettings.json",
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:23800",
+ "sslPort": 44344
+ }
+ },
+ "profiles": {
+ "JWTdemo": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "applicationUrl": "https://localhost:7079;http://localhost:5246",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/JWTdemo/Services/SqlContext.cs b/JWTdemo/Services/SqlContext.cs
new file mode 100644
index 0000000..e3e7bea
--- /dev/null
+++ b/JWTdemo/Services/SqlContext.cs
@@ -0,0 +1,25 @@
+using Microsoft.EntityFrameworkCore;
+using JWTdemo.Entities;
+
+
+namespace JWTdemo.Services
+{
+ public class SqlContext : DbContext
+ {
+ public SqlContext(DbContextOptions options) : base(options)
+ {
+ //連接PostgreSQL
+ AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
+ AppContext.SetSwitch("Npgsql.DisableDateTimeInfinityConversions", true);
+ }
+ public DbSetchatuser { get; set; } = null!;
+
+ protected override void OnModelCreating(ModelBuilder builder)
+ {
+ base.OnModelCreating(builder);
+
+ builder.Entity().HasKey(o => new { o.Id }); //Primary Key
+ }
+
+ }
+}
diff --git a/JWTdemo/Services/UserService.cs b/JWTdemo/Services/UserService.cs
new file mode 100644
index 0000000..5798886
--- /dev/null
+++ b/JWTdemo/Services/UserService.cs
@@ -0,0 +1,70 @@
+using JWTdemo.Authorization;
+using JWTdemo.Services;
+using JWTdemo.Entities;
+using JWTdemo.Models;
+
+public interface IUserService
+{
+ AuthenticateResponse? Authenticate(AuthenticateRequest model);
+ IEnumerable GetAll();
+ User? GetById(int id);
+}
+
+public class UserService : IUserService
+{
+ /*
+ // users hardcoded for simplicity, store in a db with hashed passwords in production applications
+ private List user_test = new List
+ {
+ new User { Id = 1, FirstName = "Test", LastName = "User", Username = "test", Password = "test" },
+ new User { Id = 2, FirstName = "Test", LastName = "User", Username = "admin", Password = "admin" }
+ };
+
+ public DbSet user_test { get; set; } = null!;
+
+
+ public List GetUsers ()
+ {
+ return _dbContext.user_test.ToList();
+ }
+
+ */
+
+ private readonly IJwtUtils _jwtUtils;
+
+ public UserService(IJwtUtils jwtUtils, SqlContext dbContext)
+ {
+ _jwtUtils = jwtUtils;
+ _dbContext = dbContext;
+ }
+
+
+ private readonly SqlContext _dbContext;
+
+
+ public AuthenticateResponse? Authenticate(AuthenticateRequest model)
+ {
+ var user = _dbContext.chatuser.SingleOrDefault(x => x.Username == model.Username && x.Password == model.Password);
+
+ // return null if user not found
+ if (user == null) return null;
+
+ // authentication successful so generate jwt token
+ var token = _jwtUtils.GenerateJwtToken(user);
+
+ return new AuthenticateResponse(user, token);
+ }
+
+ public IEnumerable GetAll()
+ {
+ return _dbContext.chatuser;
+ }
+
+ public User? GetById(int id)
+ {
+ return _dbContext.chatuser.FirstOrDefault(x => x.Id == id);
+ }
+
+
+
+}
\ No newline at end of file
diff --git a/JWTdemo/appsettings.Development.json b/JWTdemo/appsettings.Development.json
new file mode 100644
index 0000000..0c208ae
--- /dev/null
+++ b/JWTdemo/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/JWTdemo/appsettings.json b/JWTdemo/appsettings.json
new file mode 100644
index 0000000..f104da0
--- /dev/null
+++ b/JWTdemo/appsettings.json
@@ -0,0 +1,12 @@
+{
+ "AppSettings": {
+ "Secret": "Leo token test jwt park spaces lab 124"
+ },
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}