Microsoft.Identity.Web.OWIN 패키지를 사용하여 .NET Framework 4.7.2 이후 버전에서 ASP.NET MVC 및 Web API 애플리케이션에 최신 인증을 추가합니다.
OWIN 통합 이해
Microsoft.Identity.Web.OWIN 패키지는 OWIN 미들웨어와 Microsoft Entra ID를 사용하는 ASP.NET MVC 및 Web API 애플리케이션에 Microsoft.Identity.Web의 강력한 기능을 제공합니다.
주요 이점 검토
다음 표에는 패키지의 주요 기능과 이점이 요약되어 있습니다.
| 특징 | 이익 |
|---|---|
| TokenAcquirerFactory | 캐싱을 사용하여 자동 토큰 획득 |
| 컨트롤러 확장 |
GraphServiceClient 및 IDownstreamApi에 쉽게 액세스할 수 있습니다. |
| 분산 토큰 캐시 | SQL Server, Redis, Cosmos DB, PostgreSQL에 대한 기본 제공 지원 |
| 자동 토큰 새로 고침 | 토큰 새로 고침을 투명하게 처리합니다. |
| 증분 동의 | 원활한 동의 흐름 통합 |
지원되는 시나리오 검토
Microsoft. Identity.Web.OWIN은 다음과 같은 애플리케이션 유형 및 시나리오를 지원합니다.
- ASP.NET MVC 웹 애플리케이션(.NET Framework 4.7.2 이상)
- ASP.NET Web API(.NET Framework 4.7.2 이상)
- Hybrid Apps (MVC + Web API)
- 컨트롤러에서 Microsoft Graph 호출
- 자동 인증을 사용하여 다운스트림 API 호출
패키지 설치
Microsoft.Identity.Web.OWIN NuGet 패키지를 선호하는 방법을 사용하여 설치합니다.
패키지 관리자 콘솔 다음 명령을 실행합니다.
Install-Package Microsoft.Identity.Web.OWIN
또는 .NET CLI 사용하여 다음 명령을 실행합니다.
dotnet add package Microsoft.Identity.Web.OWIN
종속성이 자동으로 포함됨:
- Microsoft.Identity.Web.TokenAcquisition
- Microsoft. Identity.Web.TokenCache
- Microsoft.Owin
- System.web
애플리케이션 구성
Microsoft Entra 및 다운스트림 API에 연결하도록 애플리케이션 설정을 구성합니다.
Web.config 구성
다음 Microsoft Entra 및 다운스트림 API 설정을 Web.config 파일에 추가합니다.
<configuration>
<appSettings>
<!-- Microsoft Entra ID Configuration -->
<add key="AzureAd:Instance" value="https://login.microsoftonline.com/" />
<add key="AzureAd:TenantId" value="your-tenant-id" />
<add key="AzureAd:ClientId" value="your-client-id" />
<add key="AzureAd:ClientSecret" value="your-client-secret" />
<add key="AzureAd:RedirectUri" value="https://localhost:44368/" />
<add key="AzureAd:PostLogoutRedirectUri" value="https://localhost:44368/" />
<!-- Microsoft Graph Configuration -->
<add key="DownstreamApi:MicrosoftGraph:BaseUrl" value="https://graph.microsoft.com/v1.0" />
<add key="DownstreamApi:MicrosoftGraph:Scopes" value="user.read" />
<!-- Custom Downstream API Configuration -->
<add key="DownstreamApi:TodoListService:BaseUrl" value="https://localhost:44351" />
<add key="DownstreamApi:TodoListService:Scopes" value="api://todo-api-client-id/.default" />
</appSettings>
<connectionStrings>
<!-- Optional: SQL Server Token Cache -->
<add name="TokenCache"
connectionString="Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=TokenCache;Integrated Security=True;" />
</connectionStrings>
</configuration>
appsettings.json 구성 방법(대체)
다음 예제와 appsettings.json 같이 파일에 설정을 저장할 수도 있습니다.
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantId": "your-tenant-id",
"ClientId": "your-client-id",
"ClientSecret": "your-client-secret",
"RedirectUri": "https://localhost:44368/",
"PostLogoutRedirectUri": "https://localhost:44368/"
},
"DownstreamApi": {
"MicrosoftGraph": {
"BaseUrl": "https://graph.microsoft.com/v1.0",
"Scopes": "user.read"
},
"TodoListService": {
"BaseUrl": "https://localhost:44351",
"Scopes": "api://todo-api-client-id/.default"
}
}
}
시작 클래스 설정
시작 클래스에서 인증 미들웨어, 토큰 획득 및 다운스트림 API 서비스를 등록합니다.
App_Start/Startup.Auth.cs 구성
다음 코드는 Microsoft.Identity.Web.OWIN을 사용한 전체 설정을 보여줍니다.
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.OWIN;
using Microsoft.Identity.Web.TokenCacheProviders.Distributed;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.OpenIdConnect;
using Owin;
using System;
using System.Configuration;
using System.Web;
namespace MyMvcApp
{
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
// Set default authentication type
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
// Configure cookie authentication
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
CookieName = "MyApp.Auth",
ExpireTimeSpan = TimeSpan.FromHours(1),
SlidingExpiration = true
});
// Configure OpenID Connect authentication
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = ConfigurationManager.AppSettings["AzureAd:ClientId"],
Authority = $"https://login.microsoftonline.com/{ConfigurationManager.AppSettings["AzureAd:TenantId"]}",
RedirectUri = ConfigurationManager.AppSettings["AzureAd:RedirectUri"],
PostLogoutRedirectUri = ConfigurationManager.AppSettings["AzureAd:PostLogoutRedirectUri"],
Scope = "openid profile email offline_access",
ResponseType = "code id_token",
TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
NameClaimType = "preferred_username"
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("/Error?message=" + context.Exception.Message);
return Task.FromResult(0);
}
}
});
// Configure Microsoft Identity Web services
var services = CreateOwinServiceCollection();
// Add token acquisition
services.AddTokenAcquisition();
// Add Microsoft Graph support
services.AddMicrosoftGraph();
// Add downstream API support
services.AddDownstreamApi("MicrosoftGraph", services.BuildServiceProvider()
.GetRequiredService<IConfiguration>().GetSection("DownstreamApi:MicrosoftGraph"));
services.AddDownstreamApi("TodoListService", services.BuildServiceProvider()
.GetRequiredService<IConfiguration>().GetSection("DownstreamApi:TodoListService"));
// Configure token cache (choose one option)
ConfigureTokenCache(services);
// Build service provider
var serviceProvider = services.BuildServiceProvider();
// Create and register token acquirer factory
var tokenAcquirerFactory = TokenAcquirerFactory.GetDefaultInstance();
tokenAcquirerFactory.Build(serviceProvider);
// Add OWIN token acquisition middleware
app.Use<OwinTokenAcquisitionMiddleware>(tokenAcquirerFactory);
}
private IServiceCollection CreateOwinServiceCollection()
{
var services = new ServiceCollection();
// Add configuration from appsettings.json and/or Web.config
IConfiguration configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: true)
.AddInMemoryCollection(new Dictionary<string, string>
{
["AzureAd:Instance"] = ConfigurationManager.AppSettings["AzureAd:Instance"],
["AzureAd:TenantId"] = ConfigurationManager.AppSettings["AzureAd:TenantId"],
["AzureAd:ClientId"] = ConfigurationManager.AppSettings["AzureAd:ClientId"],
["AzureAd:ClientSecret"] = ConfigurationManager.AppSettings["AzureAd:ClientSecret"],
["DownstreamApi:MicrosoftGraph:BaseUrl"] = ConfigurationManager.AppSettings["DownstreamApi:MicrosoftGraph:BaseUrl"],
["DownstreamApi:MicrosoftGraph:Scopes"] = ConfigurationManager.AppSettings["DownstreamApi:MicrosoftGraph:Scopes"],
})
.Build();
services.AddSingleton(configuration);
return services;
}
private void ConfigureTokenCache(IServiceCollection services)
{
// Option 1: In-memory cache (development)
services.AddDistributedTokenCaches(cacheServices =>
{
cacheServices.AddDistributedMemoryCache();
});
// Option 2: SQL Server cache (production)
/*
services.AddDistributedTokenCaches(cacheServices =>
{
cacheServices.AddDistributedSqlServerCache(options =>
{
options.ConnectionString = ConfigurationManager.ConnectionStrings["TokenCache"].ConnectionString;
options.SchemaName = "dbo";
options.TableName = "TokenCache";
options.DefaultSlidingExpiration = TimeSpan.FromMinutes(90);
});
});
*/
// Option 3: Redis cache (production, high-scale)
/*
services.AddDistributedTokenCaches(cacheServices =>
{
cacheServices.AddStackExchangeRedisCache(options =>
{
options.Configuration = ConfigurationManager.AppSettings["Redis:ConnectionString"];
options.InstanceName = "MyMvcApp_";
});
});
*/
}
}
}
컨트롤러 통합
패키지에서 제공하는 확장 메서드를 사용하여 컨트롤러에서 Microsoft Graph 및 다운스트림 API에 액세스합니다.
MVC 컨트롤러 통합
다음 예제에서는 컨트롤러 확장 메서드를 사용하여 Microsoft Graph 액세스하는 방법을 보여 줍니다.
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.OWIN;
using Microsoft.Graph;
using System.Threading.Tasks;
using System.Web.Mvc;
namespace MyMvcApp.Controllers
{
[Authorize]
public class HomeController : Controller
{
// GET: Home/Index
public async Task<ActionResult> Index()
{
try
{
// Access Microsoft Graph using extension method
var graphClient = this.GetGraphServiceClient();
var user = await graphClient.Me.GetAsync();
ViewBag.UserName = user.DisplayName;
ViewBag.Email = user.Mail ?? user.UserPrincipalName;
ViewBag.JobTitle = user.JobTitle;
return View();
}
catch (MsalUiRequiredException)
{
// Incremental consent required
return new ChallengeResult();
}
catch (Exception ex)
{
return View("Error", new ErrorViewModel { Message = ex.Message });
}
}
// GET: Home/Profile
public async Task<ActionResult> Profile()
{
var graphClient = this.GetGraphServiceClient();
// Get user profile
var user = await graphClient.Me
.GetAsync(requestConfig => requestConfig.QueryParameters.Select = new[] { "displayName", "mail", "jobTitle", "department" });
return View(user);
}
// GET: Home/Photo
public async Task<ActionResult> Photo()
{
var graphClient = this.GetGraphServiceClient();
try
{
// Get user photo
var photoStream = await graphClient.Me.Photo.Content.GetAsync();
return File(photoStream, "image/jpeg");
}
catch (ServiceException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
{
return File(Server.MapPath("~/Content/images/default-user.png"), "image/png");
}
}
}
}
Web API 컨트롤러 통합
다음 예제에서는 ApiController 확장 메서드를 사용하여 다운스트림 API를 호출하는 방법을 보여 줍니다.
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.OWIN;
using Microsoft.Identity.Abstractions;
using System.Threading.Tasks;
using System.Web.Http;
namespace MyWebApi.Controllers
{
[Authorize]
[RoutePrefix("api/todos")]
public class TodoController : ApiController
{
// GET: api/todos
[HttpGet]
[Route("")]
public async Task<IHttpActionResult> GetTodos()
{
try
{
// Call downstream API using extension method
var downstreamApi = this.GetDownstreamApi();
var todos = await downstreamApi.GetForUserAsync<List<TodoItem>>(
"TodoListService",
options =>
{
options.RelativePath = "api/todolist";
});
return Ok(todos);
}
catch (MsalUiRequiredException)
{
return Unauthorized();
}
catch (HttpRequestException ex)
{
return InternalServerError(ex);
}
}
// POST: api/todos
[HttpPost]
[Route("")]
public async Task<IHttpActionResult> CreateTodo([FromBody] TodoItem todo)
{
var downstreamApi = this.GetDownstreamApi();
var createdTodo = await downstreamApi.PostForUserAsync<TodoItem, TodoItem>(
"TodoListService",
todo,
options =>
{
options.RelativePath = "api/todolist";
});
return Created($"api/todos/{createdTodo.Id}", createdTodo);
}
}
}
Microsoft Graph 호출
GraphServiceClient 사용하여 컨트롤러의 Microsoft Graph 데이터와 상호 작용합니다.
Microsoft Graph 클라이언트 설정
Microsoft Graph 클라이언트는 다음 호출을 사용하여 Startup.Auth.cs 이미 구성되어 있습니다.
services.AddMicrosoftGraph();
컨트롤러에서 GraphServiceClient 사용
다음 예제에서는 MVC 컨트롤러의 일반적인 Microsoft Graph 작업을 보여 줍니다.
[Authorize]
public class GraphController : Controller
{
public async Task<ActionResult> MyProfile()
{
var graphClient = this.GetGraphServiceClient();
var user = await graphClient.Me.GetAsync();
return View(user);
}
public async Task<ActionResult> MyManager()
{
var graphClient = this.GetGraphServiceClient();
var manager = await graphClient.Me.Manager.GetAsync();
return View(manager);
}
public async Task<ActionResult> MyDirectReports()
{
var graphClient = this.GetGraphServiceClient();
var directReports = await graphClient.Me.DirectReports.GetAsync();
return View(directReports.Value);
}
public async Task<ActionResult> SendEmail([FromBody] EmailMessage message)
{
var graphClient = this.GetGraphServiceClient();
var email = new Message
{
Subject = message.Subject,
Body = new ItemBody
{
ContentType = BodyType.Text,
Content = message.Body
},
ToRecipients = new[]
{
new Recipient
{
EmailAddress = new EmailAddress
{
Address = message.To
}
}
}
};
await graphClient.Me.SendMail.PostAsync(new SendMailPostRequestBody
{
Message = email
});
return RedirectToAction("Index");
}
}
다운스트림 API 호출
컨트롤러에서 자동 토큰 획득을 사용하여 다운스트림 API를 등록하고 호출합니다.
다운스트림 API 구성
다음 코드로 Startup.Auth.cs 다운스트림 API를 등록합니다.
services.AddDownstreamApi("TodoListService", configuration.GetSection("DownstreamApi:TodoListService"));
다음에서 해당 설정을 추가합니다.Web.config
<add key="DownstreamApi:TodoListService:BaseUrl" value="https://localhost:44351" />
<add key="DownstreamApi:TodoListService:Scopes" value="api://todo-api-client-id/.default" />
컨트롤러에서 IDownstreamApi 사용
다음 예제에서는 MVC 컨트롤러에서 다운스트림 API에 대해 CRUD 작업을 수행하는 방법을 보여 줍니다.
[Authorize]
public class TodoController : Controller
{
// GET all todos
public async Task<ActionResult> Index()
{
var downstreamApi = this.GetDownstreamApi();
var todos = await downstreamApi.GetForUserAsync<List<TodoItem>>(
"TodoListService",
options =>
{
options.RelativePath = "api/todolist";
});
return View(todos);
}
// GET specific todo
public async Task<ActionResult> Details(int id)
{
var downstreamApi = this.GetDownstreamApi();
var todo = await downstreamApi.GetForUserAsync<TodoItem>(
"TodoListService",
options =>
{
options.RelativePath = $"api/todolist/{id}";
});
return View(todo);
}
// POST new todo
[HttpPost]
public async Task<ActionResult> Create(TodoItem todo)
{
var downstreamApi = this.GetDownstreamApi();
var createdTodo = await downstreamApi.PostForUserAsync<TodoItem, TodoItem>(
"TodoListService",
todo,
options =>
{
options.RelativePath = "api/todolist";
});
return RedirectToAction("Index");
}
// PUT update todo
[HttpPost]
public async Task<ActionResult> Edit(int id, TodoItem todo)
{
var downstreamApi = this.GetDownstreamApi();
await downstreamApi.CallApiForUserAsync(
"TodoListService",
options =>
{
options.HttpMethod = HttpMethod.Put;
options.RelativePath = $"api/todolist/{id}";
options.RequestBody = todo;
});
return RedirectToAction("Index");
}
// DELETE todo
[HttpPost]
public async Task<ActionResult> Delete(int id)
{
var downstreamApi = this.GetDownstreamApi();
await downstreamApi.CallApiForUserAsync(
"TodoListService",
options =>
{
options.HttpMethod = HttpMethod.Delete;
options.RelativePath = $"api/todolist/{id}";
});
return RedirectToAction("Index");
}
}
샘플 애플리케이션 살펴보기
다음 샘플을 사용하여 Microsoft.Identity.Web.OWIN의 작동을 확인하세요.
공식 Microsoft 샘플 검토
다음 표에는 Microsoft.Identity.Web.OWIN 통합을 보여주는 공식 샘플이 나와 있습니다.
| 샘플 | 설명 |
|---|---|
| ms-identity-aspnet-webapp-openidconnect | Microsoft.Identity.Web.OWIN을 사용한 ASP.NET MVC 앱 |
| 키 파일 |
App_Start/Startup.Auth.cs, Controllers/HomeController.cs |
다음 명령을 사용하여 샘플을 복제하고 실행합니다.
git clone https://github.com/Azure-Samples/ms-identity-aspnet-webapp-openidconnect
cd ms-identity-aspnet-webapp-openidconnect
# Update Web.config with your Microsoft Entra app registration
# Run in Visual Studio
모범 사례 준수
이러한 권장 패턴을 적용하고 Microsoft.Identity.Web.OWIN을 사용하여 빌드할 때 일반적인 실수를 방지합니다.
권장 패턴 적용
1. 프로덕션 환경에서 분산 캐시 사용:
// Production
services.AddDistributedTokenCaches(cacheServices =>
{
cacheServices.AddDistributedSqlServerCache(options =>
{
options.ConnectionString = ConfigurationManager.ConnectionStrings["TokenCache"].ConnectionString;
options.SchemaName = "dbo";
options.TableName = "TokenCache";
options.DefaultSlidingExpiration = TimeSpan.FromMinutes(90);
});
});
2. 증분 동의를 정상적으로 처리합니다.
try
{
var graphClient = this.GetGraphServiceClient();
var user = await graphClient.Me.GetAsync();
}
catch (MsalUiRequiredException)
{
// User needs to consent to additional scopes
return new ChallengeResult();
}
3. 문제 해결을 위해 상관 관계 ID를 사용합니다.
var downstreamApi = this.GetDownstreamApi();
var correlationId = Guid.NewGuid();
var result = await downstreamApi.GetForUserAsync<Todo>(
"TodoListService",
options =>
{
options.RelativePath = $"api/todolist/{id}";
options.TokenAcquisitionOptions = new TokenAcquisitionOptions
{
CorrelationId = correlationId
};
});
4. 적절한 오류 처리 구현:
try
{
// Call API
}
catch (MsalUiRequiredException)
{
return new ChallengeResult();
}
catch (HttpRequestException ex)
{
logger.Error($"API call failed: {ex.Message}");
return View("Error");
}
일반적인 실수 방지
1. 웹 팜에 메모리 내 캐시를 사용하지 마세요.
// Wrong for load-balanced scenarios
services.AddDistributedTokenCaches(cacheServices =>
{
cacheServices.AddDistributedMemoryCache();
});
// Correct
services.AddDistributedTokenCaches(cacheServices =>
{
cacheServices.AddDistributedSqlServerCache(/* ... */);
});
2. 구성을 하드 코딩하지 마세요.
// Wrong
ClientId = "your-client-id-here"
// Correct
ClientId = ConfigurationManager.AppSettings["AzureAd:ClientId"]
3. 토큰 만료를 무시하지 마세요.
// Microsoft.Identity.Web.OWIN handles this automatically
// No manual token refresh needed!
일반적인 문제 해결
설치 및 런타임 중에 발생하는 일반적인 문제에 대해서는 다음 솔루션을 검토합니다.
일반적인 문제 해결
문제 1: "IAuthorizationHeaderProvider를 찾을 수 없습니다."
솔루션:OwinTokenAcquirerFactory에서 Startup.Auth.cs이(가) 등록되었는지 확인하세요.
var tokenAcquirerFactory = TokenAcquirerFactory.GetDefaultInstance();
tokenAcquirerFactory.Build(serviceProvider);
app.Use<OwinTokenAcquisitionMiddleware>(tokenAcquirerFactory);
문제 2: "GraphServiceClient를 찾을 수 없음"
솔루션:AddMicrosoftGraph()에서 Startup.Auth.cs 추가:
services.AddMicrosoftGraph();
문제 3: 토큰 캐시가 유지되지 않음
솔루션: 분산 캐시 구성 확인:
services.AddDistributedTokenCaches(cacheServices =>
{
cacheServices.AddDistributedSqlServerCache(options =>
{
// Ensure connection string is correct
options.ConnectionString = ConfigurationManager.ConnectionStrings["TokenCache"].ConnectionString;
});
});
관련 콘텐츠 탐색
관련 기능 및 시나리오에 대해 자세히 알아봅니다.