Skip to content

Commit 48cdc0c

Browse files
authored
add an overload for ReadJsonWebToken to take a ReadOnlyMemory, add tests and benchmark (#3205)
1 parent 4e3300c commit 48cdc0c

File tree

5 files changed

+143
-0
lines changed

5 files changed

+143
-0
lines changed

benchmark/Microsoft.IdentityModel.Benchmarks/Microsoft.IdentityModel.Benchmarks.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
<ProjectReference Include="..\..\src\Microsoft.IdentityModel.Tokens\Microsoft.IdentityModel.Tokens.csproj" />
4646
<ProjectReference Include="..\..\src\System.IdentityModel.Tokens.Jwt\System.IdentityModel.Tokens.Jwt.csproj" />
4747
<ProjectReference Include="..\..\src\Microsoft.IdentityModel.Protocols.SignedHttpRequest\Microsoft.IdentityModel.Protocols.SignedHttpRequest.csproj" />
48+
<ProjectReference Include="..\..\test\Microsoft.IdentityModel.TestUtils\Microsoft.IdentityModel.TestUtils.csproj" />
4849
</ItemGroup>
4950

5051
<PropertyGroup Condition="'$(TargetFramework)' == 'net6.0'">
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using BenchmarkDotNet.Attributes;
6+
using Microsoft.IdentityModel.JsonWebTokens;
7+
using Microsoft.IdentityModel.TestUtils;
8+
using Microsoft.IdentityModel.Tokens;
9+
10+
namespace Microsoft.IdentityModel.Benchmarks
11+
{
12+
public class ReadJsonWebTokenBenchmarks
13+
{
14+
private JsonWebTokenHandler _handler;
15+
private string _smallToken;
16+
private string _largeToken;
17+
18+
[GlobalSetup]
19+
public void Setup()
20+
{
21+
_handler = new JsonWebTokenHandler();
22+
23+
// Small token with minimal claims
24+
_smallToken = _handler.CreateToken(
25+
Default.PayloadString,
26+
KeyingMaterial.JsonWebKeyRsa256SigningCredentials);
27+
28+
// Large token with many claims
29+
var claims = Default.PayloadClaims;
30+
for (int i = 0; i < 50; i++)
31+
claims.Add(new System.Security.Claims.Claim($"claim{i}", $"value{i}"));
32+
33+
_largeToken = _handler.CreateToken(
34+
new SecurityTokenDescriptor
35+
{
36+
Subject = new CaseSensitiveClaimsIdentity(claims),
37+
SigningCredentials = KeyingMaterial.JsonWebKeyRsa256SigningCredentials
38+
});
39+
}
40+
41+
[Benchmark(Baseline = true)]
42+
public JsonWebToken ReadJsonWebToken_String_Small()
43+
{
44+
return _handler.ReadJsonWebToken(_smallToken);
45+
}
46+
47+
[Benchmark]
48+
public JsonWebToken ReadJsonWebToken_Span_Small()
49+
{
50+
return _handler.ReadJsonWebToken(_smallToken.AsMemory());
51+
}
52+
53+
[Benchmark]
54+
public JsonWebToken ReadJsonWebToken_String_Large()
55+
{
56+
return _handler.ReadJsonWebToken(_largeToken);
57+
}
58+
59+
[Benchmark]
60+
public JsonWebToken ReadJsonWebToken_Span_Large()
61+
{
62+
return _handler.ReadJsonWebToken(_largeToken.AsMemory());
63+
}
64+
}
65+
}

src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.cs

+25
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,31 @@ public virtual JsonWebToken ReadJsonWebToken(string token)
486486
return new JsonWebToken(token);
487487
}
488488

