一、JWT 是什么
JWT(JSON Web Token) :是一种无状态 的身份认证令牌,登录成功后后端生成一段加密字符串,前端后续请求带 Token 鉴权,不用再每次查库校验账号密码。
二、JWT 三段结构(用 . 分隔)
- Header 头部加密算法、令牌类型,默认 HS256 对称加密。
- Payload 载荷 存放用户非敏感信息 :用户 ID、账号、角色、过期时间等。禁止存密码、隐私数据。
- Signature 签名 头部 + 载荷 + 密钥 加密生成,防止 Token 被篡改。
三、JWT 认证流程
- 前端登录提交账号密码
- 后端校验账号密码正确
- 后端生成 JWT Token 返回前端
- 前端把 Token 存在本地(LocalStorage)
- 后续每次请求 Header 携带 Token
- 后端拦截器解析、验签、判断是否过期、是否合法
- 合法则放行接口,无 Token / 非法 / 过期直接 401
四、JWT 优缺点
优点
- 无状态:服务端不用存 Session,分布式、集群友好
- 跨域友好:适合前后端分离
- 自带信息:Payload 可带用户角色、ID,减少查库
- 扩展性强:微服务、网关鉴权通用
缺点
- 一旦签发,中途无法作废(只能等过期,需配合黑名单做注销)
- Payload Base64 解码就能看,不能存敏感数据
- Token 不宜过长,携带太多字段影响请求头大小
五、适用场景
- 前后端分离项目登录认证
- 微服务、分布式系统鉴权
- 第三方授权登录
- 移动端 App 登录认证
六、JWT 高频面试题(必背)
-
JWT 由哪几部分组成?
Header 头部、Payload 载荷、Signature 签名三部分,用点分隔。
-
为什么 JWT 不能存敏感信息?
Payload 只是 Base64 编码,可直接解码查看,不是加密,不能存密码、手机号等隐私。
-
JWT 是有状态还是无状态?
无状态,服务端不存储 Token 信息,靠签名校验合法性,适合集群分布式。
-
JWT 怎么防止篡改?
后端用密钥对 Header+Payload 生成签名,篡改任意部分签名都会校验失败。
-
JWT 过期了怎么处理?
前端捕获 401,跳转登录;企业常用双 Token:AccessToken 短期 + RefreshToken 无感刷新。
-
JWT 如何实现注销?
原生无法主动作废,解决方案:
- 维护 Token 黑名单
- 缩短过期时间 + 强制重新登录
- 更换全局密钥批量失效
-
对称加密和非对称加密区别?
- 对称 HS256:同一密钥加解密,简单适合单体项目
- 非对称 RSA:私钥签发、公钥验签,适合微服务跨项目
七、实战练习:封装 JWT 帮助类(.NET 可用直接复制)
1. 安装 NuGet
Microsoft.IdentityModel.Tokens
System.IdentityModel.Tokens.Jwt
2. 配置节点(appsettings.json)
cs
"Jwt": {
"Secret": "ABCDEFG123456789ABCDEFG123456789",
"Issuer": "AdminWeb",
"Audience": "AdminClient",
"ExpireMinutes": 120
}
3. 封装 JwtHelper 帮助类
新建 Common/Helper/JwtHelper.cs
cs
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.IdentityModel.Tokens;
namespace Admin.NET.Common.Helper;
public static class JwtHelper
{
/// <summary>
/// 生成Token
/// </summary>
/// <param name="userId">用户ID</param>
/// <param name="account">账号</param>
/// <param name="role">角色</param>
/// <param name="secret">密钥</param>
/// <param name="issuer">签发人</param>
/// <param name="audience">受众</param>
/// <param name="expireMin">过期分钟</param>
/// <returns>Token</returns>
public static string GenerateToken(long userId, string account, string role,
string secret, string issuer, string audience, int expireMin)
{
// 1. 声明载荷
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, userId.ToString()),
new Claim(ClaimTypes.Name, account),
new Claim(ClaimTypes.Role, role)
};
// 2. 密钥
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
// 3. 生成Token
var tokenDescriptor = new SecurityTokenDescriptor
{
Issuer = issuer,
Audience = audience,
Subject = new ClaimsIdentity(claims),
Expires = DateTime.UtcNow.AddMinutes(expireMin),
SigningCredentials = creds
};
var handler = new JwtSecurityTokenHandler();
var securityToken = handler.CreateToken(tokenDescriptor);
return handler.WriteToken(securityToken);
}
/// <summary>
/// 校验Token
/// </summary>
public static bool ValidateToken(string token, string secret, string issuer, string audience, out ClaimsPrincipal? claims)
{
claims = null;
try
{
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret));
var validateParam = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = issuer,
ValidAudience = audience,
IssuerSigningKey = key,
ClockSkew = TimeSpan.Zero
};
var handler = new JwtSecurityTokenHandler();
claims = handler.ValidateToken(token, validateParam, out _);
return true;
}
catch
{
return false;
}
}
}