步驟 1:安裝必要的 NuGet 包

需安裝用於 JWT 處理和認證中間件的包:

  • System.IdentityModel.Tokens.Jwt:用於生成和解析 JWT Token。
  • Microsoft.AspNetCore.Authentication.JwtBearer:提供 JWT 認證的中間件支持。

可通過 NuGet 包管理器安裝:

Install-Package System.IdentityModel.Tokens.Jwt Install-Package Microsoft.AspNetCore.Authentication.JwtBearer

步驟 2:配置 JWT 相關參數(appsettings.json)

在配置文件中定義 JWT 的密鑰、發行人、受眾、過期時間等核心參數:

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=localhost;Database=teach;Trusted_Connection=True;TrustServerCertificate=True;"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  // 新增 JWT 配置
  "Jwt": {
    "Issuer": "teach", // 自定義:你的服務名稱(如“員工管理系統”)
    "Audience": "token", // 自定義:前端應用名稱(如“員工系統前端”)
    "SecretKey": "xYjK7pQ2rT9wE4sF6aG8dH1zC3vB5nM0l" // 自定義:32位以上隨機安全密鑰
  }
}

步驟 3:配置 JWT 認證服務(Program.cs)

在依賴注入容器中註冊 JWT 認證中間件,並設置 Token 驗證規則:

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using Serilog;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Reflection;
using System.Text;
using System.Text.Json.Serialization;
using web01.Configurations;
using web01.Data;
using web01.exception;
using web01.services;
using web01.Utils;

// 配置 Serilog(先於其他代碼執行)
Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Information() // 最低日誌級別(Debug/Info/Warn/Error/Fatal)
    .WriteTo.Console() // 輸出到控制枱
    .WriteTo.File(
        path: "logs/app.log", // 日誌文件路徑
        rollingInterval: RollingInterval.Day, // 按天切割文件
        retainedFileCountLimit: 30, // 保留最近 30 天的日誌
        outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] {Message:lj}{NewLine}{Exception}" // 日誌格式
    )
    .CreateLogger();

var builder = WebApplication.CreateBuilder(args);


// 啓用 Serilog(關鍵:替換默認日誌)
builder.Host.UseSerilog();

// Add services to the container.

builder.Services.AddControllers(c=>
{
    c.Filters.Add<GlobalExceptionFilter>();// 註冊全局異常過濾器
}).AddJsonOptions(options =>
{
    // 配置 DateTime 類型的序列化格式
    options.JsonSerializerOptions.Converters.Add(new CustomDateTimeConverter("yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd"));
    // (可選)枚舉轉字符串的轉換器
    //options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
}); 
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new()
    {
        Version = "v1",
        Title = "教學管理系統",
    });
    // 1. 定義 Bearer 認證(JWT Token 格式)
    c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
    {
        Description = "請輸入 JWT Token,格式為:Bearer {token}",
        Name = "Authorization",
        In = ParameterLocation.Header,
        Type = SecuritySchemeType.ApiKey,
        Scheme = "Bearer"
    });

    // 2. 為所有帶 [Authorize] 的接口自動添加認證要求
    c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
    {
        new OpenApiSecurityScheme
        {
            Reference = new OpenApiReference
            {
                Type = ReferenceType.SecurityScheme,
                Id = "Bearer"
            }
        },
        Array.Empty<string>()
    }
});

});
// 註冊 EF Core 上下文,關聯 SQL Server 連接字符串
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
//服務層接口註冊到 .NET 的依賴注入容器
builder.Services.AddScoped<IClazzService, ClazzService>();//註冊班級服務
builder.Services.AddScoped<IEmpService, EmpService>();//註冊員工服務
// 註冊 AutoMapper,掃描包含 MappingProfile 的程序集
builder.Services.AddAutoMapper(
    cfg => {
        cfg.LicenseKey = "eyJhbGciOiJSUzI1NiIsImtpZCI6Ikx1Y2t5UGVubnlTb2Z0d2FyZUxpY2Vuc2VLZXkvYmJiMTNhY2I1OTkwNGQ4OWI0Y2IxYzg1ZjA4OGNjZjkiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2x1Y2t5cGVubnlzb2Z0d2FyZS5jb20iLCJhdWQiOiJMdWNreVBlbm55U29mdHdhcmUiLCJleHAiOiIxNzkyMjgxNjAwIiwiaWF0IjoiMTc2MDc3NDAyNiIsImFjY291bnRfaWQiOiIwMTk5ZjY0YTlmMWY3ZjAxOTJiOGI5YjM0MmY3ZjNlMyIsImN1c3RvbWVyX2lkIjoiY3RtXzAxazd2NHl4YTVydjZqenhhc3lqcTQwdjd0Iiwic3ViX2lkIjoiLSIsImVkaXRpb24iOiIwIiwidHlwZSI6IjIifQ.Jx1tgVJGiq-u6oRxSqsvd1QMSWvBQfDo7yD0yqyxDC1A-iaVfAjIdvyKgQx6aqpj_OPCWenpTNU2Mnc8lAqpwS_mrck9SEFn9CLud36-qIwDzVnLQCqFEeJQmfSkWfh1CFkkponNpvFbFkpPbtpojnlnC1mOqeOE521jo--0-5u_OLJKH8sZNa8ALtadgDUesSgzVWAVlHZNLmnzShO4oICj82J36FmInZjRvzrG_SjDjJGMXCCgIY0zrQqibFNHTYn09sFbc-sSHC-X_M6CLoL5ucPV8O6FZzpeE3aCq2xetLMRVpXjuONAxKVRX-YnyhNU_Hr96zLWTvtKeSl_xA"; // 替換為實際密鑰
    },
    typeof(web01.Configurations.MappingProfile).Assembly
);

