Microsoft 사용하여 웹 API에서 권한 부여를 구현합니다. Identity.Web

이 기사에서는 Microsoft.Identity.Web을 사용하여 ASP.NET Core 웹 API에서 권한 부여를 구현합니다. 범위(위임된 권한) 및 앱 권한(애플리케이션 권한)의 유효성을 검사하여 보호된 리소스에 대한 액세스를 제어합니다. 이 예제에서는 id 공급자로 Microsoft Entra ID 사용합니다.

권한 부여 개념 이해

이 섹션에서는 인증과 권한 부여의 주요 차이점을 설명하고, Microsoft.Identity.Web이 액세스 토큰의 유효성을 어떻게 검사하는지 설명합니다.

인증 및 권한 부여

개념 Purpose 결과
인증 ID 확인 401 실패할 경우 권한 없음
승인 권한 확인 403 불충분할 경우 금지됨

유효성이 검사되는 항목

웹 API가 액세스 토큰을 받으면 Microsoft. Identity.Web은 다음의 유효성을 검사합니다.

  1. 토큰 서명 - 신뢰할 수 있는 기관에서 온 것입니까?
  2. 토큰 대상 그룹 - 이 API용인가요?
  3. 토큰 만료 - 여전히 유효한가요?
  4. 범위/역할 - 클라이언트 앱과 주체(사용자)에게 올바른 권한이 있나요?

이 가이드에서는 #4 - 범위 및 앱 권한의 유효성을 검사하는 데 중점을 둡니다.

범위(위임된 권한)

범위는 사용자가 사용자를 대신하여 작업할 수 있는 권한을 앱에 위임할 때 적용됩니다(예: 로그인한 사용자를 대신하여 호출된 웹 API).

세부 정보
토큰 클레임 scp 또는 scope (클라이언트 앱); roles (사용자)
예제 값 "access_as_user", "User.Read", "Files.ReadWrite"

앱 사용 권한(애플리케이션 권한)

앱 권한은 앱이 클라이언트 자격 증명을 사용하는 디먼 또는 백그라운드 서비스와 같은 사용자 컨텍스트 없이 자체 웹 API를 호출할 때 적용됩니다.

세부 정보
토큰 클레임 roles
예제 값 "Mail.Read.All", "User.Read.All"

RequiredScope를 사용하여 범위 유효성 검사

이 특성은 RequiredScope 액세스 토큰에 지정된 범위 중 하나 이상이 포함되어 있는지 확인합니다. API가 사용자 위임 요청만 제공하는 경우 이 특성을 사용합니다.

범위 유효성 검사 설정

API에서 범위 유효성 검사를 사용하도록 설정하려면 다음 단계를 수행합니다.

1. API에서 권한 부여를 사용하도록 설정합니다.

애플리케이션 파이프라인에 인증 및 권한 부여 서비스를 추가합니다.

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Web;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));

builder.Services.AddAuthorization(); // Required for authorization

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization(); // Must be after UseAuthentication
app.MapControllers();

app.Run();

2. 컨트롤러 또는 작업 보호:

[Authorize] 특성 및 [RequiredScope] 특성을 컨트롤러 또는 개별 작업에 적용합니다.

using Microsoft.AspNetCore.Authorization;
using Microsoft.Identity.Web.Resource;

[Authorize]
[RequiredScope("access_as_user")]
public class TodoListController : ControllerBase
{
    [HttpGet]
    public IActionResult GetTodos()
    {
        // Only accessible if token has "access_as_user" scope
        return Ok(new[] { "Todo 1", "Todo 2" });
    }
}

범위 패턴 적용

애플리케이션에서 범위를 관리하는 방법에 가장 적합한 패턴을 선택합니다.

패턴 1: 하드 코딩된 범위

개발 시 범위가 고정되고 알려진 경우 이 패턴을 사용합니다.

[Authorize]
[RequiredScope("access_as_user")]
public class TodoListController : ControllerBase
{
    // All actions require "access_as_user" scope
}

여러 범위 중 하나를 허용하려면 매개 변수로 나열합니다.

[Authorize]
[RequiredScope("read", "write", "admin")]
public class TodoListController : ControllerBase
{
    // Token must have "read" OR "write" OR "admin"
}

패턴 2: 환경 설정의 범위

환경별로 범위를 구성할 수 있어야 하는 경우 이 패턴을 사용합니다. 구성 파일에서 범위를 정의합니다.

appsettings.json:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "your-tenant-id",
    "ClientId": "your-api-client-id",
    "Scopes": "access_as_user read write"
  }
}

컨트롤러에서 구성 키를 참조합니다.

[Authorize]
[RequiredScope(RequiredScopesConfigurationKey = "AzureAd:Scopes")]
public class TodoListController : ControllerBase
{
    // Scopes read from configuration
}

