使用 ASP.NET Core 中的特定方案授权

有关 ASP.NET Core 中的身份验证方案的简介,请参阅身份验证方案

在某些情况下,例如单页应用程序 (SPA),通常会使用多种身份验证方法。 例如,应用可能会针对登录使用基于 cookie 的身份验证,并使用 JWT 持有者身份验证来处理 JavaScript 请求。 在某些情况下,应用可能会有一个身份验证处理程序的多个实例。 例如,有两个 cookie 处理程序,一个包含基本标识,另一个是在多重身份验证 (MFA) 触发后创建的。 可能会触发 MFA,因为用户请求了需要额外安全性的操作。 有关当用户请求需要 MFA 的资源时强制执行 MFA 的详细信息,请参阅使用 MFA 保护部分这一 GitHub 问题。

身份验证方案是在身份验证期间配置身份验证服务时命名的。 例如:

using Microsoft.AspNetCore.Authentication;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication()
        .AddCookie(options =>
        {
            options.LoginPath = "/Account/Unauthorized/";
            options.AccessDeniedPath = "/Account/Forbidden/";
        })
        .AddJwtBearer(options =>
        {
            options.Audience = "http://localhost:5001/";
            options.Authority = "http://localhost:5000/";
        });

builder.Services.AddAuthentication()
        .AddIdentityServerJwt();

builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseAuthentication();
app.UseIdentityServer();
app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.MapFallbackToFile("index.html");

app.Run();

在上面的代码中,添加了两个身份验证处理程序:一个用于 cookie,一个用于持有者。

注意

指定默认方案会导致 HttpContext.User 属性设置为该标识。 如果不需要该行为,请通过调用 AddAuthentication 的无参数形式来禁用它。

使用 Authorize 特性选择方案

在授权时,应用指示要使用的处理程序。 选择应用要通过将以逗号分隔的身份验证方案列表传递给 [Authorize] 来进行授权的处理程序。 [Authorize] 特性指定要使用的身份验证方案,不管是否存在默认设置。 例如:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Mvc;

namespace AuthScheme.Controllers;

[Authorize(AuthenticationSchemes = AuthSchemes)]
public class MixedController : Controller
{
    private const string AuthSchemes =
        CookieAuthenticationDefaults.AuthenticationScheme + "," +
        JwtBearerDefaults.AuthenticationScheme;
    public ContentResult Index() => Content(MyWidgets.GetMyContent());

}

在上面的示例中,cookie 和持有者处理程序都将运行,并且可能会为当前的用户创建并追加标识。 通过仅指定单个方案,相应的处理程序将运行:

[Authorize(AuthenticationSchemes=JwtBearerDefaults.AuthenticationScheme)]
public class Mixed2Controller : Controller
{
    public ContentResult Index() => Content(MyWidgets.GetMyContent());
}

在上面的代码中,仅使用“持有者”方案的处理程序将运行。 将忽略任何基于 cookie 的标识。

选择使用策略的方案

如果首选在策略中指定所需的方案,则可以在添加策略时设置 AuthenticationSchemes 集合:

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("Over18", policy =>
    {
        policy.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
        policy.RequireAuthenticatedUser();
        policy.Requirements.Add(new MinimumAgeRequirement(18));
    });
});

builder.Services.AddAuthentication()
                .AddIdentityServerJwt();

builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseAuthentication();
app.UseIdentityServer();
app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.MapFallbackToFile("index.html");

app.Run();

在上面的示例中,“Over18”策略仅针对由“持有者”处理程序创建的标识运行。 通过设置 [Authorize] 特性的 Policy 属性来使用策略:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace AuthScheme.Controllers;
[Authorize(Policy = "Over18")]
public class RegistrationController : Controller
{
    // Do Registration

使用多种身份验证方案

某些应用可能需要支持多种身份验证类型。 例如,应用可能会从 Azure Active Directory 以及从用户数据库对用户进行身份验证。 再比如,一个应用同时从 Active Directory 联合身份验证服务和 Active Directory B2C 对用户进行身份验证。 在这种情况下,应用应接受来自多个颁发者的 JWT 持有者令牌。

添加所有想接受的身份验证方案。 例如,以下代码可添加颁发者不同的两个 JWT 持有者身份验证方案:

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;

var builder = WebApplication.CreateBuilder(args);

// Authentication
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options =>
        {
            options.Audience = "https://localhost:5000/";
            options.Authority = "https://localhost:5000/identity/";
        })
        .AddJwtBearer("AzureAD", options =>
        {
            options.Audience = "https://localhost:5000/";
            options.Authority = "https://login.microsoftonline.com/eb971100-7f436/";
        });

// Authorization
builder.Services.AddAuthorization(options =>
{
    var defaultAuthorizationPolicyBuilder = new AuthorizationPolicyBuilder(
        JwtBearerDefaults.AuthenticationScheme,
        "AzureAD");
    defaultAuthorizationPolicyBuilder =
        defaultAuthorizationPolicyBuilder.RequireAuthenticatedUser();
    options.DefaultPolicy = defaultAuthorizationPolicyBuilder.Build();
});

