我正在使用
JwtSecurityTokenHandler
在 aspnet core web api 应用程序中发行令牌:
JwtSecurityToken token = BuildJwtSecurityToken(...);
public string toks = new JwtSecurityTokenHandler().WriteToken(token);
每次请求和创建令牌时,都会实例化一个新的 JwtSecurityTokenHandler,我可以使用它的全局实例并将其用于每次令牌生成吗?
private JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
private GenToken(JwtSecurityToken token) => handler.WriteToken(token);
这样使用有什么问题吗?
文档说
JwtSecurityTokenHandler
的实例成员不能保证线程安全。
我看到的大多数示例,例如 blog.wille-zone.de 上的这个示例,为每个请求单独创建它。
我不会在这方面变得太聪明;即使它当前是线程安全的,这里没有人可以承诺微软保持这种状态。
如果您确实希望他们做出承诺,您可以首先在 https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet 提出问题。他们可以将其写入规范/代码/文档中。
我在高并发系统中也遇到了同样的问题,并分析了它以揭开真相。
using System.IdentityModel.Tokens.Jwt;
namespace SafeTokenHandler;
public static class JwtSecurityTokenHandlerExtensions
{
private static readonly JwtSecurityTokenHandler JwtSecurityTokenHandlerShared = new();
public static string CompileToString(this JwtSecurityToken token) => JwtSecurityTokenHandlerShared.WriteToken(token);
public static JwtSecurityToken ToJwtSecurityToken(this string token) => JwtSecurityTokenHandlerShared.ReadJwtToken(token);
}
BenchmarkDotNet v0.13.11, macOS Sonoma 14.0 (23A344) [Darwin 23.0.0]
Apple M2 Pro, 1 CPU, 10 logical and 10 physical cores
.NET SDK 8.0.100
[Host] : .NET 8.0.0 (8.0.23.53103), Arm64 RyuJIT AdvSIMD
DefaultJob : .NET 8.0.0 (8.0.23.53103), Arm64 RyuJIT AdvSIMD
方法 | 意思是 | 错误 | 标准偏差 | 比率 | 0代 | 第一代 | 已分配 | 分配比例 |
---|---|---|---|---|---|---|---|---|
Jwt实例 | 1,403.7 纳秒 | 5.11纳秒 | 4.27 纳秒 | 1.00 | 0.9537 | 0.0172 | 7.8 KB | 1.00 |
Jwt静态 | 479.9 纳秒 | 1.39 纳秒 | 1.16纳秒 | 0.34 | 0.3195 | - | 2.62 KB | 0.34 |
BenchmarkDotNet v0.13.11, macOS Sonoma 14.0 (23A344) [Darwin 23.0.0]
Apple M2 Pro, 1 CPU, 10 logical and 10 physical cores
.NET SDK 8.0.100
[Host] : .NET 8.0.0 (8.0.23.53103), Arm64 RyuJIT AdvSIMD
DefaultJob : .NET 8.0.0 (8.0.23.53103), Arm64 RyuJIT AdvSIMD
方法 | 意思是 | 错误 | 标准偏差 | 比率 | 0代 | 第一代 | 已分配 | 分配比例 |
---|---|---|---|---|---|---|---|---|
Jwt实例 | 1,806.3 纳秒 | 7.24 纳秒 | 6.78 纳秒 | 1.00 | 0.8755 | 0.0095 | 7.16 KB | 1.00 |
Jwt静态 | 869.9 纳秒 | 1.79 纳秒 | 1.40 纳秒 | 0.48 | 0.2403 | 0.0010 | 1.97 KB | 0.28 |
代码片段:
[Fact]
public void CompileToString_MultiThread()
{
var expected = new Dictionary<int, string>();
var upper = Random.Shared.Next(1000, 10000);
for (var i = 0; i < upper; i++)
{
var token = new JwtSecurityToken(
issuer: "me",
audience: "you",
new[] { new Claim("sub", i.ToString()) });
expected.Add(i, JwtUtility.CompileToString(token));
}
var actual = new ConcurrentDictionary<int, string>();
Parallel.For(0, upper, i =>
{
var token = new JwtSecurityToken(
issuer: "me",
audience: "you",
new[] { new Claim("sub", i.ToString()) });
var addResult = actual.TryAdd(i, token.CompileToString());
Assert.True(addResult, $"Failed to add {i} to actual");
});
Assert.Equal(expected, actual);
foreach (var (expectedKey, expectedValue) in expected)
{
Assert.Equal(expectedValue, actual[expectedKey]);
}
}
代码片段:
[Fact]
public void ToJwtSecurityToken_MultiThread()
{
var expected = new Dictionary<int, string>();
var upper = Random.Shared.Next(1000, 10000);
for (var i = 0; i < upper; i++)
{
var token = new JwtSecurityToken(
issuer: "me",
audience: "you",
new[] { new Claim("sub", i.ToString()) });
expected.Add(i, JwtUtility.CompileToString(token));
}
var actual = new ConcurrentDictionary<int, JwtSecurityToken>();
Parallel.For(0, upper, i =>
{
var addResult = actual.TryAdd(i, expected[i].ToJwtSecurityToken());
Assert.True(addResult, $"Failed to add {i} to actual");
});
foreach (var (expectedKey, expectedValue) in expected)
{
Assert.Equal(expectedValue, actual[expectedKey].CompileToString());
}
}
进行的基准测试和功能测试表明,
WriteToken
和ReadJwtToken
方法是线程安全的。
这些结果对于需要高并发的应用程序至关重要,确保在各种负载条件下获得可靠且一致的性能。