489+
/// <summary>
490+
/// Converts a ReadOnlyMemory span of characters into an instance of <see cref="JsonWebToken"/>.
491+
/// </summary>
492+
/// <param name="token">A ReadOnlyMemory span containing a JSON Web Token (JWT) in JWS or JWE Compact Serialization format.</param>
493+
/// <returns>A <see cref="JsonWebToken"/>.</returns>
494+
/// <exception cref="ArgumentException">Thrown if the length of <paramref name="token"/> is greater than <see cref="TokenHandler.MaximumTokenSizeInBytes"/>.</exception>
495+
/// <remarks>
496+
/// <para>If the <paramref name="token"/> is in JWE Compact Serialization format, only the protected header will be deserialized.</para>
497+
/// This method is unable to decrypt the payload. Use <see cref="ValidateToken(string, TokenValidationParameters)"/>to obtain the payload.
498+
/// <para>
499+
/// The token is NOT validated and no security decisions should be made about the contents.
500+
/// Use <see cref="ValidateToken(string, TokenValidationParameters)"/> or <see cref="ValidateTokenAsync(string, TokenValidationParameters)"/> to ensure the token is acceptable.
501+
/// </para>
502+
/// </remarks>
503+
public virtual JsonWebToken ReadJsonWebToken(ReadOnlyMemory<char> token)
504+
{
505+
if (token.IsEmpty)
506+
throw LogHelper.LogArgumentNullException(nameof(token));
507+
508+
if (token.Span.Length > MaximumTokenSizeInBytes)
509+
throw LogHelper.LogExceptionMessage(new ArgumentException(LogHelper.FormatInvariant(TokenLogMessages.IDX10209, LogHelper.MarkAsNonPII(token.Length), LogHelper.MarkAsNonPII(MaximumTokenSizeInBytes))));
510+
511+
return new JsonWebToken(token);
512+
}
513+
489514
/// <summary>
490515
/// Converts a string into an instance of <see cref="JsonWebToken"/>.
491516
/// </summary>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
virtual Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.ReadJsonWebToken(System.ReadOnlyMemory<char> token) -> Microsoft.IdentityModel.JsonWebTokens.JsonWebToken

test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandlerTests.cs

+51
Original file line numberDiff line numberDiff line change
@@ -4573,6 +4573,57 @@ public static TheoryData<CreateTokenTheoryData> ValidateAuthenticationTagLengthT
45734573
}
45744574
};
45754575
}
4576+
4577+
[Theory, MemberData(nameof(ReadJsonWebTokenSpanTheoryData))]
4578+
public void ReadJsonWebToken_Span(JwtTheoryData theoryData)
4579+
{
4580+
var context = TestUtilities.WriteHeader($"{this}.ReadJsonWebToken_Span", theoryData);
4581+
try
4582+
{
4583+
var handler = new JsonWebTokenHandler();
4584+
var readOnlySpan = theoryData.Token.AsMemory();
4585+
var jwtFromSpan = handler.ReadJsonWebToken(readOnlySpan);
4586+
var jwtFromString = handler.ReadJsonWebToken(theoryData.Token);
4587+
4588+
if (theoryData.ExpectedException == null)
4589+
{
4590+
// Results should match between span and string versions
4591+
IdentityComparer.AreEqual(jwtFromSpan, jwtFromString, context);
4592+
}
4593+
4594+
theoryData.ExpectedException.ProcessNoException(context);
4595+
}
4596+
catch (Exception ex)
4597+
{
4598+
theoryData.ExpectedException.ProcessException(ex, context);
4599+
}
4600+
4601+
TestUtilities.AssertFailIfErrors(context);
4602+
}
4603+
4604+
public static TheoryData<JwtTheoryData> ReadJsonWebTokenSpanTheoryData()
4605+
{
4606+
return new TheoryData<JwtTheoryData>
4607+
{
4608+
new JwtTheoryData
4609+
{
4610+
TestId = "ValidToken",
4611+
Token = Default.AsymmetricJws,
4612+
},
4613+
new JwtTheoryData
4614+
{
4615+
TestId = "TokenTooLong",
4616+
Token = new string('x', TokenValidationParameters.DefaultMaximumTokenSizeInBytes + 1),
4617+
ExpectedException = ExpectedException.ArgumentException("IDX10209:"),
4618+
},
4619+
new JwtTheoryData
4620+
{
4621+
TestId = "EmptyToken",
4622+
Token = string.Empty,
4623+
ExpectedException = ExpectedException.ArgumentNullException("IDX10000:"),
4624+
}
4625+
};
4626+
}
45764627
}
45774628

45784629
public class CreateTokenTheoryData : TheoryDataBase

0 commit comments

Comments
 (0)