다음을 통해 공유


자습서: Microsoft ID 플랫폼을 사용하여 ASP.NET Core Web API 빌드 및 보호

적용 대상: 워크포스 테넌트 흰색 확인 표시가 있는 녹색 원 외부 테넌트(Green circle with a white check mark symbol.자세히 알아보기)

이 자습서 시리즈에서는 Microsoft ID 플랫폼으로 ASP.NET Core Web API를 보호하여 권한이 부여된 사용자 및 클라이언트 앱에만 액세스할 수 있도록 제한하는 방법을 보여 줍니다. 빌드하는 웹 API는 위임된 권한(범위) 및 애플리케이션 권한(앱 역할)을 모두 사용합니다.

이 자습서에서는 다음을 수행합니다.

  • ASP.NET Core 웹 API 빌드
  • Microsoft Entra 앱 등록 세부 정보를 사용하도록 웹 API 구성
  • 웹 API 엔드포인트 보호
  • 웹 API를 실행하여 HTTP 요청을 수신 대기하는지 확인합니다.

필수 구성 요소

새 ASP.NET Core Web API 프로젝트 만들기

최소 ASP.NET Core Web API 프로젝트를 만들려면 다음 단계를 수행합니다.

  1. Visual Studio Code 또는 다른 코드 편집기에서 터미널을 열고 프로젝트를 만들 디렉터리로 이동합니다.

  2. .NET CLI 또는 다른 명령줄 도구에서 다음 명령을 실행합니다.

    dotnet new web -o TodoListApi
    cd TodoListApi
    
  3. 를 선택합니다. 대화 상자가 작성자를 신뢰할지 묻는다면 그렇다고 대답하십시오.

  4. 프로젝트에 필요한 자산을 추가할지 묻는 대화 상자가 표시되면 선택합니다.

필수 패키지 설치

ASP.NET Core Web API를 빌드, 보호 및 테스트하려면 다음 패키지를 설치해야 합니다.

  • Microsoft.EntityFrameworkCore.InMemory- 메모리 내 데이터베이스와 함께 Entity Framework Core를 사용할 수 있는 패키지입니다. 테스트 용도로 유용하지만 프로덕션용으로 설계되지 않았습니다.
  • Microsoft.Identity.Web - Microsoft ID 플랫폼과 통합되는 웹앱 및 웹 API에 인증 및 권한 부여 지원을 추가하는 작업을 간소화하는 ASP.NET Core 라이브러리 집합입니다.

패키지를 설치하려면 다음을 사용합니다.

dotnet add package Microsoft.EntityFrameworkCore.InMemory
dotnet add package Microsoft.Identity.Web

앱 등록 세부 정보 구성

앱 폴더에서 appsettings.json 파일을 열고 웹 API를 등록한 후 기록한 앱 등록 세부 정보를 추가합니다.

{
    "AzureAd": {
        "Instance": "Enter_the_Authority_URL_Here",
        "TenantId": "Enter_the_Tenant_Id_Here",
        "ClientId": "Enter_the_Application_Id_Here"
    },
    "Logging": {...},
  "AllowedHosts": "*"
}

다음의 자리 표시자를 다음과 같이 바꿉니다.

  • Enter_the_Application_Id_Here 애플리케이션(클라이언트) ID로 대체합니다.
  • Enter_the_Tenant_Id_Here 디렉터리(테넌트) ID로 대체합니다.
  • 다음 섹션에서 설명한 대로 Enter_the_Authority_URL_Here 기관 URL로 바꿉습니다.

앱의 권한 URL

기관 URL은 MSAL(Microsoft 인증 라이브러리)에서 토큰을 요청할 수 있는 디렉터리를 지정합니다. 다음과 같이 인력과 외부 테넌트 모두에서 다르게 빌드합니다.

//Instance for workforce tenant
Instance: "https://login.microsoftonline.com/"

사용자 지정 URL 도메인 사용(선택 사항)

사용자 지정 URL 도메인은 조직 테넌트에서 지원되지 않습니다.

권한 추가

모든 API는 클라이언트 앱이 사용자의 액세스 토큰을 성공적으로 가져오려면 위임된 권한이라고도 하는 최소 하나의 범위를 게시해야 합니다. API는 또한 클라이언트 앱이 사용자 로그인을 하지 않을 때도 자체 액세스 토큰을 얻을 수 있도록, 애플리케이션 권한이라고도 하는 최소 하나의 앱 역할을 게시해야 합니다.

