步骤 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 的密钥、发行人、受众、过期时间等核心参数:
cs
{
"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 验证规则:
cs
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 的逻辑,便于在登录接口中调用:
cs
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:
cs
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 验证通过
cs
[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上下文
封装工具类
cs
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中注册
cs
// 注册UserContext工具类
builder.Services.AddScoped<UserContext>();