JWT(JSON Web Token)常被误解为一种加密后的用户凭证,实际上它只做签名而不做加密。令牌本身由三段Base64Url字符串拼接而成,任何拿到令牌的人都可以解开载荷部分查看内容,因此绝不能把密码或敏感身份数据直接放进JWT。理解这一点是正确实现Token验证的前提。

一、JWT认证的核心机制
JWT的结构可以从一个典型令牌说起。形如 xxxxx.yyyyy.zzzzz 的字符串中,第一段是头部,声明令牌类型和签名算法,例如 HS256;第二段是载荷,存放签发方、过期时间、用户标识等声明;第三段是签名,由服务端使用密钥对前两段进行哈希计算得到。
这种设计的优势在于服务端无需保存会话状态。传统Cookie会话需要在内存或数据库中记录SessionID,而JWT把用户信息放在令牌里,服务端只要验证签名和有效期就能识别用户身份。付出的代价也很直接:令牌一旦签发,在过期之前无法主动吊销,除非引入额外的刷新令牌或黑名单机制。
签名算法选择上,HS256使用对称密钥,适合单体服务或内部系统;RS256使用非对称密钥对,私钥签发、公钥验证,更适合微服务网关与资源服务分离的场景。无论哪种算法,密钥泄露都会导致攻击者伪造令牌。
二、在ASP.NET Core Web API中签发JWT Token
要在 ASP.NET Core 项目中使用JWT认证,首先需要安装 Microsoft.AspNetCore.Authentication.JwtBearer 包。它提供中间件和默认配置,能够自动从请求的 Authorization 头中提取 Bearer Token 并完成验证。
安装完成后,在 appsettings.json 中配置密钥、签发方、接收方和过期时间。密钥长度必须足够,使用HS256时建议至少32字节,否则运行时会直接报错。接着在 Program.cs 中注册认证服务,并设置令牌验证参数。
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))
};
});
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
生成令牌通常放在登录接口里。用户提交用户名和密码后,接口先校验凭证,再构造Claims集合。这里的 Claim 是用户身份的表述,可以包含用户ID、邮箱、角色等。随后用 JwtSecurityTokenHandler 把令牌写成字符串返回给客户端。
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.IdentityModel.Tokens;
using System.Text;
[ApiController]
[Route("api/auth")]
public class AuthController : ControllerBase
{
private readonly IConfiguration _configuration;
public AuthController(IConfiguration configuration)
{
_configuration = configuration;
}
[HttpPost("login")]
public IActionResult Login(LoginRequest request)
{
// 这里应校验数据库中的用户密码,示例仅做说明
if (request.Username != "admin" || request.Password != "123456")
{
return Unauthorized();
}
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, "1001"),
new Claim(JwtRegisteredClaimNames.Email, "admin@ipipp.com"),
new Claim(ClaimTypes.Role, "admin")
};
var key = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: _configuration["Jwt:Issuer"],
audience: _configuration["Jwt:Audience"],
claims: claims,
expires: DateTime.UtcNow.AddMinutes(120),
signingCredentials: creds
);
var tokenString = new JwtSecurityTokenHandler().WriteToken(token);
return Ok(new { token = tokenString, expires = DateTime.UtcNow.AddMinutes(120) });
}
}
public class LoginRequest
{
public string Username { get; set; }
public string Password { get; set; }
}
三、保护API控制器并验证Token
令牌返回给客户端后,后续请求需要在 HTTP 头中加入 Authorization: Bearer 令牌字符串。在控制器或具体动作上添加 [Authorize] 特性后,JwtBearer 中间件会在请求到达Action前执行验证,包括签名是否合法、令牌是否过期、签发方和接收方是否与配置一致。
验证通过的请求可以直接通过 User 对象读取声明。下面的控制器演示了获取当前用户ID和角色,并返回订单数据。使用 [AllowAnonymous] 可以让某个动作跳过认证,通常用于登录或健康检查接口。
using Microsoft.AspNetCore.Authorization;
using System.Security.Claims;
[ApiController]
[Route("api/orders")]
public class OrdersController : ControllerBase
{
[HttpGet]
[Authorize]
public IActionResult GetOrders()
{
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var email = User.FindFirstValue(ClaimTypes.Email);
var role = User.FindFirstValue(ClaimTypes.Role);
return Ok(new { userId, email, role, orders = new[] { "A1001", "A1002" } });
}
[HttpGet("public")]
[AllowAnonymous]
public IActionResult PublicInfo()
{
return Ok(new { message = "无需认证" });
}
}
如果需要按角色或策略限制访问,可以配合 AddAuthorization 注册策略,再使用 [Authorize(Policy = "AdminOnly")] 或 [Authorize(Roles = "admin")]。角色声明需要与登录时加入的 Claim 类型一致,否则会出现认证通过但授权失败的情况。
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AdminOnly", policy => policy.RequireRole("admin"));
});
四、JWT安全实践与常见问题
安全存储是JWT落地时最容易出问题的环节。把令牌放在 localStorage 里容易受到XSS攻击,攻击者一旦注入脚本即可读取令牌并冒用身份。更稳妥的做法是将令牌放入 HttpOnly Cookie,前端代码无法读取,能显著降低XSS导致的凭证泄露风险,但需要配合CSRF防护。移动端没有Cookie机制,通常将令牌保存在系统安全存储中。
另一个常见问题是密钥管理。开发环境里把密钥写死在配置文件中虽然方便,但生产环境应使用环境变量、密钥管理服务或配置中心注入。如果使用对称签名,密钥必须足够长且定期轮换;轮换时旧令牌会快速失效,需要设计合理的过渡策略。
过期时间设置也需要平衡。过期太短用户体验差,过期太长风险窗口大。一般可以将访问令牌设为15分钟到2小时,同时使用刷新令牌延长登录周期。刷新令牌更敏感,应当只发送给认证服务,并保存在服务端或安全存储中。JWT本身无法凭空吊销,需要黑名单或版本号机制应对用户退出、改密等场景。
最后注意传输安全。Authorization头中的令牌如果不走HTTPS,可能被中间人截获。测试时不要把真实密钥提交到代码仓库,也不要打印完整令牌到日志。Web API 应统一在网关或应用层强制HTTPS,从源头上减少泄露面。
JWT认证ASP.NET CoreToken验证修改时间:2026-09-21 16:02:53