gpt4 book ai didi

asp.net - 在 Multi-Tenancy 应用中添加JwtBearer

转载 作者:行者123 更新时间:2023-12-03 23:50:58 28 4
gpt4 key购买 nike

我需要在 asp.net core 上实现 Multi-Tenancy REST API 并使用 Jwt Web token 进行身份验证。

Asp.net 核心文档建议在 Startup.cs ConfigureServices 方法中使用以下代码:

    services.AddAuthentication().AddJwtBearer("Bearer", options =>
{
options.Audience = "MyAudience";
options.Authority = "https://myauhorityserver.com";
}

问题是我的 REST API 应用程序是 Multi-Tenancy 的。租户是从 URL 中发现的,例如
https://apple.myapi.com, 
https://samsung.myapi.com,
https://google.myapi.com

因此,每个此类 URL 最终都将指向相同的 IP,但基于 URL 中的第一个字,应用程序会在使用适当的数据库连接时发现租户。

每个这样的租户都有自己的授权 URL。我们使用 Keycloak 作为身份管理服务器,因此其上的每个租户都有自己的 REALM。
所以每个租户的权限 URL 是这样的:
https://mykeycloack.com/auth/realms/11111111, 
https://mykeycloack.com/auth/realms/22222222,
https://mykeycloack.com/auth/realms/33333333

API 应用程序应该能够动态添加和删除租户,而无需重新启动应用程序,因此,在应用程序启动时设置所有租户并不是一个好主意。

根据 options.Events.OnAuthenticationFailed 事件,我试图通过对 AddJwtBearer 的更多调用添加更多架构,但是所有调用都转到架构“Bearer”。目前尚不清楚如何使其他模式处理 HTTP header 中带有 Bearer token 的调用。即使在自定义中间件的帮助下以某种方式可能实现,正如我之前提到的那样,在应用程序启动时为承载 token 身份验证提供租户特定的配置也不是解决方案,因为需要动态添加新租户。

附加信息:
根据 fiddler 的说法,授权 URL 最终与
/.well-known/openid-configuration

并在第一个请求到达标有 API 端点时调用
[Authorize]

如果 to 配置失败,则 API 调用也会失败。如果对配置的调用成功,则应用程序不会在下一个 API 请求中再次调用它。

期待任何建议。提前致谢。

最佳答案

我找到了一个解决方案,并将其设为可重用的 Nuget 包,您可以查看一下。
我几乎用 DynamicBearerTokenHandler 替换了 JwtBearerTokenHandler,它提供了在运行时解析 OpenIdConnectOptions 的灵活性。
我还制作了另一个包来照顾你,你唯一需要的是解析正在进行的请求的权限,你收到 HttpContext.
这是包:
https://github.com/PoweredSoft/DynamicJwtBearer
如何使用它。

public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
services.AddMemoryCache();
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddDynamicJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.TokenValidationParameters.ValidateAudience = false;
})
.AddDynamicAuthorityJwtBearerResolver<ResolveAuthorityService>();

services.AddControllers();
}
}

The service


internal class ResolveAuthorityService : IDynamicJwtBearerAuthorityResolver
{
private readonly IConfiguration configuration;

public ResolveAuthorityService(IConfiguration configuration)
{
this.configuration = configuration;
}

public TimeSpan ExpirationOfConfiguration => TimeSpan.FromHours(1);

public Task<string> ResolveAuthority(HttpContext httpContext)
{
var realm = httpContext.Request.Headers["X-Tenant"].FirstOrDefault() ?? configuration["KeyCloak:MasterRealm"];
var authority = $"{configuration["KeyCloak:Endpoint"]}/realms/{realm}";
return Task.FromResult(authority);
}
}

Whats different from the original one



// before

if (_configuration == null && Options.ConfigurationManager != null)
{
_configuration = await
Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
}

// after
var currentConfiguration = await this.dynamicJwtBearerHanderConfigurationResolver.ResolveCurrentOpenIdConfiguration(Context);

How its replaced


 public static AuthenticationBuilder AddDynamicJwtBearer(this AuthenticationBuilder builder, string authenticationScheme, Action<JwtBearerOptions> action = null)
{
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<JwtBearerOptions>, JwtBearerPostConfigureOptions>());

if (action != null)
return builder.AddScheme<JwtBearerOptions, DynamicJwtBearerHandler>(authenticationScheme, null, action);

return builder.AddScheme<JwtBearerOptions, DynamicJwtBearerHandler>(authenticationScheme, null, _ => { });
}

Source of the Handler


