.NET Core 中生成 JWT(JSON Web Token)
实现说明
核心依赖
- 需要安装 NuGet 包:
System.IdentityModel.Tokens.Jwt
和Microsoft.AspNetCore.Authentication.JwtBearer
- 需要安装 NuGet 包:
关键组件
JwtSettings
:存储 JWT 配置(密钥、发行人、受众、过期时间等)JwtHelper
:封装 JWT 生成和验证的核心逻辑- 服务注册:在
Program.cs
中配置 JWT 认证服务 - 示例控制器:展示如何生成令牌和使用令牌访问受保护资源
使用流程
- 客户端发送登录请求(用户名 / 密码)
- 服务器验证成功后生成 JWT 令牌并返回
- 客户端后续请求在 HTTP 头部携带
Authorization: Bearer {token}
- 服务器验证令牌有效性并授权访问
安全注意事项
- 密钥(SecretKey)必须足够长且保密,建议至少 16 个字符
- 生产环境中应使用 HTTPS 传输令牌
- 合理设置过期时间(ExpiresMinutes),避免过长
- 可以根据需要添加更多自定义声明(如用户权限、部门等)
{"JwtSettings": {"SecretKey": "YourSuperSecretKeyWithEnoughLength123456", // 至少16个字符"Issuer": "YourCompanyName","Audience": "YourAppName","ExpiresMinutes": 30}
}
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;var builder = WebApplication.CreateBuilder(args);// 1. 读取JWT配置
var jwtSettings = new JwtSettings();
builder.Configuration.GetSection("JwtSettings").Bind(jwtSettings);
builder.Services.AddSingleton(jwtSettings);// 2. 注册JWT工具类
builder.Services.AddSingleton<JwtHelper>();// 3. 配置JWT认证服务
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>{options.TokenValidationParameters = new TokenValidationParameters{ValidateIssuerSigningKey = true,IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.SecretKey)),ValidateIssuer = true,ValidIssuer = jwtSettings.Issuer,ValidateAudience = true,ValidAudience = jwtSettings.Audience,ValidateLifetime = true,ClockSkew = TimeSpan.Zero};});// 添加控制器支持
builder.Services.AddControllers();var app = builder.Build();// 启用认证中间件
app.UseAuthentication();
app.UseAuthorization();app.MapControllers();app.Run();
JwtSettings:
/// <summary>
/// JWT配置参数
/// </summary>
public class JwtSettings
{/// <summary>/// 密钥(必须足够长,建议至少16个字符)/// </summary>public string SecretKey { get; set; }/// <summary>/// 发行人/// </summary>public string Issuer { get; set; }/// <summary>/// 受众/// </summary>public string Audience { get; set; }/// <summary>/// 过期时间(分钟)/// </summary>public int ExpiresMinutes { get; set; }
}
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.IdentityModel.Tokens;/// <summary>
/// JWT工具类
/// </summary>
public class JwtHelper
{private readonly JwtSettings _jwtSettings;public JwtHelper(JwtSettings jwtSettings){_jwtSettings = jwtSettings ?? throw new ArgumentNullException(nameof(jwtSettings));}/// <summary>/// 生成JWT令牌/// </summary>/// <param name="userId">用户ID</param>/// <param name="userName">用户名</param>/// <param name="roles">用户角色</param>/// <returns>JWT令牌</returns>public string GenerateToken(string userId, string userName, IEnumerable<string> roles = null){// 1. 创建声明(Claims)var claims = new List<Claim>{new Claim(ClaimTypes.NameIdentifier, userId), // 用户IDnew Claim(ClaimTypes.Name, userName), // 用户名new Claim(JwtRegisteredClaimNames.Iat, new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64) // 签发时间};// 添加角色声明if (roles != null){foreach (var role in roles){claims.Add(new Claim(ClaimTypes.Role, role));}}// 2. 生成密钥var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtSettings.SecretKey));var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);// 3. 设置令牌过期时间var expires = DateTime.Now.AddMinutes(_jwtSettings.ExpiresMinutes);// 4. 生成令牌var token = new JwtSecurityToken(issuer: _jwtSettings.Issuer,audience: _jwtSettings.Audience,claims: claims,expires: expires,signingCredentials: credentials);// 5. 转换为字符串return new JwtSecurityTokenHandler().WriteToken(token);}/// <summary>/// 验证JWT令牌并返回声明/// </summary>/// <param name="token">JWT令牌</param>/// <returns>声明集合</returns>public ClaimsPrincipal ValidateToken(string token){var tokenHandler = new JwtSecurityTokenHandler();var key = Encoding.UTF8.GetBytes(_jwtSettings.SecretKey);try{var principal = tokenHandler.ValidateToken(token, new TokenValidationParameters{ValidateIssuerSigningKey = true,IssuerSigningKey = new SymmetricSecurityKey(key),ValidateIssuer = true,ValidIssuer = _jwtSettings.Issuer,ValidateAudience = true,ValidAudience = _jwtSettings.Audience,ValidateLifetime = true, // 验证过期时间ClockSkew = TimeSpan.Zero // 不允许有时间偏差}, out var validatedToken);return principal;}catch (Exception ex){// 令牌验证失败(过期、篡改等)throw new SecurityTokenException("无效的JWT令牌", ex);}}
}
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;[ApiController]
[Route("api/[controller]")]
public class AuthController : ControllerBase
{private readonly JwtHelper _jwtHelper;public AuthController(JwtHelper jwtHelper){_jwtHelper = jwtHelper;}/// <summary>/// 用户登录并获取JWT令牌/// </summary>[HttpPost("login")]public IActionResult Login([FromBody] LoginRequest request){// 实际应用中需要验证用户名密码(此处简化)if (request.Username == "admin" && request.Password == "123456"){// 生成JWT令牌(包含用户ID、用户名和角色)var token = _jwtHelper.GenerateToken(userId: "1001",userName: "admin",roles: new List<string> { "Admin", "User" });return Ok(new { Token = token, ExpiresIn = 30 * 60 }); // 有效期(秒)}return Unauthorized("用户名或密码错误");}/// <summary>/// 需要JWT认证的测试接口/// </summary>[HttpGet("protected")][Authorize(Roles = "Admin")] // 仅允许Admin角色访问public IActionResult Protected(){// 从令牌中获取用户信息var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;var userName = User.FindFirst(ClaimTypes.Name)?.Value;return Ok(new { Message = "这是受保护的资源", UserId = userId, UserName = userName });}
}/// <summary>
/// 登录请求模型
/// </summary>
public class LoginRequest
{public string Username { get; set; }public string Password { get; set; }
}
using System.Security.Claims;
using System.Collections.Generic;namespace UserInfoProvider.Abstractions
{/// <summary>/// 用户信息提供者接口/// </summary>public interface IUserInfoProvider{/// <summary>/// 是否已认证/// </summary>bool IsAuthenticated { get; }/// <summary>/// 获取用户ID/// </summary>string UserId { get; }/// <summary>/// 获取用户名/// </summary>string UserName { get; }/// <summary>/// 获取用户角色/// </summary>IEnumerable<string> Roles { get; }/// <summary>/// 获取指定声明的值/// </summary>/// <param name="claimType">声明类型</param>/// <returns>声明值</returns>string GetClaimValue(string claimType);/// <summary>/// 获取所有声明/// </summary>/// <returns>声明集合</returns>IEnumerable<Claim> GetAllClaims();}
}
using Microsoft.AspNetCore.Http;
using System.Security.Claims;
using System.Collections.Generic;
using System.Linq;
using UserInfoProvider.Abstractions;namespace UserInfoProvider.Implementations
{/// <summary>/// 基于HttpContext的用户信息提供者/// </summary>public class HttpContextUserInfoProvider : IUserInfoProvider{private readonly IHttpContextAccessor _httpContextAccessor;private ClaimsPrincipal _user => _httpContextAccessor.HttpContext?.User;/// <summary>/// 构造函数/// </summary>public HttpContextUserInfoProvider(IHttpContextAccessor httpContextAccessor){_httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));}/// <summary>/// 是否已认证/// </summary>public bool IsAuthenticated => _user?.Identity?.IsAuthenticated ?? false;/// <summary>/// 用户ID/// </summary>public string UserId => GetClaimValue(ClaimTypes.NameIdentifier);/// <summary>/// 用户名/// </summary>public string UserName => GetClaimValue(ClaimTypes.Name);/// <summary>/// 用户角色/// </summary>public IEnumerable<string> Roles => GetAllClaims().Where(c => c.Type == ClaimTypes.Role).Select(c => c.Value);/// <summary>/// 获取指定声明的值/// </summary>public string GetClaimValue(string claimType){if (string.IsNullOrEmpty(claimType))throw new ArgumentException("声明类型不能为空", nameof(claimType));return !IsAuthenticated ? null : _user.FindFirstValue(claimType);}/// <summary>/// 获取所有声明/// </summary>public IEnumerable<Claim> GetAllClaims(){return !IsAuthenticated ? Enumerable.Empty<Claim>() : _user.Claims;}}
}