被黑客爆破的API崩溃了:“100个请求/秒?这要算到宇宙热寂!”
// JWT核武器启动 services.AddJwtBearer(options => { options.RequireHttpsMetadata = true; // HTTPS防弹衣 options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, // ️ 签名验证 IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("你的密钥")), ValidateAudience = false, ValidateIssuer = false, ClockSkew = TimeSpan.Zero // ⏰ 0秒宽容期,过期即挂! }; });
这就是JWT鉴权的“核武器”——ASP.NET Core驱动的零信任炼金术!
核心概念:
exp
字段控制令牌有效期(如1小时过期)role
/scope
字段控制API访问权限步骤1:从零开始搭建JWT鉴权框架
// 安装NuGet包
// Package Manager命令:
// Install-Package Microsoft.AspNetCore.Authentication.JwtBearer
// Install-Package System.IdentityModel.Tokens.Jwt
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// JWT配置:签名密钥、有效期、角色权限
var key = Encoding.ASCII.GetBytes("你的密钥必须超过256位!"); // 密钥长度越长越安全!
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true, // 必须验证签名
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false, // 生产环境需验证Issuer
ValidateAudience = false, // 生产环境需验证Audience
ClockSkew = TimeSpan.Zero // ⏰ 0秒宽容期,精准过期!
};
});
// 接入控制器与数据库
services.AddControllers();
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("Default")));
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseAuthentication(); // 鉴权中间件
app.UseAuthorization(); // 授权中间件
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
// 生成JWT的控制器示例
[ApiController]
[Route("api/[controller]")]
public class AuthController : ControllerBase
{
[HttpPost("login")]
public IActionResult Login([FromBody] LoginModel model)
{
// 验证用户凭证(此处省略数据库查询)
var user = new User { Id = 1, Username = "admin", Role = "admin" };
// 生成JWT
var tokenHandler = new JwtSecurityTokenHandler();
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, user.Username), // 用户名
new Claim("role", user.Role), // 角色
new Claim("custom_claim", "你的自定义数据") // 自定义Claim
}),
Expires = DateTime.UtcNow.AddHours(1), // 1小时过期
SigningCredentials = new SigningCredentials(
new SymmetricSecurityKey(Encoding.UTF8.GetBytes("你的密钥")),
SecurityAlgorithms.HmacSha256Signature) // 使用HS256算法
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return Ok(new { Token = tokenHandler.WriteToken(token) }); // 返回令牌
}
}
步骤2:动态刷新令牌机制
// 刷新令牌:防止用户频繁登录
[ApiController]
[Route("api/[controller]")]
public class RefreshTokenController : ControllerBase
{
private readonly IConfiguration _config;
private readonly IHttpContextAccessor _httpContextAccessor;
public RefreshTokenController(IConfiguration config, IHttpContextAccessor httpContextAccessor)
{
_config = config;
_httpContextAccessor = httpContextAccessor;
}
[HttpPost("refresh")]
public IActionResult RefreshToken([FromBody] RefreshTokenModel model)
{
// 验证旧令牌是否有效
var oldToken = _httpContextAccessor.HttpContext.Request.Headers["Authorization"].FirstOrDefault()?.Split(' ').Last();
if (oldToken == null) return Unauthorized();
// 验证RefreshToken是否在数据库中存在且未过期
var refreshToken = _context.RefreshTokens.Find(model.RefreshToken);
if (refreshToken == null || refreshToken.ExpiryDate < DateTime.Now)
return Unauthorized("Refresh token expired or invalid");
// 生成新令牌
var user = _context.Users.Find(refreshToken.UserId);
var newToken = GenerateJwtToken(user);
// 更新RefreshToken(或生成新RefreshToken)
refreshToken.ExpiryDate = DateTime.Now.AddHours(24); // 24小时刷新有效期
_context.SaveChanges();
return Ok(new { Token = newToken });
}
private string GenerateJwtToken(User user)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_config["Jwt:Key"]);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, user.Username),
new Claim("role", user.Role)
}),
Expires = DateTime.UtcNow.AddHours(1),
SigningCredentials = new SigningCredentials(
new SymmetricSecurityKey(key),
SecurityAlgorithms.HmacSha256Signature)
};
return tokenHandler.WriteToken(tokenHandler.CreateToken(tokenDescriptor));
}
}
步骤3:角色权限控制(RBAC模型)
// 基于角色的权限控制
[ApiController]
[Route("api/[controller]")]
[Authorize(Roles = "admin")] // 仅允许admin角色访问
public class AdminController : ControllerBase
{
[HttpGet("secret")]
public IActionResult Secret()
{
return Ok("只有管理员能看到这个秘密!");
}
}
// 自定义Policy策略(更灵活的权限控制)
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthorization(options =>
{
options.AddPolicy("SuperAdminPolicy", policy =>
{
policy.RequireRole("superadmin"); // 要求superadmin角色
policy.RequireClaim("custom_claim", "special_value"); // 需要特定自定义Claim
});
});
}
// 在控制器中使用Policy
[Authorize(Policy = "SuperAdminPolicy")]
public class SuperAdminController : ControllerBase
{
// 只有满足Policy的用户才能访问
}
}
步骤4:跨域(CORS)与HTTPS“防弹衣”
// 跨域配置:只允许特定域名访问
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseCors(options =>
{
options.WithOrigins("https://your-frontend.com") // 允许的域名
.AllowAnyHeader()
.AllowAnyMethod();
});
// HTTPS强制:非HTTPS请求返回403
app.UseHsts();
app.UseHttpsRedirection();
}
技巧1:动态密钥轮换(滚动密钥)
// 滚动密钥:每小时更换密钥,防止彩虹表攻击
public class RollingKeyJwtBearerOptions : JwtBearerOptions
{
private readonly IConfiguration _configuration;
private readonly IDateTimeProvider _dateTimeProvider;
public RollingKeyJwtBearerOptions(IConfiguration configuration, IDateTimeProvider dateTimeProvider)
{
_configuration = configuration;
_dateTimeProvider = dateTimeProvider;
}
public override void Configure(JwtBearerOptions options)
{
// 动态获取当前密钥(根据时间分段)
var currentTime = _dateTimeProvider.UtcNow;
var keySegment = (currentTime.Hour / 2).ToString(); // 每2小时更换密钥
var key = _configuration[$"Jwt:Key_{keySegment}"];
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(key)),
// ...其他配置
};
}
}
技巧2:令牌绑定(防止重放攻击)
// 令牌绑定:每个令牌绑定客户端IP与UserAgent
public class JwtTokenService
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IMemoryCache _cache;
public JwtTokenService(IHttpContextAccessor httpContextAccessor, IMemoryCache cache)
{
_httpContextAccessor = httpContextAccessor;
_cache = cache;
}
public string GenerateTokenWithBinding(string userId)
{
var clientIp = _httpContextAccessor.HttpContext.Connection.RemoteIpAddress?.ToString();
var userAgent = _httpContextAccessor.HttpContext.Request.Headers["User-Agent"].ToString();
var tokenHandler = new JwtSecurityTokenHandler();
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.NameIdentifier, userId),
new Claim("client_ip", clientIp), // 绑定IP
new Claim("user_agent", userAgent) // 绑定UserAgent
}),
// ...其他配置
};
var token = tokenHandler.CreateToken(tokenDescriptor);
_cache.Set(userId, token.Id, TimeSpan.FromHours(1)); // 缓存令牌ID与绑定信息
return tokenHandler.WriteToken(token);
}
public bool ValidateTokenBinding(string token)
{
var handler = new JwtSecurityTokenHandler();
var jsonToken = handler.ReadToken(token) as JwtSecurityToken;
var userId = jsonToken.Claims.First(claim => claim.Type == "nameid").Value;
var cachedTokenId = _cache.Get<string>(userId);
// 验证令牌ID与绑定信息是否匹配
return cachedTokenId == jsonToken.Id &&
jsonToken.Claims.Any(claim => claim.Type == "client_ip" && claim.Value == _httpContextAccessor.HttpContext.Connection.RemoteIpAddress?.ToString());
}
}
技巧3:分布式令牌存储(Redis黑名单)
// 黑名单:全局禁用令牌(如用户注销)
public class JwtBlacklistService
{
private readonly IRedisCache _redis;
public JwtBlacklistService(IRedisCache redis)
{
_redis = redis;
}
public void AddToBlacklist(string tokenId)
{
_redis.SetAsync($"blacklist:{tokenId}", "true", TimeSpan.FromHours(1)); // 加入黑名单1小时
}
public bool IsBlacklisted(string tokenId)
{
return _redis.GetAsync($"blacklist:{tokenId}") != null;
}
}
// 在验证流程中检查黑名单
public class JwtTokenValidator : ITokenValidator
{
private readonly JwtBlacklistService _blacklistService;
public JwtTokenValidator(JwtBlacklistService blacklistService)
{
_blacklistService = blacklistService;
}
public bool ValidateToken(string token)
{
var handler = new JwtSecurityTokenHandler();
var jsonToken = handler.ReadToken(token) as JwtSecurityToken;
if (jsonToken == null) return false;
// 检查是否在黑名单
if (_blacklistService.IsBlacklisted(jsonToken.Id))
return false;
return true;
}
}
问题1:令牌被盗?用“JTI”字段当“追踪器”!
// JTI(JWT ID):唯一标识每个令牌,方便追踪
var tokenDescriptor = new SecurityTokenDescriptor
{
JwtId = Guid.NewGuid().ToString(), // 生成唯一JTI
// ...其他配置
};
// 在黑名单中存储JTI
_blacklistService.AddToBlacklist(token.Id);
问题2:权限越界?用“Claim Policy”当“防暴盾”!
// 自定义Policy验证多个Claim
services.AddAuthorization(options =>
{
options.AddPolicy("AdminOrSuperAdmin", policy =>
{
policy.RequireAssertion(context =>
{
var roles = context.User.Claims
.Where(c => c.Type == "role")
.Select(c => c.Value);
return roles.Contains("admin") || roles.Contains("superadmin");
});
});
});
通过本文的方案,你的API可以:
未来展望: