在.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>();

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

相关推荐
前端加油站2 小时前
一份实用的Vue3技术栈代码评审指南
前端·vue.js
睡前要喝豆奶粉2 小时前
在.NET Core Web Api中使用阿里云OSS
阿里云·c#·.netcore
Jonathan Star8 小时前
沉浸式雨天海岸:用A-Frame打造WebXR互动场景
前端·javascript
工业甲酰苯胺9 小时前
实现 json path 来评估函数式解析器的损耗
java·前端·json
老前端的功夫9 小时前
Web应用的永生之术:PWA落地与实践深度指南
java·开发语言·前端·javascript·css·node.js
LilySesy9 小时前
ABAP+WHERE字段长度不一致报错解决
java·前端·javascript·bug·sap·abap·alv
Wang's Blog10 小时前
前端FAQ: Vue 3 与 Vue 2 相⽐有哪些重要的改进?
前端·javascript·vue.js
周杰伦fans11 小时前
.NET Core WebAPI 中 HTTP 请求方法详解:从新手到精通
网络协议·http·.netcore
再希11 小时前
React+Tailwind CSS+Shadcn UI
前端·react.js·ui