🔥关注墨瑾轩,带你探索编程的奥秘!🚀
🔥超萌技术攻略,轻松晋级编程高手🚀
🔥技术宝库已备好,就等你来挖掘🚀
🔥订阅墨瑾轩,智趣学习不孤单🚀
🔥即刻启航,编程之旅更有趣🚀
在微服务和前后端分离的架构中,传统的 Session 认证早已力不从心。而 JWT(JSON Web Token)凭借其无状态、跨域友好、可扩展性强等特性,成为现代 Web 应用的“身份通行证”。本文将通过 JWT Token 的生成、验证、角色授权、自定义策略 四大核心模块,带你在 ASP.NET Core 中构建一套从“登录认证”到“细粒度权限控制”的完整解决方案。
第一章:JWT Token 的“三体法则”
1.1 JWT 的结构解析
JWT 由三部分组成:
- Header:声明算法和 Token 类型(如
HS256
)。 - Payload:携带用户信息(如用户名、角色、过期时间)。
- Signature:签名验证 Token 的完整性。
{
"alg": "HS256",
"typ": "JWT"
}
.
{
"sub": "1234567890",
"name": "John Doe",
"role": "Admin",
"exp": 1516239022
}
.
HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload), secretKey)
第二章:从零搭建 JWT 认证体系
2.1 安装依赖包
使用 NuGet 安装 JWT Bearer 认证中间件:
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
2.2 配置 JWT 认证服务
在 Program.cs
(或旧版 Startup.cs
)中配置 JWT 认证服务:
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// 配置 JWT 认证服务
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"], // 从 appsettings.json 读取
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(builder.Configuration["Jwt:SecretKey"])),
ClockSkew = TimeSpan.Zero // 严格校验过期时间
};
});
// 添加授权服务
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AdminOnly", policy =>
policy.RequireRole("Admin")); // 基于角色的授权策略
});
var app = builder.Build();
// 启用认证和授权中间件
app.UseAuthentication();
app.UseAuthorization();
// 其他中间件...
app.Run();
}
}
关键注释:
ValidIssuer
/ValidAudience
:签发者和受众,需与生成 Token 时一致。IssuerSigningKey
:对称密钥,务必保密!建议通过环境变量或 Azure Key Vault 管理。ClockSkew
:默认允许 5 分钟时钟偏差,此处设为TimeSpan.Zero
以严格校验过期时间。
第三章:JWT Token 的“生成艺术”
3.1 登录接口生成 Token
[ApiController]
[Route("api/[controller]")]
public class AuthController : ControllerBase
{
private readonly IConfiguration _config;
public AuthController(IConfiguration config)
{
_config = config;
}
[HttpPost("login")]
public IActionResult Login([FromBody] LoginModel model)
{
// 模拟用户验证
if (model.Username != "admin" || model.Password != "password")
return Unauthorized();
// 构建 Claims
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, model.Username),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(ClaimTypes.Role, "Admin") // 添加角色
};
// 配置 Token 参数
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:SecretKey"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var expires = DateTime.Now.AddDays(7); // Token 有效期
// 生成 Token
var token = new JwtSecurityToken(
issuer: _config["Jwt:Issuer"],
audience: _config["Jwt:Audience"],
claims: claims,
expires: expires,
signingCredentials: creds
);
return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(token),
expiration = token.ValidTo
});
}
}
关键注释:
JwtRegisteredClaimNames
:标准声明,如sub
(主体)、jti
(唯一标识)。ClaimTypes.Role
:添加角色信息,用于后续授权。expires
:Token 过期时间,建议结合刷新 Token 机制延长会话。
第四章:权限控制的“双重奏”
4.1 基于角色的权限控制
使用 [Authorize(Roles = "Admin")]
限制特定角色访问:
[ApiController]
[Route("api/[controller]")]
[Authorize(Roles = "Admin")] // 仅 Admin 可访问
public class AdminController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
return Ok("欢迎管理员!这是机密信息。");
}
}
4.2 自定义授权策略
在 Program.cs
中定义基于策略的授权规则:
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("Over18", policy =>
policy.RequireAssertion(context =>
{
var user = context.User;
var birthYearClaim = user.FindFirst("BirthYear");
if (birthYearClaim == null)
return false;
int birthYear = int.Parse(birthYearClaim.Value);
int currentYear = DateTime.Now.Year;
return currentYear - birthYear >= 18;
}));
});
在控制器中使用策略:
[ApiController]
[Route("api/[controller]")]
[Authorize(Policy = "Over18")] // 仅 18 岁以上用户可访问
public class ContentController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
return Ok("18+ 内容,请勿未成年人访问!");
}
}
第五章:JWT 的“防坑指南”
5.1 常见问题及解决方案
5.1.1 问题:返回 401 未授权
原因:
- Token 过期
- 签名错误(密钥不匹配)
- 请求头未携带 Token
解决方案:
- 检查
SecretKey
是否与生成 Token 时一致 - 在
AddJwtBearer
中添加日志记录:.AddJwtBearer(options => { options.Events = new JwtBearerEvents { OnAuthenticationFailed = context => { Console.WriteLine("JWT 认证失败: " + context.Exception.Message); return Task.CompletedTask; } }; })
5.1.2 问题:角色信息无法获取
原因:
- 未在 Token 中添加
ClaimTypes.Role
- 未启用
SaveToken = true
解决方案:
- 确保生成 Token 时包含角色声明
- 在
AddJwtBearer
中启用SaveToken
:options.SaveToken = true;
第六章:JWT 的“进阶玩法”
6.1 刷新 Token 机制
实现 Token 刷新以延长会话:
[HttpPost("refresh")]
public async Task<IActionResult> Refresh([FromBody] RefreshTokenModel model)
{
// 验证旧 Token
var principal = GetPrincipalFromExpiredToken(model.Token);
if (principal == null)
return Unauthorized();
// 验证 RefreshToken(需自行存储,如数据库或 Redis)
var storedRefreshToken = _context.RefreshTokens
.FirstOrDefault(t => t.Token == model.RefreshToken && t.UserId == principal.Identity.Name);
if (storedRefreshToken == null || storedRefreshToken.Expires < DateTime.Now)
return Unauthorized();
// 生成新 Token
var newToken = GenerateToken(principal.Claims.ToList());
var newRefreshToken = GenerateRefreshToken(principal.Identity.Name);
// 更新 RefreshToken
storedRefreshToken.Token = newRefreshToken;
storedRefreshToken.Expires = DateTime.Now.AddDays(7);
return Ok(new
{
token = newToken,
refreshToken = newRefreshToken
});
}
关键注释:
GenerateToken
:与登录时逻辑相同。GenerateRefreshToken
:生成长生命周期的 RefreshToken,需安全存储。
JWT 的“终极奥义”
“JWT 不是万能的,但没有 JWT 是万万不能的!”
- 无状态认证:JWT 的核心优势,适用于分布式系统。
- 角色+策略:灵活组合满足复杂权限需求。
- 刷新机制:平衡安全性与用户体验。
最后的吐槽
- “Token 生成后怎么存?Local Storage vs Cookie?”
- “密钥泄露了怎么办?快去换 Key Vault!”
- “JWT 能加密吗?其实 JWE 才是正解!”