步驟 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後有一個空格)
總結流程
- 安裝依賴 → 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>();