이 방법을 사용하면 다시 컴파일하지 않고 범위를 변경할 수 있습니다.

패턴 3: 작업 수준 범위

다른 작업에 다른 권한이 필요한 경우 이 패턴을 사용합니다. 개별 작업 메서드에 적용 [RequiredScope] :

[Authorize]
public class TodoListController : ControllerBase
{
    [HttpGet]
    [RequiredScope("read")]
    public IActionResult GetTodos()
    {
        return Ok(todos);
    }

    [HttpPost]
    [RequiredScope("write")]
    public IActionResult CreateTodo([FromBody] Todo todo)
    {
        // Only tokens with "write" scope can create
        return CreatedAtAction(nameof(GetTodos), todo);
    }

    [HttpDelete("{id}")]
    [RequiredScope("admin")]
    public IActionResult DeleteTodo(int id)
    {
        // Only tokens with "admin" scope can delete
        return NoContent();
    }
}

유효성 검사 흐름 이해

요청이 도착하면 미들웨어는 다음 순서로 처리합니다.

  1. ASP.NET Core 인증 미들웨어가 토큰의 유효성을 검사합니다.
  2. RequiredScope 속성이 scp 클레임이나 scope 클레임을 확인합니다.
  3. 토큰에 하나 이상의 일치하는 범위가 포함된 경우 요청이 진행됩니다.
  4. 일치하는 범위를 찾을 수 없으면 API는 403 사용할 수 없음 응답을 반환합니다.

다음 예제에서는 일반적인 오류 응답을 보여줍니다.

{
  "error": "insufficient_scope",
  "error_description": "The token does not have the required scope 'access_as_user'."
}

RequiredScopeOrAppPermission을 사용하여 앱 권한 유효성 검사

이 특성은 RequiredScopeOrAppPermission범위 (위임됨) 또는 앱 권한 (애플리케이션)의 유효성을 검사합니다. API가 동일한 엔드포인트에서 사용자 위임 앱과 디먼/서비스 앱을 모두 제공하는 경우 이 특성을 사용합니다.

API가 사용자 위임 요청만 제공하는 경우 대신 사용합니다 RequiredScope .

범위 또는 앱 권한 유효성 검사 설정

토큰 형식 중 하나를 허용하도록 특성을 적용합니다.

using Microsoft.Identity.Web.Resource;

[Authorize]
[RequiredScopeOrAppPermission(
    AcceptedScope = new[] { "access_as_user" },
    AcceptedAppPermission = new[] { "TodoList.ReadWrite.All" }
)]
public class TodoListController : ControllerBase
{
    [HttpGet]
    public IActionResult GetTodos()
    {
        // Accessible with EITHER:
        // - User-delegated token with "access_as_user" scope, OR
        // - App-only token with "TodoList.ReadWrite.All" app permission
        return Ok(todos);
    }
}

설정에서 앱 권한 구성

구성에서 범위 및 앱 권한을 저장하여 다시 컴파일하지 않고 변경합니다.

appsettings.json:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "your-tenant-id",
    "ClientId": "your-api-client-id",
    "Scopes": "access_as_user",
    "AppPermissions": "TodoList.ReadWrite.All TodoList.Admin"
  }
}

컨트롤러의 구성 키를 참조합니다.

[Authorize]
[RequiredScopeOrAppPermission(
    RequiredScopesConfigurationKey = "AzureAd:Scopes",
    RequiredAppPermissionsConfigurationKey = "AzureAd:AppPermissions"
)]
public class TodoListController : ControllerBase
{
    // Scopes and app permissions from configuration
}

토큰 클레임 차이점 비교

다음 표에서는 사용자 위임 토큰과 앱 전용 토큰 간에 클레임이 어떻게 다른지 보여 줍니다.

토큰 형식 클레임 예제 값
위임된 사용자 scp 또는 scope "access_as_user User.Read"
앱 전용 roles ["TodoList.ReadWrite.All"]

다음 예제에서는 사용자가 위임한 토큰을 보여줍니다.

{
  "aud": "api://your-api-client-id",
  "iss": "https://login.microsoftonline.com/.../v2.0",
  "scp": "access_as_user",
  "sub": "user-object-id",
  ...
}

다음 예제에서는 앱 전용 토큰을 보여줍니다.

{
  "aud": "api://your-api-client-id",
  "iss": "https://login.microsoftonline.com/.../v2.0",
  "roles": ["TodoList.ReadWrite.All"],
  "sub": "app-object-id",
  ...
}

권한 부여 정책 만들기