builder.Services.AddAuthentication()
        .AddIdentityServerJwt();

builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseAuthentication();
app.UseIdentityServer();
app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.MapFallbackToFile("index.html");

app.Run();

注意

默认身份验证方案 JwtBearerDefaults.AuthenticationScheme 中只注册了一种 JWT 持有者身份验证。 必须在唯一的身份验证方案中注册额外的身份验证。

更新默认授权策略以接受这两种身份验证方案。 例如:

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;

var builder = WebApplication.CreateBuilder(args);

// Authentication
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options =>
        {
            options.Audience = "https://localhost:5000/";
            options.Authority = "https://localhost:5000/identity/";
        })
        .AddJwtBearer("AzureAD", options =>
        {
            options.Audience = "https://localhost:5000/";
            options.Authority = "https://login.microsoftonline.com/eb971100-7f436/";
        });

// Authorization
builder.Services.AddAuthorization(options =>
{
    var defaultAuthorizationPolicyBuilder = new AuthorizationPolicyBuilder(
        JwtBearerDefaults.AuthenticationScheme,
        "AzureAD");
    defaultAuthorizationPolicyBuilder =
        defaultAuthorizationPolicyBuilder.RequireAuthenticatedUser();
    options.DefaultPolicy = defaultAuthorizationPolicyBuilder.Build();
});

builder.Services.AddAuthentication()
        .AddIdentityServerJwt();

builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseAuthentication();
app.UseIdentityServer();
app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.MapFallbackToFile("index.html");

app.Run();

当重写默认身份验证策略时,可以在控制器中使用 [Authorize] 特性。 随后,控制器使用由第一个或第二个颁发者颁发的 JWT 来接受请求。

请参阅使用多个身份验证方案的此 GitHub 问题

以下示例使用 Azure Active Directory B2C 和另一个 Azure Active Directory 租户:

using Microsoft.AspNetCore.Authentication;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Net.Http.Headers;
using System.IdentityModel.Tokens.Jwt;

var builder = WebApplication.CreateBuilder(args);

// Authentication
builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme = "B2C_OR_AAD";
    options.DefaultChallengeScheme = "B2C_OR_AAD";
})
.AddJwtBearer("B2C", jwtOptions =>
{
    jwtOptions.MetadataAddress = "B2C-MetadataAddress";
    jwtOptions.Authority = "B2C-Authority";
    jwtOptions.Audience = "B2C-Audience";
})
.AddJwtBearer("AAD", jwtOptions =>
{
    jwtOptions.MetadataAddress = "AAD-MetadataAddress";
    jwtOptions.Authority = "AAD-Authority";
    jwtOptions.Audience = "AAD-Audience";
    jwtOptions.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateIssuerSigningKey = true,
        ValidAudiences = builder.Configuration.GetSection("ValidAudiences").Get<string[]>(),
        ValidIssuers = builder.Configuration.GetSection("ValidIssuers").Get<string[]>()
    };
})
.AddPolicyScheme("B2C_OR_AAD", "B2C_OR_AAD", options =>
{
    options.ForwardDefaultSelector = context =>
    {
        string authorization = context.Request.Headers[HeaderNames.Authorization];
        if (!string.IsNullOrEmpty(authorization) && authorization.StartsWith("Bearer "))
        {
            var token = authorization.Substring("Bearer ".Length).Trim();
            var jwtHandler = new JwtSecurityTokenHandler();

            return (jwtHandler.CanReadToken(token) && jwtHandler.ReadJwtToken(token).Issuer.Equals("B2C-Authority"))
                ? "B2C" : "AAD";
        }
        return "AAD";
    };
});

builder.Services.AddAuthentication()
        .AddIdentityServerJwt();

builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseAuthentication();
app.UseIdentityServer();
app.UseAuthorization();

app.MapDefaultControllerRoute().RequireAuthorization();
app.MapRazorPages().RequireAuthorization();

app.MapFallbackToFile("index.html");

app.Run();

在前面的代码中,ForwardDefaultSelector 用于为当前请求选择默认方案,该方案是身份验证处理程序默认应将所有身份验证操作转发到的目标。 默认转发逻辑首先检查最具体的 ForwardAuthenticateForwardChallengeForwardForbidForwardSignInForwardSignOut 设置,然后依次检查 ForwardDefaultSelectorForwardDefault。 第一个非 null 结果将会用作要转发到的目标方案。 有关详细信息,请参阅ASP.NET Core 中的策略方案

有关 ASP.NET Core 中的身份验证方案的简介,请参阅身份验证方案