appsettings.json 파일에 이러한 권한을 지정합니다. 이 자습서에서는 다음과 같은 위임된 및 애플리케이션 권한을 등록했습니다.

  • 위임된 권한:ToDoList.ReadToDoList.ReadWrite.
  • 애플리케이션 권한:ToDoList.Read.AllToDoList.ReadWrite.All.

사용자 또는 클라이언트 애플리케이션이 웹 API를 호출하면 이러한 범위 또는 권한이 있는 클라이언트만 보호된 엔드포인트에 액세스할 수 있는 권한이 부여됩니다.

{
  "AzureAd": {
    "Instance": "Enter_the_Authority_URL_Here",
    "TenantId": "Enter_the_Tenant_Id_Here",
    "ClientId": "Enter_the_Application_Id_Here",
    "Scopes": {
      "Read": ["ToDoList.Read", "ToDoList.ReadWrite"],
      "Write": ["ToDoList.ReadWrite"]
    },
    "AppPermissions": {
      "Read": ["ToDoList.Read.All", "ToDoList.ReadWrite.All"],
      "Write": ["ToDoList.ReadWrite.All"]
    }
  },
  "Logging": {...},
  "AllowedHosts": "*"
}

API에서 인증 및 권한 부여 구현

인증 및 권한 부여를 구성하려면 program.cs 파일을 열고 해당 내용을 다음 코드 조각으로 바꿉니다.

인증 체계 추가

이 API에서는 JWT(JSON Web Token) 전달자 체계를 기본 인증 메커니즘으로 사용합니다. AddAuthentication 메서드를 사용하여 JWT 전달자 체계를 등록합니다.

// Add required packages to your imports
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Web;

var builder = WebApplication.CreateBuilder(args);

// Add an authentication scheme
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration);

앱의 모델 만들기

프로젝트의 루트 폴더에서 Models라는 폴더를 만듭니다. Models 폴더로 이동하여 이름이 지정된 ToDo.cs 파일을 만든 다음, 다음 코드를 추가합니다.

using System;

namespace ToDoListAPI.Models;

public class ToDo
{
    public int Id { get; set; }
    public Guid Owner { get; set; }
    public string Description { get; set; } = string.Empty;
}

앞의 코드는 ToDo라는 모델을 만듭니다. 이 모델은 앱이 관리하는 데이터를 나타냅니다.

데이터베이스 컨텍스트 추가

다음으로 데이터 모델에 대한 Entity Framework 기능을 조정하는 데이터베이스 컨텍스트 클래스를 정의합니다. 이 클래스는 애플리케이션과 데이터베이스 간의 상호 작용을 관리하는 Microsoft.EntityFrameworkCore.DbContext 클래스에서 상속됩니다. 데이터베이스 컨텍스트를 추가하려면 다음 단계를 수행합니다.

  1. 프로젝트의 루트 폴더에 DbContext 라는 폴더를 만듭니다.

  2. DbContext 폴더로 이동하여 이름이 지정된 ToDoContext.cs 파일을 만든 다음, 다음 코드를 추가합니다.

    using Microsoft.EntityFrameworkCore;
    using ToDoListAPI.Models;
    
    namespace ToDoListAPI.Context;
    
    public class ToDoContext : DbContext
    {
        public ToDoContext(DbContextOptions<ToDoContext> options) : base(options)
        {
        }
    
        public DbSet<ToDo> ToDos { get; set; }
    }
    
  3. 프로젝트의 루트 폴더에서 Program.cs 파일을 열고 다음 코드로 업데이트합니다.

    // Add the following to your imports
    using ToDoListAPI.Context;
    using Microsoft.EntityFrameworkCore;
    
    //Register ToDoContext as a service in the application
    builder.Services.AddDbContext<ToDoContext>(opt =>
        opt.UseInMemoryDatabase("ToDos"));
    

앞의 코드 조각에서는 DB 컨텍스트를 ASP.NET Core 애플리케이션 서비스 공급자(종속성 주입 컨테이너라고도 함)에 범위가 지정된 서비스로 등록합니다. 또한 ToDo List API에 메모리 내 데이터베이스를 사용하도록 클래스를 구성 ToDoContext 합니다.

컨트롤러 설정

