ASP.NET Core에서 특정 체계를 통해 권한 부여

ASP.NET Core에서 인증 체계에 대한 소개는 인증 체계를 참조하세요.

SPA(단일 페이지 애플리케이션)와 같은 일부 시나리오에서는 여러 인증 방법을 사용하는 것이 일반적입니다. 예를 들어 앱은 cookie 기반 인증을 사용하여 로그인하고 JavaScript 요청에 대한 JWT 전달자 인증을 사용할 수 있습니다. 경우에 따라 앱에 인증 처리기의 여러 인스턴스가 있을 수 있습니다. 예를 들어 한 처리기에는 기본 ID가 포함되고 다른 하나는 MFA(다단계 인증)가 트리거될 때 만들어지는 두 개의 cookie 처리기가 있습니다. 사용자가 추가 보안이 필요한 작업을 요청했기 때문에 MFA가 트리거될 수 있습니다. 사용자가 MFA가 필요한 리소스를 요청할 때 MFA를 적용하는 데 대한 자세한 내용은 GitHub 이슈 MFA를 사용하여 보호 섹션을 참조하세요.

인증 체계는 인증 중에 인증 서비스가 구성되면 이름이 지정됩니다. 예시:

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 속성이 해당 ID로 설정됩니다. 해당 동작을 원하지 않는 경우 AddAuthentication의 매개 변수가 없는 형식을 호출하여 비활성화합니다.

권한 부여 특성을 갖는 체계 선택

권한 부여 시점에 앱은 사용할 처리기를 나타냅니다. 쉼표로 구분된 인증 체계 목록을 [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 및 전달자 처리기가 모두 실행되고 현재 사용자에 대한 ID를 만들고 추가합니다. 단일 체계를 지정하는 것으로서 해당 처리기가 실행됩니다.

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

위의 코드에서는 "Bearer" 체계가 있는 처리기만 실행됩니다. 모든 cookie 기반 ID가 무시됩니다.

정책이 있는 체계 선택

정책에서 원하는 체계를 지정하고자 하는 경우, 정책을 추가할 때 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" 정책은 "Bearer" 처리기에서 만든 ID에 대해서만 실행됩니다. [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 Federation Services 및 Azure 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를 인증 처리기가 기본적으로 모든 인증 작업을 전달해야 하는 현재 요청에 대한 기본 체계를 선택하는 데 사용됩니다. 기본 전달 논리에서는 가장 구체적인 ForwardAuthenticate, ForwardChallenge, ForwardForbid, ForwardSignIn, ForwardSignOut 설정을 우선 확인 다음, ForwardDefaultSelector 설정을 확인한 후 ForwardDefault 설정을 확인하게 됩니다. null이 아닌 첫 번째 결과가 전달할 대상 체계로 사용됩니다. 자세한 내용은 ASP.NET Core의 정책 체계를 참조하세요.

ASP.NET Core에서 인증 체계에 대한 소개는 인증 체계를 참조하세요.

SPA(단일 페이지 애플리케이션)와 같은 일부 시나리오에서는 여러 인증 방법을 사용하는 것이 일반적입니다. 예를 들어 앱은 cookie 기반 인증을 사용하여 로그인하고 JavaScript 요청에 대한 JWT 전달자 인증을 사용할 수 있습니다. 경우에 따라 앱에 인증 처리기의 여러 인스턴스가 있을 수 있습니다. 예를 들어 한 처리기에는 기본 ID가 포함되고 다른 하나는 MFA(다단계 인증)가 트리거될 때 만들어지는 두 개의 cookie 처리기가 있습니다. 사용자가 추가 보안이 필요한 작업을 요청했기 때문에 MFA가 트리거될 수 있습니다. 사용자가 MFA가 필요한 리소스를 요청할 때 MFA를 적용하는 데 대한 자세한 내용은 GitHub 이슈 MFA를 사용하여 보호 섹션을 참조하세요.

인증 체계는 인증 중에 인증 서비스가 구성되면 이름이 지정됩니다. 예시:

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 속성이 해당 ID로 설정됩니다. 해당 동작을 원하지 않는 경우 AddAuthentication의 매개 변수가 없는 형식을 호출하여 비활성화합니다.

권한 부여 특성을 갖는 체계 선택

권한 부여 시점에 앱은 사용할 처리기를 나타냅니다. 쉼표로 구분된 인증 체계 목록을 [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 및 전달자 처리기가 모두 실행되고 현재 사용자에 대한 ID를 만들고 추가합니다. 단일 스키마만 지정하면 해당 처리기가 실행됩니다.

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

위의 코드에서는 "Bearer" 체계가 있는 처리기만 실행됩니다. 모든 cookie 기반 ID가 무시됩니다.

정책이 있는 체계 선택

정책에서 원하는 체계를 지정하려는 경우 정책을 추가할 때 AuthenticationSchemes 컬렉션을 설정할 수 있습니다.

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

앞의 예제에서 "Over18" 정책은 "Bearer" 처리기에서 만든 ID에 대해서만 실행됩니다. [Authorize] 특성의 Policy 속성을 설정하여 정책을 사용합니다.

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

여러 인증 체계 사용

일부 앱은 여러 유형의 인증을 지원해야 할 수 있습니다. 예를 들어 앱은 Azure Active Directory 및 사용자 데이터베이스에서 사용자를 인증할 수 있습니다. 또 다른 예제는 Active Directory Federation Services 및 Azure 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 이슈를 참조하세요.