在.NET Core Web Api中使用JWT并配置UserContext获取用户信息

步骤 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后有一个空格)

总结流程

  1. 安装依赖 → 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>();

需要用到的类中在构造函数中注入就可以使用了

相关推荐
醉城夜风~41 分钟前
CSS元素显示模式(display)
前端·css
AI大模型-小华1 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
做前端的娜娜子4 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅4 小时前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni4 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学5 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端
赵庆明老师6 小时前
Vben精讲:21-详解web-antd:tsconfig.json
前端·json·vim
wc886 小时前
微软EDGE浏览器功能学习
前端·学习·edge
Csvn6 小时前
🎯 原生 `<dialog>` 元素:终于可以扔掉一半的自定义弹窗组件了?
前端