복잡한 권한 부여 시나리오의 경우 ASP.NET Core 권한 부여 정책을 사용합니다. 정책을 사용하면 규칙을 중앙 집중화하고, 여러 요구 사항을 결합하고, 테스트 가능한 권한 부여 논리를 작성할 수 있습니다.

Benefit Description
중앙 집중식 논리 권한 부여 규칙을 한 번 정의하고 모든 곳에서 다시 사용
구성 가능 여러 요구 사항 결합(범위 + 클레임 + 사용자 지정 논리)
테스트 가능 권한 부여 논리의 단위 테스트를 더 쉽게 수행하기
Flexible 범위 유효성 검사 이외의 사용자 지정 요구 사항

패턴 1: RequireScope를 사용하여 정책 정의

특정 범위가 필요한 명명된 정책을 정의한 다음 컨트롤러에서 참조합니다.

using Microsoft.Identity.Web;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("TodoReadPolicy", policyBuilder =>
    {
        policyBuilder.RequireScope("read", "access_as_user");
    });

    options.AddPolicy("TodoWritePolicy", policyBuilder =>
    {
        policyBuilder.RequireScope("write", "admin");
    });
});

var app = builder.Build();

컨트롤러 작업에 정책을 적용합니다.

[Authorize]
public class TodoListController : ControllerBase
{
    [HttpGet]
    [Authorize(Policy = "TodoReadPolicy")]
    public IActionResult GetTodos()
    {
        return Ok(todos);
    }

    [HttpPost]
    [Authorize(Policy = "TodoWritePolicy")]
    public IActionResult CreateTodo([FromBody] Todo todo)
    {
        return CreatedAtAction(nameof(GetTodos), todo);
    }
}

패턴 2: ScopeAuthorizationRequirement를 사용하여 정책 정의

보다 명시적인 범위 요구 사항에 사용합니다 ScopeAuthorizationRequirement .

using Microsoft.Identity.Web;
using Microsoft.Identity.Web.Resource;

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("CustomPolicy", policyBuilder =>
    {
        policyBuilder.AddRequirements(
            new ScopeAuthorizationRequirement(new[] { "access_as_user" })
        );
    });
});

패턴 3: 기본 정책 설정

모든 [Authorize] 특성에 자동으로 적용되는 기본 정책을 설정합니다.

builder.Services.AddAuthorization(options =>
{
    var defaultPolicy = new AuthorizationPolicyBuilder()
        .RequireScope("access_as_user")
        .Build();

    options.DefaultPolicy = defaultPolicy;
});

모든 [Authorize] 속성에 이제 access_as_user 범위가 필요합니다.

[Authorize] // Automatically requires "access_as_user" scope
public class TodoListController : ControllerBase
{
    // All actions protected by default policy
}

패턴 4: 여러 요구 사항 결합

단일 정책에서 범위, 역할 및 인증 요구 사항을 결합합니다.

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AdminPolicy", policyBuilder =>
    {
        policyBuilder.RequireScope("admin");
        policyBuilder.RequireRole("Admin"); // Also check role claim
        policyBuilder.RequireAuthenticatedUser();
    });
});

패턴 5: 구성에서 정책 빌드

정책을 환경별로 유지하기 위해 구성에서 범위를 로드합니다.

var requiredScopes = builder.Configuration["AzureAd:Scopes"]?.Split(' ');

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("ApiAccessPolicy", policyBuilder =>
    {
        if (requiredScopes != null)
        {
            policyBuilder.RequireScope(requiredScopes);
        }
    });
});

테넌트별 요청 필터링

특정 Microsoft Entra 테넌트에서 토큰에 대한 API 액세스를 제한합니다. 이는 다중 테넌트 API가 승인된 고객 테넌트에서만 요청을 수락해야 하는 경우에 유용합니다.

허용된 테넌트에 대한 액세스 제한

허용 목록에 대해 테넌트 ID 클레임을 확인하는 정책을 정의합니다.

builder.Services.AddAuthorization(options =>
{
    string[] allowedTenants =
    {
        "14c2f153-90a7-4689-9db7-9543bf084dad", // Contoso tenant
        "af8cc1a0-d2aa-4ca7-b829-00d361edb652", // Fabrikam tenant
        "979f4440-75dc-4664-b2e1-2cafa0ac67d1"  // Northwind tenant
    };

    options.AddPolicy("AllowedTenantsOnly", policyBuilder =>
    {
        policyBuilder.RequireClaim(
            "http://schemas.microsoft.com/identity/claims/tenantid",
            allowedTenants
        );
    });

    // Apply to all endpoints by default
    options.DefaultPolicy = options.GetPolicy("AllowedTenantsOnly");
});

설정에서 테넌트 필터링 구성

코드 변경 없이 관리하도록 허용된 테넌트 ID를 구성에 저장합니다.