// 註冊JWT工具類(單例模式,避免重複讀取配置)
builder.Services.AddSingleton<JwtUtils>();

// 添加JWT認證服務
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        // 配置JWT驗證參數
        options.TokenValidationParameters = new TokenValidationParameters
        {
            // 驗證發行人(需與生成令牌時的Issuer一致)
            ValidateIssuer = true,
            ValidIssuer = builder.Configuration["Jwt:Issuer"],

            // 驗證受眾(需與生成令牌時的Audience一致)
            ValidateAudience = true,
            ValidAudience = builder.Configuration["Jwt:Audience"],

            // 驗證密鑰(需與生成令牌時的密鑰一致,且長度至少32位)
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(
                Encoding.UTF8.GetBytes(builder.Configuration["Jwt:SecretKey"])
            ),

            // 驗證令牌有效期
            ValidateLifetime = true,
            ClockSkew = TimeSpan.Zero // 禁用令牌過期時間的緩衝(默認5分鐘)
        };
    });



var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI(c => 
    {
        c.SwaggerEndpoint("/swagger/v1/swagger.json","v1");
    });
}



app.UseHttpsRedirection();

app.UseStaticFiles();

// 3. 啓用認證中間件(需在UseAuthorization之前)
app.UseAuthentication();

app.UseAuthorization();

app.MapControllers();
Log.Information("應用啓動完成,測試 Serilog 輸出"); // 直接調用 Log.Information()

app.Run();

步驟 4:創建 JWT Token 生成工具類

封裝生成 Token 的邏輯,便於在登錄接口中調用:

using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;

namespace web01.Utils
{
    public class JwtUtils
    {
        private readonly IConfiguration _configuration;
        private readonly string _issuer;       // 發行人(從配置讀取)
        private readonly string _audience;     // 受眾(從配置讀取)
        private readonly string _secretKey;    // 密鑰(從配置讀取)

        public JwtUtils(IConfiguration configuration)
        {
            _configuration = configuration;
            // 從appsettings.json讀取JWT配置
            _issuer = _configuration["Jwt:Issuer"] ?? throw new ArgumentNullException("Jwt:Issuer未配置");
            _audience = _configuration["Jwt:Audience"] ?? throw new ArgumentNullException("Jwt:Audience未配置");
            _secretKey = _configuration["Jwt:SecretKey"] ?? throw new ArgumentNullException("Jwt:SecretKey未配置");

            // 驗證密鑰長度(至少32位,確保加密安全)
            if (_secretKey.Length < 32)
            {
                throw new ArgumentException("Jwt:SecretKey長度必須≥32位");
            }
        }

