将Azure-AD与dotnet core 2 web-api中的本地用户数据库相结合

时间:2018-03-20 15:02:54

标签: .net-core azure-active-directory

我正在创建一个.net-core2 web-api,它允许Azure-AD中的用户使用它。 API是多租户的,因此来自多个Azure-AD的用户应该能够进行授权。

但是,也可以为没有企业Azure-AD帐户的用户创建帐户。这些用户存储在数据库(本地用户)中。

因为它是一个web-api,我实现了一个自定义令牌提供程序,以便本地用户可以获得一个令牌来使用受保护的web-api。

但是,我无法在web-api中添加两个单独的“Bearer”身份验证:

services.AddAuthentication(options =>
{
    options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddAzureAdBearer(options => Configuration.Bind("AzureAd", options))
.AddJwtBearer(options => new JwtBearerOptions {
     TokenValidationParameters = tokenValidationParameters  
 });

这会引发错误:

  

System.InvalidOperationException:Scheme已存在:Bearer

我完全理解。但是我如何能够并行实现两种身份验证机制呢?

2 个答案:

答案 0 :(得分:1)

您必须指定其他标识符。两者都在使用" Bearer"目前的标识符。

例如,您可以通过以下方式为JWT Bearer指定另一个:

.AddJwtBearer("CustomJwt", options => { });

这解决了标识符冲突的问题,但为了并行支持两种身份验证方案,您需要进行其他修改。

2.0中的一种方式是David Fowler提出的建议:https://github.com/aspnet/Security/issues/1469

app.UseAuthentication();

app.Use(async (context, next) =>
{
    // Write some code that determines the scheme based on the incoming request
    var scheme = GetSchemeForRequest(context);
    var result = await context.AuthenticateAsync(scheme);
    if (result.Succeeded)
    {
        context.User = result.Principal;
    }
    await next();
});

在您的情况下,如果在您访问中间件时上下文中没有用户,则可以使用所有承载(Azure AD)方案。

在ASP.NET Core 2.1中,我们将获得"虚拟身份验证方案",它允许以更一流的方式使用此方案:https://github.com/aspnet/Security/pull/1550

答案 1 :(得分:0)

感谢juunas,我找到了一个有效的解决方案。我做了什么:

在Startup.cs ConfigureServices中,我添加了两种身份验证方案:

services.AddAuthentication(options =>
{
    options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddAzureAdBearer(options => Configuration.Bind("AzureAd", options))
.AddJwtBearer("JWTBearer", options => {
    options.TokenValidationParameters = tokenValidationParameters;
});

然后在授权中确保启用两个方案:

services.AddAuthorization(config => {
     config.AddPolicy(PolicyNames.RequireKeyUser,
        policy =>
        {
            policy.AddRequirements(new KeyUserRequirement());
            policy.RequireAuthenticatedUser();  
            policy.AddAuthenticationSchemes("JWTBearer", JwtBearerDefaults.AuthenticationScheme);
        });
});

在Configure中编写一些逻辑以确定运行时的auth方案:

app.Use(async (context, next) =>
        {
            // Write some code that determines the scheme based on the incoming request
            var scheme = GetSchemeForRequest(context);
            if (!String.IsNullOrEmpty(scheme)) {
                var result = await context.AuthenticateAsync(scheme);
                if (result.Succeeded)
                {
                    context.User = result.Principal;
                }
            } 
            await next();
        });

我决定使用额外的标题'Authorization-Type'来定义我的自定义JWT授权,并在'Authorization'标题中使用默认的'Bearer'前缀。所以我的GetSchemeForRequest函数:

private string GetSchemeForRequest(HttpContext context)
{
    var scheme = "";

    try {
        if (!String.IsNullOrEmpty(context.Request.Headers["Authorization"].ToString())) {
            string authHeader = context.Request.Headers["Authorization-Type"].ToString();

            if (authHeader == "JWT") {
                scheme = "JWTBearer";
            } else {
                scheme = "Bearer";
            }
        }
    }
    catch (Exception ex) {
        // Use your own logging mechanism
    }

    return scheme;
}