在某些情况下,例如单页应用程序 (SPA),通常会使用多种身份验证方法。 例如,应用可能会针对登录使用基于 cookie 的身份验证,并使用 JWT 持有者身份验证来处理 JavaScript 请求。 在某些情况下,应用可能会有一个身份验证处理程序的多个实例。 例如,有两个 cookie 处理程序,一个包含基本标识,另一个是在多重身份验证 (MFA) 触发后创建的。 可能会触发 MFA,因为用户请求了需要额外安全性的操作。 有关当用户请求需要 MFA 的资源时强制执行 MFA 的详细信息,请参阅使用 MFA 保护部分这一 GitHub 问题。

身份验证方案是在身份验证期间配置身份验证服务时命名的。 例如:

public void ConfigureServices(IServiceCollection services)
{
    // Code omitted for brevity

    services.AddAuthentication()
        .AddCookie(options => {
            options.LoginPath = "/Account/Unauthorized/";
            options.AccessDeniedPath = "/Account/Forbidden/";
        })
        .AddJwtBearer(options => {
            options.Audience = "http://localhost:5001/";
            options.Authority = "http://localhost:5000/";
        });

在上面的代码中,添加了两个身份验证处理程序:一个用于 cookie,一个用于持有者。

注意

指定默认方案会导致 HttpContext.User 属性设置为该标识。 如果不需要该行为,请通过调用 AddAuthentication 的无参数形式来禁用它。

使用 Authorize 特性选择方案

在授权时,应用指示要使用的处理程序。 选择应用要通过将以逗号分隔的身份验证方案列表传递给 [Authorize] 来进行授权的处理程序。 [Authorize] 特性指定要使用的身份验证方案,不管是否存在默认设置。 例如:

[Authorize(AuthenticationSchemes = AuthSchemes)]
public class MixedController : Controller
    // Requires the following imports:
    // using Microsoft.AspNetCore.Authentication.Cookies;
    // using Microsoft.AspNetCore.Authentication.JwtBearer;
    private const string AuthSchemes =
        CookieAuthenticationDefaults.AuthenticationScheme + "," +
        JwtBearerDefaults.AuthenticationScheme;

在上面的示例中,cookie 和持有者处理程序都将运行,并且可能会为当前的用户创建并追加标识。 通过仅指定单个方案,相应的处理程序将运行。

[Authorize(AuthenticationSchemes = 
    JwtBearerDefaults.AuthenticationScheme)]
public class MixedController : Controller

在上面的代码中,仅使用“持有者”方案的处理程序将运行。 将忽略任何基于 cookie 的标识。

选择使用策略的方案

如果首选在策略中指定所需的方案,可以在添加策略时设置 AuthenticationSchemes 集合:

services.AddAuthorization(options =>
{
    options.AddPolicy("Over18", policy =>
    {
        policy.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
        policy.RequireAuthenticatedUser();
        policy.Requirements.Add(new MinimumAgeRequirement());
    });
});

在上面的示例中,“Over18”策略仅针对由“持有者”处理程序创建的标识运行。 通过设置 [Authorize] 特性的 Policy 属性来使用策略:

[Authorize(Policy = "Over18")]
public class RegistrationController : Controller

使用多种身份验证方案

某些应用可能需要支持多种身份验证类型。 例如,应用可能会从 Azure Active Directory 以及从用户数据库对用户进行身份验证。 再比如,一个应用同时从 Active Directory 联合身份验证服务和 Active Directory B2C 对用户进行身份验证。 在这种情况下,应用应接受来自多个颁发者的 JWT 持有者令牌。

添加所有想接受的身份验证方案。 例如,Startup.ConfigureServices 中的以下代码添加具有不同颁发者的两个 JWT 持有者身份验证方案:

public void ConfigureServices(IServiceCollection services)
{
    // Code omitted for brevity

    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options =>
        {
            options.Audience = "https://localhost:5000/";
            options.Authority = "https://localhost:5000/identity/";
        })
        .AddJwtBearer("AzureAD", options =>
        {
            options.Audience = "https://localhost:5000/";
            options.Authority = "https://login.microsoftonline.com/eb971100-6f99-4bdc-8611-1bc8edd7f436/";
        });
}

注意

默认身份验证方案 JwtBearerDefaults.AuthenticationScheme 中只注册了一种 JWT 持有者身份验证。 必须在唯一的身份验证方案中注册额外的身份验证。

下一步是更新默认身份验证策略来接受这两种身份验证方案。 例如:

public void ConfigureServices(IServiceCollection services)
{
    // Code omitted for brevity

    services.AddAuthorization(options =>
    {
        var defaultAuthorizationPolicyBuilder = new AuthorizationPolicyBuilder(
            JwtBearerDefaults.AuthenticationScheme,
            "AzureAD");
        defaultAuthorizationPolicyBuilder = 
            defaultAuthorizationPolicyBuilder.RequireAuthenticatedUser();
        options.DefaultPolicy = defaultAuthorizationPolicyBuilder.Build();
    });
}

当重写默认身份验证策略时,可以在控制器中使用 [Authorize] 特性。 随后,控制器使用由第一个或第二个颁发者颁发的 JWT 来接受请求。

请参阅使用多个身份验证方案的此 GitHub 问题