컨트롤러는 일반적으로 리소스를 관리하기 위해 CRUD(만들기, 읽기, 업데이트 및 삭제) 작업을 구현합니다. 이 자습서에서는 API 엔드포인트를 보호하는 데 더 중점을 두기 때문에 컨트롤러에서 두 개의 작업 항목만 구현합니다. 모든 To-Do 항목을 읽어 오는 작업과 새 To-Do 항목을 추가하는 생성 작업입니다. 다음 단계에 따라 프로젝트에 컨트롤러를 추가합니다.

  1. 프로젝트의 루트 폴더로 이동하고 Controllers라는 폴더를 만듭니다.

  2. ToDoListController.cs 폴더 내에 이름이 지정된 파일을 만들고 다음 보일러 플레이트 코드를 추가합니다.

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.Resource;
using ToDoListAPI.Models;
using ToDoListAPI.Context;

namespace ToDoListAPI.Controllers;

[Authorize]
[Route("api/[controller]")]
[ApiController]
public class ToDoListController : ControllerBase
{
    private readonly ToDoContext _toDoContext;

    public ToDoListController(ToDoContext toDoContext)
    {
        _toDoContext = toDoContext;
    }

    [HttpGet()]
    [RequiredScopeOrAppPermission()]
    public async Task<IActionResult> GetAsync(){...}

    [HttpPost]
    [RequiredScopeOrAppPermission()]
    public async Task<IActionResult> PostAsync([FromBody] ToDo toDo){...}

    private bool RequestCanAccessToDo(Guid userId){...}

    private Guid GetUserId(){...}

    private bool IsAppMakingRequest(){...}
}

컨트롤러에 코드 추가

이 섹션에서는 이전 섹션에서 스캐폴드된 컨트롤러에 코드를 추가하는 방법을 설명합니다. 여기서는 API를 빌드하지 않고 보호하는 데 중점을 두고 있습니다.

  1. 필요한 패키지를 가져옵니다. 패키지는 Microsoft.Identity.Web 토큰 유효성 검사 처리와 같은 인증 논리를 쉽게 처리하는 데 도움이 되는 MSAL.NET 대한 래퍼입니다. 엔드포인트에 권한 부여가 필요한 것을 보장하기 위해 내장된 Microsoft.AspNetCore.Authorization 패키지를 사용합니다.

  2. 저희는 사용자의 대신으로 클라이언트가 호출하도록 위임된 권한을 사용하거나, 클라이언트가 스스로이자 사용자의 대신이 아닌 것으로 애플리케이션 권한을 사용하여 이 API를 호출할 수 있는 권한을 부여했기 때문에, 호출이 앱 스스로의 권한으로 이루어지고 있는지 여부를 아는 것이 중요합니다. 이 작업을 수행하는 가장 쉬운 방법은 액세스 토큰에 선택적 클레임이 포함되어 idtyp 있는지 여부를 찾는 것입니다. 이 idtyp 클레임은 API가 토큰이 앱 토큰인지 앱 + 사용자 토큰인지를 확인하는 가장 쉬운 방법입니다. idtyp 선택적 클레임을 활성화하는 것을 권장합니다.

    클레임을 idtyp 사용하도록 설정하지 않은 경우 해당 및 roles 클레임을 사용하여 scp 액세스 토큰이 앱 토큰인지 앱 + 사용자 토큰인지 확인할 수 있습니다. Microsoft Entra ID에서 발급한 액세스 토큰에는 두 클레임 중 하나 이상이 있습니다. 사용자에게 발급된 액세스 토큰에는 scp 클레임이 포함되어 있습니다. 애플리케이션에 발급된 액세스 토큰에는 roles 클레임이 포함되어 있습니다. 두 클레임을 모두 포함하는 액세스 토큰은 클레임이 scp 위임된 권한을 지정하는 반면 roles 클레임은 사용자의 역할을 지정하는 사용자에게만 발급됩니다. 조건을 만족하지 않는 액세스 토큰은 인정되지 않아야 합니다.

    private bool IsAppMakingRequest()
    {
        if (HttpContext.User.Claims.Any(c => c.Type == "idtyp"))
        {
            return HttpContext.User.Claims.Any(c => c.Type == "idtyp" && c.Value == "app");
        }
        else
        {
            return HttpContext.User.Claims.Any(c => c.Type == "roles") && !HttpContext.User.Claims.Any(c => c.Type == "scp");
        }
    }
    
  3. 수행 중인 요청에 의도한 작업을 수행할 수 있는 충분한 권한이 포함되어 있는지 여부를 결정하는 도우미 함수를 추가합니다. 앱이 자체적으로 요청을 만드는지 또는 앱이 사용자 ID의 유효성을 검사하여 지정된 리소스를 소유한 사용자를 대신하여 호출하는지 확인합니다.

    private bool RequestCanAccessToDo(Guid userId)
        {
            return IsAppMakingRequest() || (userId == GetUserId());
        }
    
    private Guid GetUserId()
        {
            Guid userId;
            if (!Guid.TryParse(HttpContext.User.GetObjectId(), out userId))
            {
                throw new Exception("User ID is not valid.");
            }
            return userId;
        }
    
  4. 사용 권한 정의를 연결하여 경로를 보호합니다. 컨트롤러 클래스에 특성을 추가하여 [Authorize] API를 보호합니다. 이렇게 하면 API가 권한 있는 ID로 호출되는 경우에만 컨트롤러 작업을 호출할 수 있습니다. 권한 정의는 이러한 작업을 수행하는 데 필요한 사용 권한 종류를 정의합니다.

    [Authorize]
    [Route("api/[controller]")]
    [ApiController]
    public class ToDoListController: ControllerBase{...}
    

    GET 및 POST 엔드포인트에 권한을 추가합니다. Microsoft.Identity.Web.Resource 네임스페이스의 일부인 RequiredScopeOrAppPermission 메서드를 사용하여 이 작업을 수행합니다. 그런 다음 RequiredScopesConfigurationKey 및RequiredAppPermissionsConfigurationKey 특성을 통해 범위 및 권한을 이 메서드에 전달합니다.

    [HttpGet]
    [RequiredScopeOrAppPermission(
        RequiredScopesConfigurationKey = "AzureAD:Scopes:Read",
        RequiredAppPermissionsConfigurationKey = "AzureAD:AppPermissions:Read"
    )]
    public async Task<IActionResult> GetAsync()
    {
        var toDos = await _toDoContext.ToDos!
            .Where(td => RequestCanAccessToDo(td.Owner))
            .ToListAsync();
    
        return Ok(toDos);
    }
    
    [HttpPost]
    [RequiredScopeOrAppPermission(
        RequiredScopesConfigurationKey = "AzureAD:Scopes:Write",
        RequiredAppPermissionsConfigurationKey = "AzureAD:AppPermissions:Write"
    )]
    public async Task<IActionResult> PostAsync([FromBody] ToDo toDo)
    {
        // Only let applications with global to-do access set the user ID or to-do's
        var ownerIdOfTodo = IsAppMakingRequest() ? toDo.Owner : GetUserId();
    
        var newToDo = new ToDo()
        {
            Owner = ownerIdOfTodo,
            Description = toDo.Description
        };
    
        await _toDoContext.ToDos!.AddAsync(newToDo);
        await _toDoContext.SaveChangesAsync();
    
        return Created($"/todo/{newToDo!.Id}", newToDo);
    }
    