        /// <summary>
        /// 生成JWT令牌
        /// </summary>
        /// <param name="claims">自定義用户信息(如ID、用户名等)</param>
        /// <param name="expiresHours">令牌有效期(小時),默認1小時</param>
        /// <returns>JWT令牌字符串</returns>
        public string GenerateToken(List<Claim> claims, int expiresHours = 1)
        {
            // 1. 構建密鑰
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_secretKey));
            // 2. 簽名憑據(使用HmacSha256算法)
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
            // 3. 過期時間
            var expires = DateTime.Now.AddHours(expiresHours);

            // 4. 創建JWT令牌
            var token = new JwtSecurityToken(
                issuer: _issuer,
                audience: _audience,
                claims: claims,
                expires: expires,
                signingCredentials: creds
            );

            // 5. 生成令牌字符串
            return new JwtSecurityTokenHandler().WriteToken(token);
        }

        /// <summary>
        /// 解析JWT令牌,提取Claims(用户信息)
        /// </summary>
        /// <param name="token">JWT令牌字符串</param>
        /// <returns>包含用户信息的ClaimsPrincipal</returns>
        /// <exception cref="SecurityTokenException">令牌無效/過期時拋出</exception>
        public ClaimsPrincipal ParseToken(string token)
        {
            try
            {
                var tokenHandler = new JwtSecurityTokenHandler();
                // 驗證令牌並解析
                var principal = tokenHandler.ValidateToken(token, GetTokenValidationParameters(), out var validatedToken);
                return principal;
            }
            catch (Exception ex)
            {
                throw new SecurityTokenException($"令牌解析失敗:{ex.Message}", ex);
            }
        }

        /// <summary>
        /// 驗證JWT令牌是否有效(包含簽名、過期時間、發行人、受眾校驗)
        /// </summary>
        /// <param name="token">JWT令牌字符串</param>
        /// <returns>有效返回true,否則false</returns>
        public bool ValidateToken(string token)
        {
            try
            {
                var tokenHandler = new JwtSecurityTokenHandler();
                tokenHandler.ValidateToken(token, GetTokenValidationParameters(), out _);
                return true;
            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        /// 獲取JWT驗證參數(複用邏輯)
        /// </summary>
        private TokenValidationParameters GetTokenValidationParameters()
        {
            return new TokenValidationParameters
            {
                // 驗證發行人
                ValidateIssuer = true,
                ValidIssuer = _issuer,

                // 驗證受眾
                ValidateAudience = true,
                ValidAudience = _audience,

                // 驗證簽名
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_secretKey)),

                // 驗證過期時間
                ValidateLifetime = true,
                ClockSkew = TimeSpan.Zero, // 禁用默認的5分鐘緩衝時間

                // 允許的算法(限制為HmacSha256)
                ValidAlgorithms = new[] { SecurityAlgorithms.HmacSha256 }
            };
        }
    }
}

並在

Program.cs中註冊為單例服務:

builder.Services.AddSingleton<JwtUtils>();

步驟 5:在登錄接口中生成並返回 Token在登錄控制器中,驗證用户身份後調用

JwtUtils生成 Token:

using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
using web01.DTOs;
using web01.services;
using web01.Utils;
using System.Security.Claims;

namespace web01.Controllers
{
    /// <summary>
    /// 公共接口
    /// </summary>
    [Route("api/[controller]")]
    [ApiController]
    public class PublicController:ControllerBase
    {
        private readonly IEmpService _empService;
        private readonly JwtUtils _jwtUtils;
        public PublicController(IEmpService empService, JwtUtils jwtUtils)
        {
            _empService = empService;
            _jwtUtils = jwtUtils;
        }

        


