diff --git a/benchmark/Microsoft.IdentityModel.Benchmarks/ValidatorBenchmarks.cs b/benchmark/Microsoft.IdentityModel.Benchmarks/ValidatorBenchmarks.cs new file mode 100644 index 0000000000..8a77031c81 --- /dev/null +++ b/benchmark/Microsoft.IdentityModel.Benchmarks/ValidatorBenchmarks.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using BenchmarkDotNet.Attributes; +using Microsoft.IdentityModel.Tokens; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.IdentityModel.Benchmarks +{ + /// + /// Benchmarks for validator methods to measure performance of different implementations + /// + public class ValidatorBenchmarks + { + private List _audiences; + private TokenValidationParameters _parametersWithList; + private TokenValidationParameters _parametersWithEnumerable; + + [GlobalSetup] + public void Setup() + { + _audiences = ["audience1"]; + var validAudiences = new List { "invalid1", "invalid2", "audience1" }; // Make sure audience is last to maximize iteration + + _parametersWithList = new TokenValidationParameters + { + ValidAudiences = validAudiences + }; + + _parametersWithEnumerable = new TokenValidationParameters + { + ValidAudiences = validAudiences.Select(x => x) // Force enumerable by using LINQ + }; + } + + [Benchmark(Baseline = true)] + public void ValidateAudience_WithList() + { + Validators.ValidateAudience(_audiences, null, _parametersWithList); + } + + [Benchmark] + public void ValidateAudience_WithEnumerable() + { + Validators.ValidateAudience(_audiences, null, _parametersWithEnumerable); + } + } +} diff --git a/src/Microsoft.IdentityModel.Tokens/Validators.cs b/src/Microsoft.IdentityModel.Tokens/Validators.cs index 033afa3aea..6537571426 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validators.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validators.cs @@ -143,10 +143,10 @@ private static bool AudienceIsValid(IEnumerable audiences, TokenValidati if (string.IsNullOrWhiteSpace(tokenAudience)) continue; - foreach (string validAudience in validationParametersAudiences) + bool TryMatchAudience(string validAudience) { if (string.IsNullOrWhiteSpace(validAudience)) - continue; + return false; if (AudiencesMatch(validationParameters, tokenAudience, validAudience)) { @@ -155,6 +155,25 @@ private static bool AudienceIsValid(IEnumerable audiences, TokenValidati return true; } + + return false; + } + + if (validationParametersAudiences is IList audienceList) + { + for (int i = 0; i < audienceList.Count; i++) + { + if (TryMatchAudience(audienceList[i])) + return true; + } + } + else + { + foreach (string validAudience in validationParametersAudiences) + { + if (TryMatchAudience(validAudience)) + return true; + } } } diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/Microsoft.IdentityModel.Tokens.Tests.csproj b/test/Microsoft.IdentityModel.Tokens.Tests/Microsoft.IdentityModel.Tokens.Tests.csproj index 339a012f98..ae85d7c4b5 100644 --- a/test/Microsoft.IdentityModel.Tokens.Tests/Microsoft.IdentityModel.Tokens.Tests.csproj +++ b/test/Microsoft.IdentityModel.Tokens.Tests/Microsoft.IdentityModel.Tokens.Tests.csproj @@ -22,7 +22,7 @@ - + diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/Validation/ValidatorsTests.cs b/test/Microsoft.IdentityModel.Tokens.Tests/Validation/ValidatorsTests.cs index 9d3ab30aad..2e90c50ee4 100644 --- a/test/Microsoft.IdentityModel.Tokens.Tests/Validation/ValidatorsTests.cs +++ b/test/Microsoft.IdentityModel.Tokens.Tests/Validation/ValidatorsTests.cs @@ -270,6 +270,23 @@ public static TheoryData ValidateAudienceTheoryDat Audiences = audiences1WithTwoSlashes, ExpectedException = ExpectedException.SecurityTokenInvalidAudienceException("IDX10214:"), TokenValidationParameters = new TokenValidationParameters{ ValidAudience = audience1 } + }, + new AudienceValidationTheoryData("ValidAudiencesList_OptimizedPath") + { + Audiences = new List { "audience1" }, + TokenValidationParameters = new TokenValidationParameters + { + ValidAudiences = new List { "invalidAudience", "audience1" } // Using List to test IList optimization + } + }, + new AudienceValidationTheoryData("ValidAudiencesList_EmptyList_OptimizedPath") + { + Audiences = new List { "audience1" }, + ExpectedException = ExpectedException.SecurityTokenInvalidAudienceException("IDX10214:"), + TokenValidationParameters = new TokenValidationParameters + { + ValidAudiences = new List() // Empty list should still return false through optimized path + } } }; }