.NET Core-生成、校验、解析JWT Token
重要对象
JwtSecurityToken
代表一个jwt token,可以直接用此对象生成token字符串,也可以使用token字符串创建此对象
SecurityToken
JwtSecurityToken的基类,包含基础数据
JwtSecurityTokenHandler
创建、校验token,返回ClaimsPrincipal
CanReadToken():确定字符串是否是格式良好的Json Web令牌(JWT)
ReadJwtToken(string token):token字符串转为JwtSecurityToken对象
ValidateToken(string token、TokenValidationParameters parameter,out SecurityToken validatedToken):校验token,返回ClaimsIdentity,
SymmetricSecurityKey、RsaSecurityKey
- SymmetricSecurityKey:对称签名密钥
- RsaSecurityKey:Rsa签名密钥,也就是私钥(非对称)
签名算法
- HS256
HS256
使用密钥生成固定的签名,RS256
使用成非对称进行签名。简单地说,HS256
必须与任何想要验证JWT
的客户端
或API
共享秘密。与任何其他对称算法一样,相同的秘密用于签名和验证JWT
。RS256
生成非对称签名,这意味着必须使用私钥来签签名JWT
,并且必须使用对应的公钥来验证签名。 - RS256
与对称算法不同,使用RS256
可以保证服务端是JWT
的签名者,因为服务端是唯一拥有私钥的一方。这样做将不再需要在许多应用程序之间共享私钥。
使用非对称签名算法。这样做将不再需要在许多应用程序之间共享私钥。
案例
添加NuGet包:System.IdentityModel.Tokens.Jwt
1、HMACSHA256
JwtHelper(HMACSHA256)
/// <summary>
/// JwtToken生成、校验(HMACSHA256)
/// </summary>
public static class Jwt_HMACSHA256Helper
{
private static string KEY = "f47b558d-7654-458c-99f2-123456ef0199";
/// <summary>
/// 生成token
/// </summary>
/// <returns></returns>
public static string GetJwtToken()
{
//对称秘钥
SecurityKey securityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(KEY));
var claims = new List<Claim>() {
new Claim("ID","1"),
new Claim("Name","fan")
};
JwtSecurityToken jwtToken = new JwtSecurityToken(
issuer: "fan",
audience: "fan",
claims: claims,
notBefore: DateTime.UtcNow,
expires: DateTime.UtcNow.AddSeconds(10),
signingCredentials: new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256)
);
//生成token方式1
string token = new JwtSecurityTokenHandler().WriteToken(jwtToken);
////生成token方式2
//var tokenDescriptor = new SecurityTokenDescriptor // 创建一个 Token 的原始对象
//{
// Issuer = "fan",
// Audience = "fan",
// Subject = new ClaimsIdentity(new[]
// {
// new Claim(ClaimTypes.Name, "")
// }),
// Expires = DateTime.Now.AddMinutes(60),
// SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.ASCII.GetBytes(key)), SecurityAlgorithms.HmacSha256)
//};
//SecurityToken securityToken = new JwtSecurityTokenHandler().CreateToken(tokenDescriptor);
//var token2 = new JwtSecurityTokenHandler().WriteToken(securityToken);
return token;
}
/// <summary>
/// 校验token
/// </summary>
/// <param name="token"></param>
/// <param name="principal"></param>
/// <returns></returns>
public static bool VerifyJwtToken(string token, out ClaimsPrincipal principal)
{
principal = null;
//对称秘钥
SecurityKey securityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(KEY));
//校验token
var validateParameter = new TokenValidationParameters()
{
ValidateLifetime = true,
ValidateAudience = true,
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "fan",
ValidAudience = "fan",
IssuerSigningKey = securityKey,
ClockSkew = TimeSpan.Zero//校验过期时间必须加此属性
};
//不校验,直接解析token
//jwtToken = new JwtSecurityTokenHandler().ReadJwtToken(token1);
bool success = false;
try
{
//校验并解析token,validatedToken是解密后的对象
principal = new JwtSecurityTokenHandler().ValidateToken(token, validateParameter, out SecurityToken validatedToken);
//获取payload中的数据
//var jwtPayload = ((JwtSecurityToken)validatedToken).Payload.SerializeToJson();
success = true;
}
catch (SecurityTokenExpiredException ex)
{
//表示过期
success = false;
}
catch (SecurityTokenException ex)
{
//表示token错误
success = false;
}
catch (Exception ex)
{
success = false;
}
return success;
}
}
static void Main()
{
string token = Jwt_HMACSHA256Helper.GetJwtToken();
bool success = Jwt_HMACSHA256Helper.VerifyJwtToken(token, out ClaimsPrincipal principal);
}
2、RSA
RSA公私钥生成地址:https://uutool.cn/rsa-generate/
JwtHelper(RSA)
/// <summary>
/// JwtToken生成、校验(RSA)
/// </summary>
public static class Jwt_RSAHelper
{
//RSA私钥
private static string PRIVATE_KEY = @"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
S2xOANqreXnJutzv4tVJGjp1I7YmDeU=";
//RSA公钥
private static string PUBLIC_KEY = @"MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAuXGzXP/GHpe6U0+xWQqy
Bs1qBBzP3OW1bVBU6sQqKU1Ef5IjK8Qoej4d7mjYQk5keq9OOu2d4WnVp5SiqqJQ
wCOY74dBFa5JhAGvlW1gk3hewtwtGFtq0LfLtlTzs1QFeqX+IWZoKksLfK6yZn6w
1Ol1tv2UYeHuTLwM0zcaEqyHwUl1+B2gc86zu2evv9TxQWZgIEemrHJu7QQL59Z4
BRE15v7kHUc/mLq9uYFT+a2Ftq7mcQWgfIphTGs4SZzKV0vRLwWsERSwyfrs4YyD
L0NvqswWq3NNJF0ascc3/TUor+rjHi98L+Shi+Ga1ItMCKXbjnVcAXSwX47c8gL5
qPZkl0r9Csbp3vK0Ukc91ZvkTYZ7IDCbqHU7db7QsdtVyz92vJu45cJbGuwDGqwq
P0ExYKbf5zAVXv25Gd3aBLRt/s7sQOOWWNn1XbIdtpOdiD124ZiY1tA7J+BqHV4E
Wlu0azu6lS7q1GSfMPiK/i6ltB1H7xVaW9Mw9PxHr/ZtO/mZ1B4XDeoMSCWv1wgS
zk+U03AB/qF8Z4OeIZhhPZ4fqCfhQKEfT7sZInCcFzhFr3gMlWN5+THcFBlcLi+f
ntnsMEYYXBw8UKB3OZV0u8ZJ0xp6/yj+45lRgBU6gVCW81elhgdE65LBUlsEy+UI
0Eeb2Hq73mdGpgzQ/Z1w0WcCAwEAAQ==";
public static string GetJwtToken()
{
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Name, "fan")
};
var rsa = RSA.Create();
rsa.ImportPkcs8PrivateKey(Convert.FromBase64String(PRIVATE_KEY), out _);
SecurityKey securityKey = new RsaSecurityKey(rsa);
var token = new JwtSecurityToken(
issuer: "fan",
audience: "fan",
claims: claims,
notBefore: DateTime.Now,
expires: DateTime.Now.AddSeconds(10),
signingCredentials: new SigningCredentials(securityKey, SecurityAlgorithms.RsaSha256)
);
var jwtToken = new JwtSecurityTokenHandler().WriteToken(token);
return jwtToken;
}
public static bool VerifyJwtToken(string token, out ClaimsPrincipal principal)
{
principal = null;
//校验token
var validateParameter = new TokenValidationParameters()
{
ValidateLifetime = true,
ValidateAudience = true,
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "fan",
ValidAudience = "fan",
IssuerSigningKey = new RsaSecurityKey(CreateRsaProviderFromPublicKey(PUBLIC_KEY)),
ClockSkew = TimeSpan.Zero//校验过期时间必须加此属性
};
bool success = false;
try
{
//校验并解析token,validatedToken是解密后的对象
principal = new JwtSecurityTokenHandler().ValidateToken(token, validateParameter, out SecurityToken validatedToken);
//获取payload中的数据
//var jwtPayload = ((JwtSecurityToken)validatedToken).Payload.SerializeToJson();
success = true;
}
catch (SecurityTokenExpiredException ex)
{
//表示过期
success = false;
}
catch (SecurityTokenException ex)
{
//表示token错误
success = false;
}
catch (Exception ex)
{
success = false;
}
return success;
}
#region 私有方法
/// <summary>
/// 通过公钥创建RSA
/// </summary>
/// <param name="publicKeyString"></param>
/// <returns></returns>
private static RSA CreateRsaProviderFromPublicKey(string publicKeyString)
{
// encoded OID sequence for PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1"
byte[] seqOid = { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 };
byte[] seq = new byte[15];
var x509Key = Convert.FromBase64String(publicKeyString);
// --------- Set up stream to read the asn.1 encoded SubjectPublicKeyInfo blob ------
using (MemoryStream mem = new MemoryStream(x509Key))
{
using (BinaryReader binr = new BinaryReader(mem)) //wrap Memory Stream with BinaryReader for easy reading
{
byte bt = 0;
ushort twobytes = 0;
twobytes = binr.ReadUInt16();
if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
binr.ReadByte(); //advance 1 byte
else if (twobytes == 0x8230)
binr.ReadInt16(); //advance 2 bytes
else
return null;
seq = binr.ReadBytes(15); //read the Sequence OID
if (!CompareBytearrays(seq, seqOid)) //make sure Sequence for OID is correct
return null;
twobytes = binr.ReadUInt16();
if (twobytes == 0x8103) //data read as little endian order (actual data order for Bit String is 03 81)
binr.ReadByte(); //advance 1 byte
else if (twobytes == 0x8203)
binr.ReadInt16(); //advance 2 bytes
else
return null;
bt = binr.ReadByte();
if (bt != 0x00) //expect null byte next
return null;
twobytes = binr.ReadUInt16();
if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
binr.ReadByte(); //advance 1 byte
else if (twobytes == 0x8230)
binr.ReadInt16(); //advance 2 bytes
else
return null;
twobytes = binr.ReadUInt16();
byte lowbyte = 0x00;
byte highbyte = 0x00;
if (twobytes == 0x8102) //data read as little endian order (actual data order for Integer is 02 81)
lowbyte = binr.ReadByte(); // read next bytes which is bytes in modulus
else if (twobytes == 0x8202)
{
highbyte = binr.ReadByte(); //advance 2 bytes
lowbyte = binr.ReadByte();
}
else
return null;
byte[] modint = { lowbyte, highbyte, 0x00, 0x00 }; //reverse byte order since asn.1 key uses big endian order
int modsize = BitConverter.ToInt32(modint, 0);
int firstbyte = binr.PeekChar();
if (firstbyte == 0x00)
{ //if first byte (highest order) of modulus is zero, don't include it
binr.ReadByte(); //skip this null byte
modsize -= 1; //reduce modulus buffer size by 1
}
byte[] modulus = binr.ReadBytes(modsize); //read the modulus bytes
if (binr.ReadByte() != 0x02) //expect an Integer for the exponent data
return null;
int expbytes = (int)binr.ReadByte(); // should only need one byte for actual exponent data (for all useful values)
byte[] exponent = binr.ReadBytes(expbytes);
// ------- create RSACryptoServiceProvider instance and initialize with public key -----
var rsa = RSA.Create();
RSAParameters rsaKeyInfo = new RSAParameters
{
Modulus = modulus,
Exponent = exponent
};
rsa.ImportParameters(rsaKeyInfo);
return rsa;
}
}
}
private static bool CompareBytearrays(byte[] a, byte[] b)
{
if (a.Length != b.Length)
return false;
int i = 0;
foreach (byte c in a)
{
if (c != b[i])
return false;
i++;
}
return true;
}
#endregion
}
string token = Jwt_RSAHelper.GetJwtToken();
bool success = Jwt_RSAHelper.VerifyJwtToken(token, out ClaimsPrincipal principal);