        /// <summary>
        /// 登錄接口
        /// </summary>
        /// <returns></returns>
        [HttpPost("login")]
        public async Task<ActionResult<Result<String>>> login([FromBody] LoginDTO loginDTO)
        {
            // 1. 參數校驗
            if (loginDTO == null || string.IsNullOrEmpty(loginDTO.Username) || string.IsNullOrEmpty(loginDTO.Password))
            {
                return BadRequest(Result<string>.Error("用户名或密碼不能為空"));
            }

            // 2. 驗證用户
            var emp = await _empService.LoginAsync(loginDTO.Username, loginDTO.Password);
            if (emp == null)
            {
                return Unauthorized(Result<string>.Error("用户名或密碼錯誤"));
            }

            // 3. 構建用户信息(Claims)
            var claims = new List<Claim>
    {
        new System.Security.Claims.Claim("empId", emp.Id.ToString()),       // 員工ID
        new System.Security.Claims.Claim("username", emp.Username),         // 用户名
        new System.Security.Claims.Claim(ClaimTypes.Name, emp.Name),        // 真實姓名(標準Claim類型)
        new System.Security.Claims.Claim("deptId", emp.DeptId?.ToString() ?? "") // 部門ID(可選)
    };

            // 4. 生成JWT令牌(使用工具類)
            var token = _jwtUtils.GenerateToken(claims, expiresHours: 2); // 有效期2小時

            return Ok(Result<string>.Success(token));
        }
    }
}

步驟 6:在需要認證的接口中啓用 JWT 驗證通過

[ApiController]
[Route("api/[controller]")]
[Authorize] // 控制器級認證:所有接口需攜帶有效Token
public class UserController : ControllerBase
{
    [HttpGet("my-info")]
    public IActionResult GetMyInfo()
    {
        // 從Token中提取用户信息(示例:提取userId)
        var userIdStr = HttpContext.User.FindFirstValue("userId");
        if (int.TryParse(userIdStr, out int userId))
        {
            // 業務邏輯:根據userId查詢用户信息
            return Ok(new { userId, message = "已通過JWT認證" });
        }
        return BadRequest("用户信息異常");
    }
}

步驟 7:客户端攜帶 Token 發起請求客户端需在請求頭中添加

Authorization: Bearer {你的Token},示例(Swagger/Postman):

  • 請求頭參數名:Authorization
  • 參數值:Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...(Bearer後有一個空格)

總結流程

  1. 安裝依賴 → 2. 配置 JWT 參數 → 3. 註冊認證服務 → 4. 封裝 Token 生成工具 → 5. 登錄接口生成 Token → 6. 受保護接口啓用 [Authorize] → 7. 客户端攜帶 Token 請求

UserContext上下文

封裝工具類

using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;

namespace Water.Infrastructure.Common.Utils
{
    /// <summary>
    /// 用户上下文工具類:封裝從Token解析用户身份的邏輯
    /// </summary>
    public class UserContext
    {
        private readonly IHttpContextAccessor _httpContextAccessor;

        public UserContext(IHttpContextAccessor httpContextAccessor)
        {
            _httpContextAccessor = httpContextAccessor;
        }

        /// <summary>
        /// 從Token中解析用户ID(若不存在則返回null)
        /// </summary>
        /// <returns>用户ID(long類型),若解析失敗返回null</returns>
        public long? GetUserId()
        {
            var userIdClaim = _httpContextAccessor.HttpContext?.User.FindFirst(ClaimTypes.NameIdentifier);
            if (userIdClaim == null || string.IsNullOrEmpty(userIdClaim.Value))
            {
                return null;
            }

            if (long.TryParse(userIdClaim.Value, out var userId))
            {
                return userId;
            }

            return null;
        }

        /// <summary>
        /// 從Token中解析用户ID(若不存在則拋出異常)
        /// </summary>
        /// <exception cref="UnauthorizedAccessException">當Token中無用户ID時拋出</exception>
        /// <returns>用户ID(long類型)</returns>
        public long GetUserIdOrThrow()
        {
            var userId = GetUserId();
            if (!userId.HasValue)
            {
                throw new UnauthorizedAccessException("用户身份驗證失敗,未找到用户ID");
            }
            return userId.Value;
        }
    }
}

在program.cs中註冊

// 註冊UserContext工具類
builder.Services.AddScoped<UserContext>();

需要用到的類中在構造函數中注入就可以使用了