Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

In JsonWebToken use the values as bytes instead of converting to string #2535

Draft
wants to merge 36 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
bfb1fed
Update JsonWebToken for child classes.
pmaytak Mar 21, 2024
4eb7a5f
Add props files to solution.
pmaytak Mar 21, 2024
76d9e52
Fix.
pmaytak Mar 27, 2024
b08ffa1
Add another claims dictionary to JsonWebToken.Payload to hold string …
pmaytak Apr 4, 2024
22720d7
Using and reading from the original token as a Span and saving claim …
pmaytak Apr 4, 2024
e4ab2c1
Merge main.
pmaytak Apr 20, 2024
e597160
Rename.
pmaytak Apr 24, 2024
63ab5cd
Remove ArrayPool from payload claim set.
pmaytak Apr 24, 2024
bc3140c
Rename.
pmaytak Apr 24, 2024
8397e34
Add ReadUtf8StringLocation
pmaytak Apr 25, 2024
2980f41
Make private protected.
pmaytak May 4, 2024
a14cf2c
Merge remote-tracking branch 'origin/dev' into pmaytak/1pjwt
pmaytak May 4, 2024
a4f1728
Merge origin/dev.
pmaytak Jun 12, 2024
eb4b024
Merge remote-tracking branch 'origin/dev' into pmaytak/1pjwt
pmaytak Jun 14, 2024
fb3639d
Refactor.
pmaytak Jun 18, 2024
7e4f21e
Add test.
pmaytak Jun 18, 2024
edbe8f2
Merge remote-tracking branch 'origin/dev' into pmaytak/1pjwt
pmaytak Jun 18, 2024
464b592
Merge remote-tracking branch 'origin/dev' into pmaytak/1pjwt
pmaytak Jun 18, 2024
7b968be
Fixes to claims.
pmaytak Jun 19, 2024
aac0539
Update reading string header properties.
pmaytak Jun 20, 2024
85a384f
Merge remote-tracking branch 'origin/dev' into pmaytak/1pjwt
pmaytak Jun 20, 2024
652b333
Add delegate for reading properties instead of an overload.
pmaytak Jul 31, 2024
a7f36fb
Add delegate for reading properties instead of an overload.
pmaytak Aug 3, 2024
3f6eaa0
Merge main.
pmaytak Aug 3, 2024
c86355e
Add test. Update comments.
pmaytak Aug 7, 2024
cbedbca
Merge dev.
pmaytak Aug 7, 2024
f22e584
Add code to unscape values.
pmaytak Aug 8, 2024
1dbd7cf
Merge remote-tracking branch 'origin/dev' into pmaytak/1pjwt
pmaytak Aug 14, 2024
5b8c869
Fixes.
pmaytak Aug 15, 2024
1e73609
Update reading escaped values. Update test.
pmaytak Aug 23, 2024
9d990f2
Merge remote-tracking branch 'origin/dev' into pmaytak/1pjwt
pmaytak Aug 23, 2024
57d9b46
Merge remote-tracking branch 'origin/dev' into pmaytak/1pjwt
pmaytak Aug 29, 2024
f013da8
Add benchmark to test validation with an issuer delegate using string…
pmaytak Sep 4, 2024
c9d257f
Add a class with profiler methods.
pmaytak Sep 7, 2024
75ebb5a
Default TVP IssuerBytes to an empty Span.
pmaytak Sep 13, 2024
ba4b49f
Revert to use ArrayPool for the token bytes. Save bytes only for stri…
pmaytak Sep 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 64 additions & 6 deletions src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonClaimSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Collections.ObjectModel;
using System.Globalization;
using System.Security.Claims;
using System.Text;
using System.Text.Json;
using Microsoft.IdentityModel.Logging;
using Microsoft.IdentityModel.Tokens;
Expand All @@ -24,15 +25,35 @@ internal class JsonClaimSet

internal object _claimsLock = new();
internal readonly Dictionary<string, object> _jsonClaims;
#if NET8_0_OR_GREATER
internal readonly Dictionary<string, (int startIndex, int length)?> _jsonClaimsBytes;
internal readonly Memory<byte> _tokenAsMemory;
#endif
private List<Claim> _claims;

internal JsonClaimSet() { _jsonClaims = new Dictionary<string, object>(); }
internal JsonClaimSet()
{
_jsonClaims = new();
#if NET8_0_OR_GREATER
_jsonClaimsBytes = new();
#endif
}

internal JsonClaimSet(Dictionary<string, object> jsonClaims)
{
_jsonClaims = jsonClaims;
}