appsettings.json:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "ClientId": "your-api-client-id",
    "AllowedTenants": [
      "14c2f153-90a7-4689-9db7-9543bf084dad",
      "af8cc1a0-d2aa-4ca7-b829-00d361edb652"
    ]
  }
}

테넌트 목록을 읽고 시작할 때 정책을 만듭니다.

var allowedTenants = builder.Configuration.GetSection("AzureAd:AllowedTenants")
    .Get<string[]>();

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AllowedTenantsOnly", policyBuilder =>
    {
        policyBuilder.RequireClaim(
            "http://schemas.microsoft.com/identity/claims/tenantid",
            allowedTenants ?? Array.Empty<string>()
        );
    });
});

범위를 테넌트 필터링과 결합

유효한 범위와 승인된 테넌트가 모두 필요한 정책을 만듭니다.

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("SecureApiAccess", policyBuilder =>
    {
        // Require specific scope
        policyBuilder.RequireScope("access_as_user");

        // AND require specific tenant
        policyBuilder.RequireClaim(
            "http://schemas.microsoft.com/identity/claims/tenantid",
            allowedTenants
        );
    });
});

모범 사례 준수

이러한 권장 사항을 적용하여 안전하고 유지 관리 가능한 권한 부여 논리를 빌드합니다.

해야 할 일

1. 항상 범위 유효성 검사와 페어링하십시오 [Authorize] :

[Authorize] // Authentication
[RequiredScope("access_as_user")] // Authorization
public class MyController : ControllerBase { }

2. 환경별 범위에 대한 구성 사용:

[RequiredScope(RequiredScopesConfigurationKey = "AzureAd:Scopes")]

3. 최소 권한 적용:

[HttpGet]
[RequiredScope("read")] // Only read permission needed

[HttpPost]
[RequiredScope("write")] // Write permission for modifications

4. 복잡한 권한 부여를 위한 정책 사용:

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AdminOnly", policy =>
    {
        policy.RequireScope("admin");
        policy.RequireClaim("department", "IT");
    });
});

5. 개발에서 자세한 오류 응답을 사용하도록 설정합니다.

if (builder.Environment.IsDevelopment())
{
    Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true;
}

하지 말아야 할 것들

1. [Authorize]를 사용할 때 RequiredScope를 건너뛰지 마세요.

//  Wrong - RequiredScope won't work without [Authorize]
[RequiredScope("access_as_user")]
public class MyController : ControllerBase { }

//  Correct
[Authorize]
[RequiredScope("access_as_user")]
public class MyController : ControllerBase { }

2. 프로덕션 환경에서 테넌트 ID를 하드 코딩하지 마세요.

//  Wrong
policyBuilder.RequireClaim("tid", "14c2f153-90a7-4689-9db7-9543bf084dad");

//  Better - use configuration
var tenants = Configuration.GetSection("AllowedTenants").Get<string[]>();
policyBuilder.RequireClaim("tid", tenants);

3. 역할과 범위를 혼동하지 마세요.

//  Wrong - This checks roles claim, not scopes
[RequiredScope("Admin")] // "Admin" is typically a role, not a scope

//  Correct
[RequiredScope("access_as_user")] // Scope
[Authorize(Roles = "Admin")] // Role

4. 프로덕션 오류 메시지에 중요한 범위 정보를 노출하지 마세요.

프로덕션 환경에 대한 적절한 로깅 수준 및 오류 처리를 구성합니다.


권한 부여 문제 해결

다음 지침을 사용하여 일반적인 권한 부여 문제를 진단합니다.

403 금지됨 - 권한 범위 누락

오류: API는 유효한 토큰이 있는 경우에도 403을 반환합니다.

진단:

  1. jwt.ms 토큰을 디코딩합니다.
  2. scp 또는 scope 클레임을 확인합니다.
  3. RequiredScope 속성과 값이 일치하는지 확인합니다.

Solution:

  • 토큰을 획득할 때 클라이언트 앱이 올바른 범위를 요청하는지 확인합니다.
  • 범위가 Microsoft Entra API 앱 등록에 노출되는지 확인합니다.
  • 필요한 경우 관리자 동의를 부여합니다.

RequiredScope가 작동하지 않음

증상: 이 특성은 무시된 것으로 보입니다.

확인:

  1. 특성을 추가 [Authorize] 했나요?
  2. app.UseAuthorization()app.UseAuthentication() 후에 호출됩니까?
  3. 등록되어 있나요 services.AddAuthorization() ?

구성 키를 찾을 수 없음

오류: 범위 유효성 검사가 알림 없이 실패합니다.

확인:

{
  "AzureAd": {
    "Scopes": "access_as_user" // Matches RequiredScopesConfigurationKey
  }
}

구성 경로가 정확히 일치하는지 확인합니다.