Skip to content
代码片段 群组 项目
未验证 提交 f0e1d1eb 编辑于 作者: Safia Abdalla's avatar Safia Abdalla 提交者: GitHub
浏览文件

Fix up reading auth schemes and setting default scheme (#42452)

* Fix up reading auth schemes and setting default scheme
* Address feedback from peer review
上级 a0513eaa
No related branches found
No related tags found
无相关合并请求
...@@ -14,6 +14,7 @@ namespace Microsoft.AspNetCore.Authentication; ...@@ -14,6 +14,7 @@ namespace Microsoft.AspNetCore.Authentication;
public class AuthenticationService : IAuthenticationService public class AuthenticationService : IAuthenticationService
{ {
private HashSet<ClaimsPrincipal>? _transformCache; private HashSet<ClaimsPrincipal>? _transformCache;
private const string defaultSchemesOptionsMsg = "The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action<AuthenticationOptions> configureOptions) or by setting the Authentication:DefaultScheme property in configuration.";
/// <summary> /// <summary>
/// Constructor. /// Constructor.
...@@ -64,7 +65,7 @@ public class AuthenticationService : IAuthenticationService ...@@ -64,7 +65,7 @@ public class AuthenticationService : IAuthenticationService
scheme = defaultScheme?.Name; scheme = defaultScheme?.Name;
if (scheme == null) if (scheme == null)
{ {
throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultAuthenticateScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action<AuthenticationOptions> configureOptions)."); throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultAuthenticateScheme found. {defaultSchemesOptionsMsg}");
} }
} }
...@@ -112,7 +113,7 @@ public class AuthenticationService : IAuthenticationService ...@@ -112,7 +113,7 @@ public class AuthenticationService : IAuthenticationService
scheme = defaultChallengeScheme?.Name; scheme = defaultChallengeScheme?.Name;
if (scheme == null) if (scheme == null)
{ {
throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultChallengeScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action<AuthenticationOptions> configureOptions)."); throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultChallengeScheme found. {defaultSchemesOptionsMsg}");
} }
} }
...@@ -140,7 +141,7 @@ public class AuthenticationService : IAuthenticationService ...@@ -140,7 +141,7 @@ public class AuthenticationService : IAuthenticationService
scheme = defaultForbidScheme?.Name; scheme = defaultForbidScheme?.Name;
if (scheme == null) if (scheme == null)
{ {
throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultForbidScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action<AuthenticationOptions> configureOptions)."); throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultForbidScheme found. {defaultSchemesOptionsMsg}");
} }
} }
...@@ -186,7 +187,7 @@ public class AuthenticationService : IAuthenticationService ...@@ -186,7 +187,7 @@ public class AuthenticationService : IAuthenticationService
scheme = defaultScheme?.Name; scheme = defaultScheme?.Name;
if (scheme == null) if (scheme == null)
{ {
throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultSignInScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action<AuthenticationOptions> configureOptions)."); throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultSignInScheme found. {defaultSchemesOptionsMsg}");
} }
} }
...@@ -220,7 +221,7 @@ public class AuthenticationService : IAuthenticationService ...@@ -220,7 +221,7 @@ public class AuthenticationService : IAuthenticationService
scheme = defaultScheme?.Name; scheme = defaultScheme?.Name;
if (scheme == null) if (scheme == null)
{ {
throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultSignOutScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action<AuthenticationOptions> configureOptions)."); throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultSignOutScheme found. {defaultSchemesOptionsMsg}");
} }
} }
......
...@@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Authentication; ...@@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Authentication;
/// </summary> /// </summary>
public static class AuthenticationConfigurationProviderExtensions public static class AuthenticationConfigurationProviderExtensions
{ {
private const string AuthenticationSchemesKey = "Authentication:Schemes"; private const string AuthenticationSchemesKey = "Schemes";
/// <summary> /// <summary>
/// Returns the specified <see cref="IConfiguration"/> object. /// Returns the specified <see cref="IConfiguration"/> object.
......
...@@ -11,6 +11,7 @@ using Microsoft.Extensions.Configuration; ...@@ -11,6 +11,7 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Moq; using Moq;
namespace Microsoft.AspNetCore.Authentication; namespace Microsoft.AspNetCore.Authentication;
...@@ -156,6 +157,11 @@ public class AuthenticationMiddlewareTests ...@@ -156,6 +157,11 @@ public class AuthenticationMiddlewareTests
public async Task WebApplicationBuilder_RegistersAuthenticationMiddlewares() public async Task WebApplicationBuilder_RegistersAuthenticationMiddlewares()
{ {
var builder = WebApplication.CreateBuilder(); var builder = WebApplication.CreateBuilder();
builder.Configuration.AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("Authentication:Schemes:Bearer:ClaimsIssuer", "SomeIssuer"),
new KeyValuePair<string, string>("Authentication:Schemes:Bearer:Audiences:0", "https://localhost:5001")
});
builder.Authentication.AddJwtBearer(); builder.Authentication.AddJwtBearer();
await using var app = builder.Build(); await using var app = builder.Build();
...@@ -169,6 +175,10 @@ public class AuthenticationMiddlewareTests ...@@ -169,6 +175,10 @@ public class AuthenticationMiddlewareTests
await app.StartAsync(); await app.StartAsync();
Assert.True(app.Properties.ContainsKey("__AuthenticationMiddlewareSet")); Assert.True(app.Properties.ContainsKey("__AuthenticationMiddlewareSet"));
var options = app.Services.GetService<IOptionsMonitor<JwtBearerOptions>>().Get(JwtBearerDefaults.AuthenticationScheme);
Assert.Equal(new[] { "SomeIssuer" }, options.TokenValidationParameters.ValidIssuers);
Assert.Equal(new[] { "https://localhost:5001" }, options.TokenValidationParameters.ValidAudiences);
} }
private HttpContext GetHttpContext( private HttpContext GetHttpContext(
......
...@@ -45,7 +45,7 @@ internal sealed class ClearCommand ...@@ -45,7 +45,7 @@ internal sealed class ClearCommand
if (!force) if (!force)
{ {
reporter.Output(Resources.ClearCommand_Permission); reporter.Output(Resources.FormatClearCommand_Permission(count, project));
reporter.Output("[Y]es / [N]o"); reporter.Output("[Y]es / [N]o");
if (Console.ReadLine().Trim().ToUpperInvariant() != "Y") if (Console.ReadLine().Trim().ToUpperInvariant() != "Y")
{ {
......
...@@ -10,6 +10,7 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer.Tools; ...@@ -10,6 +10,7 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer.Tools;
internal sealed record JwtAuthenticationSchemeSettings(string SchemeName, List<string> Audiences, string ClaimsIssuer) internal sealed record JwtAuthenticationSchemeSettings(string SchemeName, List<string> Audiences, string ClaimsIssuer)
{ {
private const string AuthenticationKey = "Authentication"; private const string AuthenticationKey = "Authentication";
private const string DefaultSchemeKey = "DefaultScheme";
private const string SchemesKey = "Schemes"; private const string SchemesKey = "Schemes";
private static readonly JsonSerializerOptions _jsonSerializerOptions = new JsonSerializerOptions private static readonly JsonSerializerOptions _jsonSerializerOptions = new JsonSerializerOptions
...@@ -35,7 +36,7 @@ internal sealed record JwtAuthenticationSchemeSettings(string SchemeName, List<s ...@@ -35,7 +36,7 @@ internal sealed record JwtAuthenticationSchemeSettings(string SchemeName, List<s
{ {
// If a scheme with the same name has already been registered, we // If a scheme with the same name has already been registered, we
// override with the latest token's options // override with the latest token's options
schemes[SchemeName] = settingsObject; schemes[SchemeName] = settingsObject;
} }
else else
{ {
...@@ -56,6 +57,15 @@ internal sealed record JwtAuthenticationSchemeSettings(string SchemeName, List<s ...@@ -56,6 +57,15 @@ internal sealed record JwtAuthenticationSchemeSettings(string SchemeName, List<s
}; };
} }
// Set the DefaultScheme if it has not already been set
// and only a single scheme has been configured thus far
if (config[AuthenticationKey][DefaultSchemeKey] is null
&& config[AuthenticationKey][SchemesKey] is JsonObject setSchemes
&& setSchemes.Count == 1)
{
config[AuthenticationKey][DefaultSchemeKey] = SchemeName;
}
using var writer = new FileStream(filePath, FileMode.Open, FileAccess.Write); using var writer = new FileStream(filePath, FileMode.Open, FileAccess.Write);
JsonSerializer.Serialize(writer, config, _jsonSerializerOptions); JsonSerializer.Serialize(writer, config, _jsonSerializerOptions);
} }
...@@ -70,6 +80,11 @@ internal sealed record JwtAuthenticationSchemeSettings(string SchemeName, List<s ...@@ -70,6 +80,11 @@ internal sealed record JwtAuthenticationSchemeSettings(string SchemeName, List<s
authentication[SchemesKey] is JsonObject schemes) authentication[SchemesKey] is JsonObject schemes)
{ {
schemes.Remove(name); schemes.Remove(name);
if (authentication[DefaultSchemeKey] is JsonValue defaultScheme
&& defaultScheme.GetValue<string>() == name)
{
authentication.Remove(DefaultSchemeKey);
}
} }
using var writer = new FileStream(filePath, FileMode.Create, FileAccess.Write); using var writer = new FileStream(filePath, FileMode.Create, FileAccess.Write);
......
...@@ -62,7 +62,7 @@ public class UserJwtsTestFixture : IDisposable ...@@ -62,7 +62,7 @@ public class UserJwtsTestFixture : IDisposable
} }
}"; }";
public string CreateProject(bool hasSecret = true) public string CreateProject(bool hasSecret = true, string appSettingsContent = "{}")
{ {
var projectPath = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), "userjwtstest", Guid.NewGuid().ToString())); var projectPath = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), "userjwtstest", Guid.NewGuid().ToString()));
Directory.CreateDirectory(Path.Combine(projectPath.FullName, "Properties")); Directory.CreateDirectory(Path.Combine(projectPath.FullName, "Properties"));
...@@ -81,7 +81,7 @@ public class UserJwtsTestFixture : IDisposable ...@@ -81,7 +81,7 @@ public class UserJwtsTestFixture : IDisposable
File.WriteAllText( File.WriteAllText(
Path.Combine(projectPath.FullName, "appsettings.Development.json"), Path.Combine(projectPath.FullName, "appsettings.Development.json"),
"{}"); appSettingsContent);
if (hasSecret) if (hasSecret)
{ {
......
...@@ -67,15 +67,61 @@ public class UserJwtsTests : IClassFixture<UserJwtsTestFixture> ...@@ -67,15 +67,61 @@ public class UserJwtsTests : IClassFixture<UserJwtsTestFixture>
} }
[Fact] [Fact]
public void Create_WritesGeneratedTokenToDisk() public async Task Create_SetsDefaultSchemeIfNoOtherSchemesSet()
{ {
var project = Path.Combine(_fixture.CreateProject(), "TestProject.csproj"); var project = Path.Combine(_fixture.CreateProject(), "TestProject.csproj");
var appsettings = Path.Combine(Path.GetDirectoryName(project), "appsettings.Development.json"); var appSettingsPath = Path.Combine(Path.GetDirectoryName(project), "appsettings.Development.json");
var app = new Program(_console); var app = new Program(_console);
app.Run(new[] { "create", "--project", project }); app.Run(new[] { "create", "--project", project });
Assert.Contains("New JWT saved", _console.GetOutput()); Assert.Contains("New JWT saved", _console.GetOutput());
Assert.Contains("dotnet-user-jwts", File.ReadAllText(appsettings));
using FileStream openStream = File.OpenRead(appSettingsPath);
var appSettingsFile = await JsonSerializer.DeserializeAsync<JsonObject>(openStream);
Assert.True(appSettingsFile.TryGetPropertyValue("Authentication", out var authentication));
Assert.Equal("Bearer", authentication["DefaultScheme"].GetValue<string>());
Assert.Equal("dotnet-user-jwts", authentication["Schemes"]["Bearer"]["ClaimsIssuer"].GetValue<string>());
}
[Fact]
public async Task Create_DoesNotOverrideDefaultSchemeIfAlreadySet()
{
var project = Path.Combine(_fixture.CreateProject(
hasSecret: true,
appSettingsContent: @"{ ""Authentication"": { ""DefaultScheme"": ""foobar"" } }"), "TestProject.csproj");
var appSettingsPath = Path.Combine(Path.GetDirectoryName(project), "appsettings.Development.json");
var app = new Program(_console);
app.Run(new[] { "create", "--project", project });
Assert.Contains("New JWT saved", _console.GetOutput());
using FileStream openStream = File.OpenRead(appSettingsPath);
var appSettingsFile = await JsonSerializer.DeserializeAsync<JsonObject>(openStream);
Assert.True(appSettingsFile.TryGetPropertyValue("Authentication", out var authentication));
Assert.Equal("foobar", authentication["DefaultScheme"].GetValue<string>()); //foobar not Bearer
Assert.Equal("dotnet-user-jwts", authentication["Schemes"]["Bearer"]["ClaimsIssuer"].GetValue<string>());
}
[Fact]
public async Task Create_DoesNotSetDefaultSchemeIfMultipleSchemesConfigured()
{
var project = Path.Combine(_fixture.CreateProject(
hasSecret: true,
appSettingsContent: @"{ ""Authentication"": { ""Schemes"": { ""foobar"" : { } } } }"), "TestProject.csproj");
var appSettingsPath = Path.Combine(Path.GetDirectoryName(project), "appsettings.Development.json");
var app = new Program(_console);
app.Run(new[] { "create", "--project", project });
Assert.Contains("New JWT saved", _console.GetOutput());
using FileStream openStream = File.OpenRead(appSettingsPath);
var appSettingsFile = await JsonSerializer.DeserializeAsync<JsonObject>(openStream);
Assert.True(appSettingsFile.TryGetPropertyValue("Authentication", out var authentication));
Assert.Null(authentication["DefaultScheme"]); // Should not be set beause 2 schemes configured
Assert.Equal("dotnet-user-jwts", authentication["Schemes"]["Bearer"]["ClaimsIssuer"].GetValue<string>());
} }
[Fact] [Fact]
...@@ -92,7 +138,6 @@ public class UserJwtsTests : IClassFixture<UserJwtsTestFixture> ...@@ -92,7 +138,6 @@ public class UserJwtsTests : IClassFixture<UserJwtsTestFixture>
public void List_ReturnsIdForGeneratedToken() public void List_ReturnsIdForGeneratedToken()
{ {
var project = Path.Combine(_fixture.CreateProject(), "TestProject.csproj"); var project = Path.Combine(_fixture.CreateProject(), "TestProject.csproj");
var appsettings = Path.Combine(Path.GetDirectoryName(project), "appsettings.Development.json");
var app = new Program(_console); var app = new Program(_console);
app.Run(new[] { "create", "--project", project, "--scheme", "MyCustomScheme" }); app.Run(new[] { "create", "--project", project, "--scheme", "MyCustomScheme" });
...@@ -103,10 +148,10 @@ public class UserJwtsTests : IClassFixture<UserJwtsTestFixture> ...@@ -103,10 +148,10 @@ public class UserJwtsTests : IClassFixture<UserJwtsTestFixture>
} }
[Fact] [Fact]
public void Remove_RemovesGeneratedToken() public async Task Remove_RemovesGeneratedToken()
{ {
var project = Path.Combine(_fixture.CreateProject(), "TestProject.csproj"); var project = Path.Combine(_fixture.CreateProject(), "TestProject.csproj");
var appsettings = Path.Combine(Path.GetDirectoryName(project), "appsettings.Development.json"); var appSettingsPath = Path.Combine(Path.GetDirectoryName(project), "appsettings.Development.json");
var app = new Program(_console); var app = new Program(_console);
app.Run(new[] { "create", "--project", project }); app.Run(new[] { "create", "--project", project });
...@@ -115,16 +160,45 @@ public class UserJwtsTests : IClassFixture<UserJwtsTestFixture> ...@@ -115,16 +160,45 @@ public class UserJwtsTests : IClassFixture<UserJwtsTestFixture>
app.Run(new[] { "create", "--project", project, "--scheme", "Scheme2" }); app.Run(new[] { "create", "--project", project, "--scheme", "Scheme2" });
app.Run(new[] { "remove", id, "--project", project }); app.Run(new[] { "remove", id, "--project", project });
var appsettingsContent = File.ReadAllText(appsettings);
Assert.DoesNotContain("Bearer", appsettingsContent); using FileStream openStream = File.OpenRead(appSettingsPath);
Assert.Contains("Scheme2", appsettingsContent); var appSettingsFile = await JsonSerializer.DeserializeAsync<JsonObject>(openStream);
Assert.True(appSettingsFile.TryGetPropertyValue("Authentication", out var authentication));
Assert.Null(authentication["Schemes"]["Bearer"]);
Assert.NotNull(authentication["Schemes"]["Scheme2"]);
Assert.Null(authentication["DefaultScheme"]);
}
[Fact]
public async Task Remove_DoesNotUnsetDefaultSchemeIfNoMatch()
{
var project = Path.Combine(_fixture.CreateProject(), "TestProject.csproj");
var appSettingsPath = Path.Combine(Path.GetDirectoryName(project), "appsettings.Development.json");
var app = new Program(_console);
app.Run(new[] { "create", "--project", project });
_console.ClearOutput();
app.Run(new[] { "create", "--project", project, "--scheme", "Scheme2" });
var matches = Regex.Matches(_console.GetOutput(), "New JWT saved with ID '(.*?)'");
var id = matches.SingleOrDefault().Groups[1].Value;
app.Run(new[] { "remove", id, "--project", project });
using FileStream openStream = File.OpenRead(appSettingsPath);
var appSettingsFile = await JsonSerializer.DeserializeAsync<JsonObject>(openStream);
Assert.True(appSettingsFile.TryGetPropertyValue("Authentication", out var authentication));
Assert.NotNull(authentication["Schemes"]["Bearer"]);
Assert.Null(authentication["Schemes"]["Scheme2"]);
Assert.NotNull(authentication["DefaultScheme"]); // We haven't removed the Bearer scheme so it's still the default
} }
[Fact] [Fact]
public void Clear_RemovesGeneratedTokens() public async Task Clear_RemovesGeneratedTokens()
{ {
var project = Path.Combine(_fixture.CreateProject(), "TestProject.csproj"); var project = Path.Combine(_fixture.CreateProject(), "TestProject.csproj");
var appsettings = Path.Combine(Path.GetDirectoryName(project), "appsettings.Development.json"); var appSettingsPath = Path.Combine(Path.GetDirectoryName(project), "appsettings.Development.json");
var app = new Program(_console); var app = new Program(_console);
app.Run(new[] { "create", "--project", project }); app.Run(new[] { "create", "--project", project });
...@@ -133,9 +207,14 @@ public class UserJwtsTests : IClassFixture<UserJwtsTestFixture> ...@@ -133,9 +207,14 @@ public class UserJwtsTests : IClassFixture<UserJwtsTestFixture>
Assert.Contains("New JWT saved", _console.GetOutput()); Assert.Contains("New JWT saved", _console.GetOutput());
app.Run(new[] { "clear", "--project", project, "--force" }); app.Run(new[] { "clear", "--project", project, "--force" });
var appsettingsContent = File.ReadAllText(appsettings);
Assert.DoesNotContain("Bearer", appsettingsContent); using FileStream openStream = File.OpenRead(appSettingsPath);
Assert.DoesNotContain("Scheme2", appsettingsContent); var appSettingsFile = await JsonSerializer.DeserializeAsync<JsonObject>(openStream);
Assert.True(appSettingsFile.TryGetPropertyValue("Authentication", out var authentication));
Assert.Null(authentication["Schemes"]["Bearer"]);
Assert.Null(authentication["Schemes"]["Scheme2"]);
Assert.Null(authentication["DefaultScheme"]);
} }
[Fact] [Fact]
......
0% 加载中 .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册