.NET 8中使用JwtSecurityTokenHandler验证JWT时解决RSA释放错误
我在使用.NET 8.0时遇到一个问题:当用using语句或finally块调用rsa.Dispose()时,约50%的概率会触发“无法访问已释放的对象(Microsoft.IdentityModel.Tokens.RsaSecurityKey)”错误;但如果不手动释放RSA对象,就不会出现这个错误。
问题代码
RSA rsa = RSA.Create(); // OR using(RSA rsa = RSA.Create()) try { rsa.ImportParameters(new RSAParameters { Modulus = bytesN, Exponent = bytesE }); var rsaSecurityKey = new RsaSecurityKey(rsa) { KeyId = strKid }; JwtConfiguration jwtConfiguration = _configurationUtil.GetJwtConfiguration("Kakao"); var tokenValidationParameters = new TokenValidationParameters { ValidateIssuer = jwtConfiguration.ValidateIssuer, ValidIssuer = jwtConfiguration.ValidIssuer, ValidateAudience = jwtConfiguration.ValidateAudience, ValidAudience = jwtConfiguration.ValidAudience, ValidateLifetime = jwtConfiguration.ValidateLifetime, RequireExpirationTime = jwtConfiguration.RequireExpirationTime, ValidateIssuerSigningKey = jwtConfiguration.ValidateIssuerSigningKey, IssuerSigningKey = rsaSecurityKey, TryAllIssuerSigningKeys = jwtConfiguration.TryAllIssuerSigningKeys, }; var tokenHandler = new JwtSecurityTokenHandler(); var claimsPrincipal = tokenHandler.ValidateToken(strIdToken, tokenValidationParameters, out SecurityToken validatedToken); var jwtSecurityToken = validatedToken as JwtSecurityToken; string nonce = jwtSecurityToken!.Claims.FirstOrDefault(c => c.Type == "nonce")?.Value!; if (nonce != oauthConfiguration.Nonce) throw new Exception("NONCE ERROR"); strId = jwtSecurityToken.Claims.FirstOrDefault(c => c.Type == "sub")?.Value!; strEmail = jwtSecurityToken.Claims.FirstOrDefault(c => c.Type == "email")?.Value!; } catch (Exception e) { response.RESULT_CODE = "F000"; response.RESULT_MSG = e.Message; return Page(); } finally { rsa.Dispose(); }
错误信息
IDX10511: Signature validation failed. Keys tried: 'Microsoft.IdentityModel.Tokens.RsaSecurityKey, KeyId: 'sameKid', InternalId: 'internalId'. , KeyId: sameKid '. Number of keys in TokenValidationParameters: '1'. Number of keys in Configuration: '0'. Matched key was in 'TokenValidationParameters'. kid: 'sameKid'. Exceptions caught: 'System.ObjectDisposedException: Cannot access a disposed object. Object name: 'System.Security.Cryptography.RSABCrypt'. at System.Security.Cryptography.RSABCrypt.GetKey() at System.Security.Cryptography.RSABCrypt.VerifyHash(ReadOnlySpan`1 hash, ReadOnlySpan`1 signature, HashAlgorithmName hashAlgorithm, RSASignaturePadding padding) at Microsoft.IdentityModel.Tokens.AsymmetricAdapter.VerifyUsingSpan(Boolean isRSA, ReadOnlySpan`1 bytes, Byte[] signature) at Microsoft.IdentityModel.Tokens.AsymmetricAdapter.VerifyRsa(Byte[] bytes, Byte[] signature) at Microsoft.IdentityModel.Tokens.AsymmetricAdapter.Verify(Byte[] bytes, Byte[] signature) at Microsoft.IdentityModel.Tokens.AsymmetricSignatureProvider.Verify(Byte[] input, Byte[] signature) at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignature(Byte[] encodedBytes, Byte[] signature, SecurityKey key, String algorithm, SecurityToken securityToken, TokenValidationParameters validationParameters) at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignature(String token, JwtSecurityToken jwtToken, TokenValidationParameters validationParameters, BaseConfiguration configuration) '. token: '[Security Artifact of type 'System.IdentityModel.Tokens.Jwt.JwtSecurityToken' is hidden.]'.
我试过用using和finally确保RSA对象释放,也试过不释放来规避错误,希望签名密钥始终有效,验证过程能正常完成,但不知道为什么RSA对象会被提前释放,请问该怎么正确管理RSA对象的释放?
核心原因
RsaSecurityKey默认不会复制传入的RSA对象,而是直接持有对它的引用。JWT签名验证的内部流程可能存在延迟执行的情况(比如缓存逻辑、异步操作),当你在finally里提前释放RSA对象后,验证过程再去访问它就会触发已释放对象错误。50%的概率是因为验证操作的执行时机不确定——有时候在释放前完成,有时候在释放后才执行。
正确处理方式
1. 让RsaSecurityKey接管RSA对象生命周期
创建RsaSecurityKey时,传入第二个参数willBeDisposed并设为true,这样RsaSecurityKey会负责释放底层的RSA对象,你无需手动调用Dispose():
var rsaSecurityKey = new RsaSecurityKey(rsa, willBeDisposed: true) { KeyId = strKid };
之后可以去掉finally里的rsa.Dispose(),也不要用using包裹RSA对象,交给RsaSecurityKey管理释放。
2. 直接用RSAParameters创建RsaSecurityKey
跳过手动创建RSA对象的步骤,直接用RSAParameters实例化RsaSecurityKey,这样RsaSecurityKey会自行创建内部的RSA实例,完全不依赖外部对象:
var rsaSecurityKey = new RsaSecurityKey(new RSAParameters { Modulus = bytesN, Exponent = bytesE }) { KeyId = strKid };
这种方式从根源上避免了引用释放的问题,RsaSecurityKey会自行处理内部加密对象的生命周期。
3. 确保验证完全完成后再释放
如果必须手动控制RSA对象释放,要确认tokenHandler.ValidateToken()及所有后续依赖操作完全执行完毕后,再调用Dispose()。不过这种方式需要确保验证过程没有异步延迟,可靠性不如前两种方法。
内容的提问来源于stack exchange,提问作者khoo