#if NET8_0_OR_GREATER
internal JsonClaimSet(
Dictionary<string, object> jsonClaims,
Dictionary<string, (int startIndex, int length)?> jsonClaimsBytes,
Memory<byte> tokenAsMemory)
{
_jsonClaims = jsonClaims;
_jsonClaimsBytes = jsonClaimsBytes;
_tokenAsMemory = tokenAsMemory;
}
#endif
internal List<Claim> Claims(string issuer)
{
if (_claims == null)
Expand Down Expand Up @@ -71,7 +92,7 @@ internal static void CreateClaimFromObject(List<Claim> claims, string claimType,
else if (value is double d)
claims.Add(new Claim(claimType, d.ToString(CultureInfo.InvariantCulture), ClaimValueTypes.Double, issuer, issuer));
else if (value is DateTime dt)
claims.Add(new Claim(claimType, dt.ToString("o",CultureInfo.InvariantCulture), ClaimValueTypes.DateTime, issuer, issuer));
claims.Add(new Claim(claimType, dt.ToString("o", CultureInfo.InvariantCulture), ClaimValueTypes.DateTime, issuer, issuer));
else if (value is float f)
claims.Add(new Claim(claimType, f.ToString(CultureInfo.InvariantCulture), ClaimValueTypes.Double, issuer, issuer));
else if (value is decimal m)
Expand Down Expand Up @@ -159,17 +180,43 @@ internal Claim GetClaim(string key, string issuer)

internal string GetStringValue(string key)
{
#if NET8_0_OR_GREATER
if (_jsonClaimsBytes.TryGetValue(key, out (int StartIndex, int Length)? location))
{
if (!location.HasValue)
return null;

return Encoding.UTF8.GetString(_tokenAsMemory.Slice(location.Value.StartIndex, location.Value.Length).Span);
}
#else
if (_jsonClaims.TryGetValue(key, out object obj))
{
if (obj == null)
return null;

return obj.ToString();
}
#endif

return string.Empty;
}

#if NET8_0_OR_GREATER
// Similar to GetStringValue but returns the bytes directly.
internal ReadOnlySpan<byte> GetStringBytesValue(string key)
{
if (_jsonClaimsBytes.TryGetValue(key, out (int StartIndex, int Length)? location))
{
if (!location.HasValue)
return null;

return _tokenAsMemory.Slice(location.Value.StartIndex, location.Value.Length).Span;
}

return new Span<byte>();
}
#endif

internal DateTime GetDateTime(string key)
{
long l = GetValue<long>(key, false, out bool found);
Expand Down Expand Up @@ -317,7 +364,7 @@ internal T GetValue<T>(string key, bool throwEx, out bool found)
else if (typeof(T) == typeof(Collection<object>))
return (T)(object)new Collection<object> { obj };

else if(typeof(T).IsEnum)
else if (typeof(T).IsEnum)
{
return (T)Enum.Parse(typeof(T), obj.ToString(), ignoreCase: true);
}
Expand All @@ -339,15 +386,15 @@ internal T GetValue<T>(string key, bool throwEx, out bool found)
if (objType == typeof(long))
return (T)(object)new long[] { (long)obj };

if(objType == typeof(int))
if (objType == typeof(int))
return (T)(object)new long[] { (int)obj };

if (long.TryParse(obj.ToString(), out long value))
return (T)(object)new long[] { value };
}
else if (typeof(T) == typeof(double))
{
if(double.TryParse(obj.ToString(), out double value))
if (double.TryParse(obj.ToString(), out double value))
return (T)(object)value;
}
else if (typeof(T) == typeof(uint))
Expand Down Expand Up @@ -422,6 +469,17 @@ internal bool TryGetClaim(string key, string issuer, out Claim claim)
/// <returns></returns>
internal bool TryGetValue<T>(string key, out T value)
{
#if NET8_0_OR_GREATER
if (typeof(T) == typeof(string))
{
var span = GetStringBytesValue(key);
if (!span.IsEmpty)
{
value = (T)(object)Encoding.UTF8.GetString(span);
return true;
}
}
#endif
value = GetValue<T>(key, false, out bool found);
return found;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ public partial class JsonWebToken
{
internal JsonClaimSet CreatePayloadClaimSet(byte[] bytes, int length)
{
return CreatePayloadClaimSet(bytes.AsSpan(0, length));
return CreatePayloadClaimSet(bytes.AsMemory(0, length));
}

internal JsonClaimSet CreatePayloadClaimSet(ReadOnlySpan<byte> byteSpan)
internal JsonClaimSet CreatePayloadClaimSet(Memory<byte> tokenPayloadAsMemory)
{
Utf8JsonReader reader = new(byteSpan);
Utf8JsonReader reader = new(tokenPayloadAsMemory.Span);
if (!JsonSerializerPrimitives.IsReaderAtTokenType(ref reader, JsonTokenType.StartObject, true))
throw LogHelper.LogExceptionMessage(
new JsonException(
Expand All @@ -33,11 +33,18 @@ internal JsonClaimSet CreatePayloadClaimSet(ReadOnlySpan<byte> byteSpan)
LogHelper.MarkAsNonPII(reader.BytesConsumed))));

Dictionary<string, object> claims = [];
#if NET8_0_OR_GREATER
Dictionary<string, (int startIndex, int length)?> claimsBytes = [];
#endif
while (true)
{
if (reader.TokenType == JsonTokenType.PropertyName)
{
#if NET8_0_OR_GREATER
ReadPayloadValue(ref reader, claims, claimsBytes, tokenPayloadAsMemory);
#else
ReadPayloadValue(ref reader, claims);
#endif
}
// We read a JsonTokenType.StartObject above, exiting and positioning reader at next token.
else if (JsonSerializerPrimitives.IsReaderAtTokenType(ref reader, JsonTokenType.EndObject, false))
Expand All @@ -46,7 +53,11 @@ internal JsonClaimSet CreatePayloadClaimSet(ReadOnlySpan<byte> byteSpan)
break;
};

#if NET8_0_OR_GREATER
return new JsonClaimSet(claims, claimsBytes, tokenPayloadAsMemory);
#else
return new JsonClaimSet(claims);
#endif
}

private protected virtual void ReadPayloadValue(ref Utf8JsonReader reader, IDictionary<string, object> claims)
Expand Down Expand Up @@ -117,5 +128,77 @@ private protected virtual void ReadPayloadValue(ref Utf8JsonReader reader, IDict
claims[propertyName] = JsonSerializerPrimitives.ReadPropertyValueAsObject(ref reader, propertyName, JsonClaimSet.ClassName, true);
}
}

#if NET8_0_OR_GREATER
private protected virtual void ReadPayloadValue(
ref Utf8JsonReader reader,
Dictionary<string, object> claims,
Dictionary<string, (int startIndex, int length)?> claimsBytes,
Memory<byte> tokenAsMemory)
{
if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Aud))
{
_audiences = [];
reader.Read();
if (reader.TokenType == JsonTokenType.StartArray)
{
JsonSerializerPrimitives.ReadStringsSkipNulls(ref reader, _audiences, JwtRegisteredClaimNames.Aud, ClassName);
claims[JwtRegisteredClaimNames.Aud] = _audiences;
}
else
{
if (reader.TokenType != JsonTokenType.Null)
{
_audiences.Add(JsonSerializerPrimitives.ReadString(ref reader, JwtRegisteredClaimNames.Aud, ClassName));
claims[JwtRegisteredClaimNames.Aud] = _audiences[0];
}
else
{
claims[JwtRegisteredClaimNames.Aud] = _audiences;
}
}
}
else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Azp))
{
claimsBytes[JwtRegisteredClaimNames.Azp] = JsonSerializerPrimitives.ReadStringBytesLocation(ref reader, tokenAsMemory, JwtRegisteredClaimNames.Azp, ClassName, true);
}
else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Exp))
{
_exp = JsonSerializerPrimitives.ReadLong(ref reader, JwtRegisteredClaimNames.Exp, ClassName, true);
_expDateTime = EpochTime.DateTime(_exp.Value);
claims[JwtRegisteredClaimNames.Exp] = _exp;
}
else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Iat))
{
_iat = JsonSerializerPrimitives.ReadLong(ref reader, JwtRegisteredClaimNames.Iat, ClassName, true);
_iatDateTime = EpochTime.DateTime(_iat.Value);
claims[JwtRegisteredClaimNames.Iat] = _iat;
}
else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Iss))
{
claimsBytes[JwtRegisteredClaimNames.Iss] = JsonSerializerPrimitives.ReadStringBytesLocation(ref reader, tokenAsMemory, JwtRegisteredClaimNames.Iss, ClassName, true);
}
else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Jti))
{
claimsBytes[JwtRegisteredClaimNames.Jti] = JsonSerializerPrimitives.ReadStringBytesLocation(ref reader, tokenAsMemory, JwtRegisteredClaimNames.Jti, ClassName, true);
}
else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Nbf))
{
_nbf = JsonSerializerPrimitives.ReadLong(ref reader, JwtRegisteredClaimNames.Nbf, ClassName, true);
_nbfDateTime = EpochTime.DateTime(_nbf.Value);
claims[JwtRegisteredClaimNames.Nbf] = _nbf;
}
else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Sub))
{
_sub = JsonSerializerPrimitives.ReadStringOrNumberAsString(ref reader, JwtRegisteredClaimNames.Sub, ClassName, true);
claims[JwtRegisteredClaimNames.Sub] = _sub;
}
else
{
string propertyName = reader.GetString();
claims[propertyName] = JsonSerializerPrimitives.ReadPropertyValueAsObject(ref reader, propertyName, JsonClaimSet.ClassName, true);
}
}
#endif
}
}
Loading
Loading