如何为多租户应用程序设置承载授权?
它是单页面应用程序。在浏览器站点应用程序上使用Adal.js对用户进行身份验证。身份验证应用程序发送请求到ASP.Net-Core服务器端与授权承载头。
ASP.Net-Core使用Microsoft.AspNetCore.Authentication.JwtBearer来检查请求。这是启动:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddAzureAdBearer(options => Configuration.Bind("AzureAd", options));
// ... other ...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseAuthentication();
// ... other ...
}
}
这是AddAzureAdBearer方法:
public static class AzureAdServiceCollectionExtensions
{
public static AuthenticationBuilder AddAzureAdBearer(this AuthenticationBuilder builder)
=> builder.AddAzureAdBearer(_ => { });
public static AuthenticationBuilder AddAzureAdBearer(this AuthenticationBuilder builder, Action<AzureAdOptions> configureOptions)
{
builder.Services.Configure(configureOptions);
builder.Services.AddSingleton<IConfigureOptions<JwtBearerOptions>, ConfigureAzureOptions>();
builder.AddJwtBearer();
return builder;
}
private class ConfigureAzureOptions : IConfigureNamedOptions<JwtBearerOptions>
{
private readonly AzureAdOptions AzureOptions;
public ConfigureAzureOptions(IOptions<AzureAdOptions> azureOptions)
{
AzureOptions = azureOptions.Value;
}
public void Configure(string name, JwtBearerOptions options)
{
options.Audience = AzureOptions.ClientId;
// this works (specific TenantId)
// options.Authority
// = "https://login.microsoftonline.com/f8811864-6950-4347-af1c-9d22bb3d0615"
// this did not work (common instead of specific TenantId)
// options.Authority
// = "https://login.microsoftonline.com/common";
options.Authority = $"{AzureOptions.Instance}{AzureOptions.TenantId}";
}
public void Configure(JwtBearerOptions options)
{
Configure(Options.DefaultName, options);
}
}
}
对于单租户,这可以按预期工作,可以使用[授权]属性标记控制器
[Route("api/[controller]")]
[Authorize]
public class CalendarController : Controller
{
对于多租户,我将Adal.js设置为公共端点,并且它正在工作(用户可以成功登录)。但ASP.Net-Core服务器无法检查Bearer标头,就像单租户一样
JwtBearerOptions.Authority =“https://login.microsoftonline.com/f8811864-6950-4347-af1c-9d22bb3d0615”
对于多租户我尝试发送
JwtBearerOptions.Authority =“https://login.microsoftonline.com/common”
ASP.Net-Core服务器返回未经授权的响应。
UPDATE
邮政The Common Endpoint: Walks Like a Tenant, Talks Like a Tenant… But Is Not a Tenant描述了共同权威问题的原因。
简而言之:令牌(作为授权承载头发送并且必须在服务器端验证)包含“issuer”字符串,如下所示:https://sts.windows.net/<TENAT_ID>
。 <TENAT_ID>
- 将是真正的<TENAT_ID>
而不是“普通”字符串。
因此,当验证授权承载头时,“issuer”字符串与配置的选项进行比较。权限设置。
要解决此问题,可以禁用颁发者验证。并自己做:
public void Configure(string name, JwtBearerOptions options)
{
options.Audience = AzureOptions.ClientId;
options.TokenValidationParameters = new TokenValidationParameters{
ValidateIssuer = false
};
options.Events = new JwtBearerEvents()
{
OnTokenValidated = (context) =>
{
if(!context.SecurityToken.Issuer.StartsWith("https://sts.windows.net/"))
throw new SecurityTokenValidationException();
return Task.FromResult(0);
}
};
options.Authority = $"{AzureOptions.Instance}{AzureOptions.TenantId}";
}
我不确定检查发行人是否正确。请让我知道它是否正确。
是的,你是对的。对于多租户应用程序,将ValidateIssuer设置为false。这意味着该应用程序将验证发行者。
在JwtBearerEvents.TokenValidated事件中验证令牌颁发者。发行人在“iss”索赔中发送。
public override async Task TokenValidated(TokenValidatedContext context)
{
var principal = context.Ticket.Principal;
var tenantManager = context.HttpContext.RequestServices.GetService<TenantManager>();
var userManager = context.HttpContext.RequestServices.GetService<UserManager>();
var issuerValue = principal.GetIssuerValue();
var tenant = await tenantManager.FindByIssuerValueAsync(issuerValue);
if (tenant == null)
{
// The caller was not from a trusted issuer. Throw to block the authentication flow.
throw new SecurityTokenValidationException();
}
var identity = principal.Identities.First();
}
您可以参考Microsoft文档的此部分以供参考 - Authenticating in the web API
这篇文章显示了如何进行自定义颁发者验证。 https://thomaslevesque.com/2018/12/24/multitenant-azure-ad-issuer-validation-in-asp-net-core/
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "https://login.microsoftonline.com/common";
options.Audience = configuration["AzureAdSettings:ClientId"];
options.RequireHttpsMetadata = true; // or false if you dont have https
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerValidator = (issuer, token, parameters) => issuer
//allows any issuer or use `ValidateIssuerWithPlaceholder`
};
});
帖子有这种方法来验证令牌
private static string ValidateIssuerWithPlaceholder(string issuer, SecurityToken token, TokenValidationParameters parameters)
{
// Accepts any issuer of the form "https://login.microsoftonline.com/{tenantid}/v2.0",
// where tenantid is the tid from the token.
if (token is JwtSecurityToken jwt)
{
if (jwt.Payload.TryGetValue("tid", out var value) &&
value is string tokenTenantId)
{
var validIssuers = (parameters.ValidIssuers ?? Enumerable.Empty<string>())
.Append(parameters.ValidIssuer)
.Where(i => !string.IsNullOrEmpty(i));
if (validIssuers.Any(i => i.Replace("{tenantid}", tokenTenantId) == issuer))
return issuer;
}
}
// Recreate the exception that is thrown by default
// when issuer validation fails
var validIssuer = parameters.ValidIssuer ?? "null";
var validIssuers = parameters.ValidIssuers == null
? "null"
: !parameters.ValidIssuers.Any()
? "empty"
: string.Join(", ", parameters.ValidIssuers);
string errorMessage = FormattableString.Invariant(
$"IDX10205: Issuer validation failed. Issuer: '{issuer}'. Did not match: validationParameters.ValidIssuer: '{validIssuer}' or validationParameters.ValidIssuers: '{validIssuers}'.");
throw new SecurityTokenInvalidIssuerException(errorMessage)
{
InvalidIssuer = issuer
};
}
当您为中间件指定权限时,它将自动尝试查找公钥以验证令牌,如此处所述https://github.com/Azure-Samples/active-directory-javascript-singlepageapp-dotnet-webapi-v2/issues/7