컨트롤러를 사용하도록 API 미들웨어 구성

다음으로, HTTP 요청을 처리하기 위해 컨트롤러를 인식하고 사용하도록 애플리케이션을 구성합니다. program.cs 파일을 열고 다음 코드를 추가하여 종속성 주입 컨테이너에 컨트롤러 서비스를 등록합니다.


builder.Services.AddControllers();

var app = builder.Build();
app.MapControllers();

app.Run();

위의 코드 조각 AddControllers() 에서 메서드는 들어오는 HTTP 요청을 처리하도록 컨트롤러 경로를 매핑하는 동안 MapControllers() 필요한 서비스를 등록하여 컨트롤러를 사용하도록 애플리케이션을 준비합니다.

API 실행

명령 dotnet run사용하여 오류 없이 실행되도록 API를 실행합니다. 테스트하는 동안에도 HTTPS 프로토콜을 사용하려는 경우, .NET의 개발 인증서를 신뢰해야 합니다.

  1. 터미널에 다음을 입력하여 애플리케이션을 시작합니다.

    dotnet run
    
  2. 다음과 유사한 출력이 터미널에 표시되어야 하며, 이는 애플리케이션이 실행 중 http://localhost:{port} 이고 요청을 수신 대기하고 있음을 확인합니다.

    Building...
    info: Microsoft.Hosting.Lifetime[0]
        Now listening on: http://localhost:{port}
    info: Microsoft.Hosting.Lifetime[0]
        Application started. Press Ctrl+C to shut down.
    ...
    

웹 페이지 http://localhost:{host} 다음 이미지와 유사한 출력을 표시합니다. 이는 API가 인증 없이 호출되기 때문입니다. 권한 있는 호출을 수행하려면 다음 단계 참조하여 보호된 웹 API에 액세스하는 방법에 대한 지침을 참조하세요.

웹 페이지가 시작될 때 401 오류를 보여 주는 스크린샷

이 API 코드의 전체 예제는 샘플 파일참조하세요.

다음 단계