public class DynamicJwtBearerHandler : JwtBearerHandler
{
private readonly IDynamicJwtBearerHanderConfigurationResolver dynamicJwtBearerHanderConfigurationResolver;

public DynamicJwtBearerHandler(IOptionsMonitor<JwtBearerOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, IDynamicJwtBearerHanderConfigurationResolver dynamicJwtBearerHanderConfigurationResolver) : base(options, logger, encoder, clock)
{
this.dynamicJwtBearerHanderConfigurationResolver = dynamicJwtBearerHanderConfigurationResolver;
}

/// <summary>
/// Searches the 'Authorization' header for a 'Bearer' token. If the 'Bearer' token is found, it is validated using <see cref="TokenValidationParameters"/> set in the options.
/// </summary>
/// <returns></returns>
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
string token = null;
try
{
// Give application opportunity to find from a different location, adjust, or reject token
var messageReceivedContext = new MessageReceivedContext(Context, Scheme, Options);

// event can set the token
await Events.MessageReceived(messageReceivedContext);
if (messageReceivedContext.Result != null)
{
return messageReceivedContext.Result;
}

// If application retrieved token from somewhere else, use that.
token = messageReceivedContext.Token;

if (string.IsNullOrEmpty(token))
{
string authorization = Request.Headers[HeaderNames.Authorization];

// If no authorization header found, nothing to process further
if (string.IsNullOrEmpty(authorization))
{
return AuthenticateResult.NoResult();
}

if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
token = authorization.Substring("Bearer ".Length).Trim();
}

// If no token found, no further work possible
if (string.IsNullOrEmpty(token))
{
return AuthenticateResult.NoResult();
}
}

var currentConfiguration = await this.dynamicJwtBearerHanderConfigurationResolver.ResolveCurrentOpenIdConfiguration(Context);
var validationParameters = Options.TokenValidationParameters.Clone();
if (currentConfiguration != null)
{
var issuers = new[] { currentConfiguration.Issuer };
validationParameters.ValidIssuers = validationParameters.ValidIssuers?.Concat(issuers) ?? issuers;

validationParameters.IssuerSigningKeys = validationParameters.IssuerSigningKeys?.Concat(currentConfiguration.SigningKeys)
?? currentConfiguration.SigningKeys;
}

List<Exception> validationFailures = null;
SecurityToken validatedToken;
foreach (var validator in Options.SecurityTokenValidators)
{
if (validator.CanReadToken(token))
{
ClaimsPrincipal principal;
try
{
principal = validator.ValidateToken(token, validationParameters, out validatedToken);
}
catch (Exception ex)
{
Logger.TokenValidationFailed(ex);

// Refresh the configuration for exceptions that may be caused by key rollovers. The user can also request a refresh in the event.
if (Options.RefreshOnIssuerKeyNotFound && Options.ConfigurationManager != null
&& ex is SecurityTokenSignatureKeyNotFoundException)
{
Options.ConfigurationManager.RequestRefresh();
}

if (validationFailures == null)
{
validationFailures = new List<Exception>(1);
}
validationFailures.Add(ex);
continue;
}

Logger.TokenValidationSucceeded();

var tokenValidatedContext = new TokenValidatedContext(Context, Scheme, Options)
{
Principal = principal,
SecurityToken = validatedToken
};

await Events.TokenValidated(tokenValidatedContext);
if (tokenValidatedContext.Result != null)
{
return tokenValidatedContext.Result;
}

if (Options.SaveToken)
{
tokenValidatedContext.Properties.StoreTokens(new[]
{
new AuthenticationToken { Name = "access_token", Value = token }
});
}

tokenValidatedContext.Success();
return tokenValidatedContext.Result;
}
}

if (validationFailures != null)
{
var authenticationFailedContext = new AuthenticationFailedContext(Context, Scheme, Options)
{
Exception = (validationFailures.Count == 1) ? validationFailures[0] : new AggregateException(validationFailures)
};

await Events.AuthenticationFailed(authenticationFailedContext);
if (authenticationFailedContext.Result != null)
{
return authenticationFailedContext.Result;
}

return AuthenticateResult.Fail(authenticationFailedContext.Exception);
}

return AuthenticateResult.Fail("No SecurityTokenValidator available for token: " + token ?? "[null]");
}
catch (Exception ex)
{
Logger.ErrorProcessingMessage(ex);

var authenticationFailedContext = new AuthenticationFailedContext(Context, Scheme, Options)
{
Exception = ex
};

await Events.AuthenticationFailed(authenticationFailedContext);
if (authenticationFailedContext.Result != null)
{
return authenticationFailedContext.Result;
}

throw;
}
}
}

关于asp.net - 在 Multi-Tenancy 应用中添加JwtBearer,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57945073/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com