다중 테 넌 트 응용 프로그램용 백 엔드 웹 API 보안Secure a backend web API for multitenant applications
Tailspin 설문 조사 애플리케이션은 백 엔드 웹 API를 사용하여 설문 조사에 대한 CRUD 작업을 관리합니다.The Tailspin Surveys application uses a backend web API to manage CRUD operations on surveys. 예를 들어 사용자가 "내 설문 조사"를 클릭하면 웹 애플리케이션은 웹 API에 HTTP 요청을 보냅니다.For example, when a user clicks "My Surveys", the web application sends an HTTP request to the web API:
GET /users/{userId}/surveys
웹 API는 JSON 개체를 반환합니다.The web API returns a JSON object:
{
"Published":[],
"Own":[
{"Id":1,"Title":"Survey 1"},
{"Id":3,"Title":"Survey 3"},
],
"Contribute": [{"Id":8,"Title":"My survey"}]
}
웹앱은 OAuth 2 전달자 토큰을 사용하여 자신을 인증해야 하므로 웹 API는 익명 요청을 허용하지 않습니다.The web API does not allow anonymous requests, so the web app must authenticate itself using OAuth 2 bearer tokens.
참고
이는 서버 간 시나리오입니다.This is a server-to-server scenario. 애플리케이션은 브라우저 클라이언트에서 API에 대한 AJAX 호출을 만들지 않습니다.The application does not make any AJAX calls to the API from the browser client.
실행할 수 있는 두 가지 주요 방법이 있습니다.There are two main approaches you can take:
- 위임된 사용자 ID.Delegated user identity. 웹 애플리케이션은 사용자의 ID로 인증합니다.The web application authenticates with the user's identity.
- 애플리케이션 ID.Application identity. 웹 응용 프로그램은 OAuth 2 클라이언트 자격 증명 흐름을 사용 하 여 클라이언트 ID를 사용 하 여 인증 합니다.The web application authenticates with its client ID, using OAuth 2 client credential flow.
Tailspin 애플리케이션은 위임된 사용자 ID를 구현합니다.The Tailspin application implements delegated user identity. 주요 차이점은 다음과 같습니다.Here are the main differences:
위임 된 사용자 id:Delegated user identity:
- 웹 API로 전송되는 전달자 토큰은 사용자 ID를 포함합니다.The bearer token sent to the web API contains the user identity.
- 웹 API는 사용자 ID에 따라 권한 부여를 결정합니다.The web API makes authorization decisions based on the user identity.
- 웹 애플리케이션은 사용자가 작업을 수행할 수 있는 권한이 없는 경우 웹 API에서 403(사용할 수 없음) 오류를 처리해야 합니다.The web application needs to handle 403 (Forbidden) errors from the web API, if the user is not authorized to perform an action.
- 일반적으로 웹 애플리케이션은 UI 요소 표시 또는 숨기기와 같은 UI에 영향을 주는 몇 가지 권한 부여를 결정합니다.)Typically, the web application still makes some authorization decisions that affect UI, such as showing or hiding UI elements).
- 웹 API는 JavaScript 애플리케이션 또는 네이티브 클라이언트 애플리케이션과 같은 신뢰할 수 없는 클라이언트에서 잠재적으로 사용될 수 있습니다.The web API can potentially be used by untrusted clients, such as a JavaScript application or a native client application.
응용 프로그램 id:Application identity:
- 웹 API는 사용자에 대한 정보를 가져오지 않습니다.The web API does not get information about the user.
- 웹 API는 사용자 ID에 따라 권한 부여를 수행할 수 없습니다.The web API cannot perform any authorization based on the user identity. 웹 애플리케이션에서 모든 권한 부여를 결정합니다.All authorization decisions are made by the web application.
- 웹 API는 신뢰할 수 없는 클라이언트(JavaScript 또는 네이티브 클라이언트 애플리케이션)에서 사용될 수 없습니다.The web API cannot be used by an untrusted client (JavaScript or native client application).
- 웹 API에는 권한 부여 논리가 없으므로 이 방법은 구현하는 데 다소 수월할 수 있습니다.This approach may be somewhat simpler to implement, because there is no authorization logic in the Web API.
방법 중 하나에서 웹 애플리케이션은 웹 API를 호출하는 데 필요한 자격 증명인 액세스 토큰을 가져와야 합니다.In either approach, the web application must get an access token, which is the credential needed to call the web API.
- 위임 된 사용자 id의 경우 토큰은 사용자를 대신 하 여 토큰을 발급할 수 있는 Azure Active Directory와 같은 id 공급자 (IDP)에서 가져온 것 이어야 합니다.For delegated user identity, the token has to come from an identity provider (IDP), such as Azure Active Directory, which can issue a token on behalf of the user.
- 클라이언트 자격 증명의 경우 애플리케이션은 해당 자체 토큰 서버인 IDP 또는 호스트에서 토큰을 가져올 수도 있습니다.For client credentials, an application might get the token from the IDP or host its own token server. (하지만 토큰 서버를 처음부터 작성 하지 마세요. IdentityServer4같은 잘 테스트 된 프레임 워크를 사용 합니다.) Azure AD를 사용 하 여 인증 하는 경우 클라이언트 자격 증명 흐름에도 불구 하 고 Azure AD에서 액세스 토큰을 가져오는 것이 좋습니다.(But don't write a token server from scratch; use a well-tested framework like IdentityServer4.) If you authenticate with Azure AD, it's strongly recommended to get the access token from Azure AD, even with client credential flow.
이 문서의 나머지 부분에서는 애플리케이션이 Azure AD로 인증하는 것을 가정합니다.The rest of this article assumes the application is authenticating with Azure AD.
Azure AD에서 액세스 토큰을 요청 하 고 웹 API에 토큰을 보내는 웹 응용 프로그램을 보여 주는 다이어그램입니다.A diagram that shows the web application requesting an access token from Azure AD and sending the token to the web API.
Azure AD에서 웹 API 등록Register the web API in Azure AD
Azure AD에서 웹 API에 대한 전달자 토큰을 발급하려면 Azure AD에서 몇 가지를 구성해야 합니다.In order for Azure AD to issue a bearer token for the web API, you need to configure some things in Azure AD.
Azure AD에서 웹 API를 등록합니다.Register the web API in Azure AD.
웹앱의 클라이언트 ID를
knownClientApplications속성의 웹 API 애플리케이션 매니페스트에 추가합니다.Add the client ID of the web app to the web API application manifest, in theknownClientApplicationsproperty. 자세한 내용은 GitHub 추가 정보를 참조 하세요.See the GitHub readme for more information.웹 애플리케이션에 웹 API를 호출하는 권한을 부여합니다.Give the web application permission to call the web API. Azure Portal에서 응용 프로그램 id (클라이언트 자격 증명 흐름)에 대 한 "응용 프로그램 권한" 또는 위임 된 사용자 id에 대 한 "위임 된 권한"의 두 가지 사용 권한 유형을 설정할 수 있습니다.In the Azure portal, you can set two types of permissions: "Application Permissions" for application identity (client credential flow), or "Delegated Permissions" for delegated user identity.
응용 프로그램 사용 권한 및 위임 된 사용 권한을 보여 주는 Azure Portal의 스크린샷A screenshot of the Azure portal that shows the application permissions and delegated permissions.
액세스 토큰 가져오기Getting an access token
웹 API를 호출하기 전에 웹 애플리케이션은 Azure AD에서 액세스 토큰을 가져옵니다.Before calling the web API, the web application gets an access token from Azure AD. .NET 애플리케이션에서는 .NET용 ADAL(Azure AD 인증 라이브러리)을 사용합니다.In a .NET application, use the Azure AD Authentication Library (ADAL) for .NET.
OAuth 2 권한 부여 코드 흐름에서 애플리케이션은 액세스 토큰에 대한 권한 부여 코드를 교환합니다.In the OAuth 2 authorization code flow, the application exchanges an authorization code for an access token. 다음 코드는 ADAL을 사용하여 액세스 토큰을 가져옵니다.The following code uses ADAL to get the access token. 이 코드는 AuthorizationCodeReceived 이벤트 중에 호출됩니다.This code is called during the AuthorizationCodeReceived event.
// The OpenID Connect middleware sends this event when it gets the authorization code.
public override async Task AuthorizationCodeReceived(AuthorizationCodeReceivedContext context)
{
string authorizationCode = context.ProtocolMessage.Code;
string authority = "https://login.microsoftonline.com/" + tenantID
string resourceID = "https://tailspin.onmicrosoft.com/surveys.webapi" // App ID URI
ClientCredential credential = new ClientCredential(clientId, clientSecret);
AuthenticationContext authContext = new AuthenticationContext(authority, tokenCache);
AuthenticationResult authResult = await authContext.AcquireTokenByAuthorizationCodeAsync(
authorizationCode, new Uri(redirectUri), credential, resourceID);
// If successful, the token is in authResult.AccessToken
}
필요한 다양한 매개 변수는 다음과 같습니다.Here are the various parameters that are needed:
authority.authority. 로그인한 사용자의 테넌트 ID에서 파생됨.Derived from the tenant ID of the signed in user. (SaaS 공급자의 테넌트 ID가 아님)(Not the tenant ID of the SaaS provider)authorizationCode.authorizationCode. IDP에서 다시 가져온 인증 코드.the auth code that you got back from the IDP.clientId.clientId. 웹 애플리케이션의 클라이언트 ID.The web application's client ID.clientSecret.clientSecret. 웹 애플리케이션의 클라이언트 암호.The web application's client secret.redirectUri.redirectUri. Openid connect Connect에 대해 설정 하는 리디렉션 URI입니다.The redirect URI that you set for OpenID Connect. IDP가 토큰으로 다시 호출하는 위치입니다.This is where the IDP calls back with the token.resourceID.resourceID. Azure AD에서 웹 API를 등록할 때 만든 웹 API의 앱 ID URIThe App ID URI of the web API, which you created when you registered the web API in Azure ADtokenCache.tokenCache. 액세스 토큰을 캐시하는 개체.An object that caches the access tokens. 토큰 캐싱을 참조하세요.See Token caching.
AcquireTokenByAuthorizationCodeAsync 이(가) 성공하는 경우 ADAL은 토큰을 캐시합니다.If AcquireTokenByAuthorizationCodeAsync succeeds, ADAL caches the token. 나중에 AcquireTokenSilentAsync를 호출하여 캐시에서 토큰을 가져올 수 있습니다.Later, you can get the token from the cache by calling AcquireTokenSilentAsync:
AuthenticationContext authContext = new AuthenticationContext(authority, tokenCache);
var result = await authContext.AcquireTokenSilentAsync(resourceID, credential, new UserIdentifier(userId, UserIdentifierType.UniqueId));
여기서 userId는 http://schemas.microsoft.com/identity/claims/objectidentifier 클레임에 있는 사용자의 개체 ID입니다.where userId is the user's object ID, which is found in the http://schemas.microsoft.com/identity/claims/objectidentifier claim.
액세스 토큰을 사용하여 웹 API 호출Using the access token to call the web API
토큰을 가지면 웹 API에 대한 HTTP 요청의 인증 헤더에 전송합니다.Once you have the token, send it in the Authorization header of the HTTP requests to the web API.
Authorization: Bearer xxxxxxxxxx
설문 조사 애플리케이션의 다음 확장 메서드는 HttpClient 클래스를 사용하여 HTTP 요청에 권한 부여 헤더를 설정합니다.The following extension method from the Surveys application sets the Authorization header on an HTTP request, using the HttpClient class.
public static async Task<HttpResponseMessage> SendRequestWithBearerTokenAsync(this HttpClient httpClient, HttpMethod method, string path, object requestBody, string accessToken, CancellationToken ct)
{
var request = new HttpRequestMessage(method, path);
if (requestBody != null)
{
var json = JsonConvert.SerializeObject(requestBody, Formatting.None);
var content = new StringContent(json, Encoding.UTF8, "application/json");
request.Content = content;
}
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await httpClient.SendAsync(request, ct);
return response;
}
웹 API에서 인증Authenticating in the web API
웹 API는 전달자 토큰을 인증해야 합니다.The web API has to authenticate the bearer token. ASP.NET Core에서는 Microsoft.AspNet.Authentication.JwtBearer 패키지를 사용할 수 있습니다.In ASP.NET Core, you can use the Microsoft.AspNet.Authentication.JwtBearer package. 이 패키지는 애플리케이션에서 OpenID Connect 전달자 토큰을 받을 수 있게 해 주는 미들웨어를 제공합니다.This package provides middleware that enables the application to receive OpenID Connect bearer tokens.
웹 API Startup 클래스에서 미들웨어를 등록합니다.Register the middleware in your web API Startup class.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ApplicationDbContext dbContext, ILoggerFactory loggerFactory)
{
// ...
app.UseJwtBearerAuthentication(new JwtBearerOptions {
Audience = configOptions.AzureAd.WebApiResourceId,
Authority = Constants.AuthEndpointPrefix,
TokenValidationParameters = new TokenValidationParameters {
ValidateIssuer = false
},
Events= new SurveysJwtBearerEvents(loggerFactory.CreateLogger<SurveysJwtBearerEvents>())
});
// ...
}
- 대상.Audience. Azure AD로 웹 API를 등록할 때 만든 웹 API에 대한 앱 ID URI로 설정합니다.Set this to the App ID URL for the web API, which you created when you registered the web API with Azure AD.
- 권한.Authority. 다중 테넌트 애플리케이션의 경우
https://login.microsoftonline.com/common/으로 설정합니다.For a multitenant application, set this tohttps://login.microsoftonline.com/common/. - TokenvalidationparametersTokenValidationParameters. 다중 테넌트 애플리케이션의 경우 ValidateIssuer를 false로 설정합니다.For a multitenant application, set ValidateIssuer to false. 즉, 애플리케이션이 발급자의 유효성을 검사합니다.That means the application will validate the issuer.
- Events는 JwtBearerEvents에서 파생된 클래스입니다.Events is a class that derives from JwtBearerEvents.
발급자 유효성 검사Issuer validation
JwtBearerEvents.TokenValidated 이벤트에서 토큰 발급자의 유효성을 검사합니다.Validate the token issuer in the JwtBearerEvents.TokenValidated event. 발급자는 "iss" 클레임에서 전송됩니다.The issuer is sent in the "iss" claim.
설문 조사 애플리케이션에서 웹 API는 [테넌트 등록]을 처리하지 않습니다.In the Surveys application, the web API doesn't handle tenant sign-up. 따라서 발급자가 이미 애플리케이션 데이터베이스에 있는지만을 확인합니다.Therefore, it just checks if the issuer is already in the application database. 그렇지 않으면 인증이 실패되는 예외를 throw합니다.If not, it throws an exception, which causes authentication to fail.
public override async Task TokenValidated(TokenValidatedContext context)
{
var principal = context.Ticket.Principal;
var tenantManager = context.HttpContext.RequestServices.GetService<TenantManager>();
var userManager = context.HttpContext.RequestServices.GetService<UserManager>();
var issuerValue = principal.GetIssuerValue();
var tenant = await tenantManager.FindByIssuerValueAsync(issuerValue);
if (tenant == null)
{
// The caller was not from a trusted issuer. Throw to block the authentication flow.
throw new SecurityTokenValidationException();
}
var identity = principal.Identities.First();
// Add new claim for survey_userid
var registeredUser = await userManager.FindByObjectIdentifier(principal.GetObjectIdentifierValue());
identity.AddClaim(new Claim(SurveyClaimTypes.SurveyUserIdClaimType, registeredUser.Id.ToString()));
identity.AddClaim(new Claim(SurveyClaimTypes.SurveyTenantIdClaimType, registeredUser.TenantId.ToString()));
// Add new claim for Email
var email = principal.FindFirst(ClaimTypes.Upn)?.Value;
if (!string.IsNullOrWhiteSpace(email))
{
identity.AddClaim(new Claim(ClaimTypes.Email, email));
}
}
이 예제에서처럼 TokenValidated 이벤트를 사용하여 클레임을 수정할 수도 있습니다.As this example shows, you can also use the TokenValidated event to modify the claims. 클레임은 Azure AD에서 직접 가져온 것입니다.Remember that the claims come directly from Azure AD. 웹 애플리케이션에서 가져오는 클레임을 수정할 경우 해당 변경 내용이 웹 API를 수신하는 전달자 토큰에 나타나지 않습니다.If the web application modifies the claims that it gets, those changes won't show up in the bearer token that the web API receives. 자세한 내용은 클레임 변환을 참조하세요.For more information, see Claims transformations.
권한 부여Authorization
권한 부여에 대한 일반적 내용은 역할 기반 및 리소스 기반 권한 부여를 참조하세요.For a general discussion of authorization, see Role-based and resource-based authorization.
JwtBearer 미들웨어는 인증 응답을 처리합니다.The JwtBearer middleware handles the authorization responses. 예를 들어, 컨트롤러 작업을 인증 된 사용자로 제한 하려면 [권한 부여] 특성을 사용 하 고 인증 체계로 JwtBearerDefaults 을 지정 합니다.For example, to restrict a controller action to authenticated users, use the [Authorize] attribute and specify JwtBearerDefaults.AuthenticationScheme as the authentication scheme:
[Authorize(ActiveAuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
사용자가 인증되지 않은 경우 401 상태 코드를 반환합니다.This returns a 401 status code if the user is not authenticated.
권한 부여 정책에 따라 컨트롤러 작업을 제한 하려면 [권한 부여] 특성에서 정책 이름을 지정 합니다.To restrict a controller action by authorization policy, specify the policy name in the [Authorize] attribute:
[Authorize(Policy = PolicyNames.RequireSurveyCreator)]
사용자가 인증되지 않은 경우 401 상태 코드를 반환하고 사용자가 인증됐지만 권한이 부여되지 않은 경우 403을 반환합니다.This returns a 401 status code if the user is not authenticated, and 403 if the user is authenticated but not authorized. 시작 시 정책을 등록합니다.Register the policy on startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthorization(options =>
{
options.AddPolicy(PolicyNames.RequireSurveyCreator,
policy =>
{
policy.AddRequirements(new SurveyCreatorRequirement());
policy.RequireAuthenticatedUser(); // Adds DenyAnonymousAuthorizationRequirement
policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);
});
options.AddPolicy(PolicyNames.RequireSurveyAdmin,
policy =>
{
policy.AddRequirements(new SurveyAdminRequirement());
policy.RequireAuthenticatedUser(); // Adds DenyAnonymousAuthorizationRequirement
policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);
});
});
// ...
}
응용 프로그램 비밀 보호Protecting application secrets
다음과 같이 민감하고 보호되어야 하는 애플리케이션 설정을 갖는 것이 일반적입니다.It's common to have application settings that are sensitive and must be protected, such as:
- 데이터베이스 연결 문자열Database connection strings
- 암호Passwords
- 암호화 키Cryptographic keys
보안 모범 사례로 이 암호를 소스 제어에 저장해서는 안됩니다.As a security best practice, you should never store these secrets in source control. 소스 코드 리포지토리가 프라이빗인 경우에도 —이 쉽게 누출될 수 있습니다.It's too easy for them to leak — even if your source code repository is private. 공용으로부터 암호를 유지하는 것 뿐만이 아닙니다.And it's not just about keeping secrets from the general public. 대규모 프로젝트에서 프로덕션 암호에 액세스할 수 있는 개발자 및 운영자를 제한할 수 있습니다.On larger projects, you might want to restrict which developers and operators can access the production secrets. (테스트 또는 개발 환경에 대한 설정은 서로 다릅니다.)(Settings for test or development environments are different.)
보다 안전한 옵션은 이러한 비밀을 Azure Key Vault에 저장하는 것입니다.A more secure option is to store these secrets in Azure Key Vault. 키 자격 증명 모음은 암호화 키 및 기타 암호를 관리하기 위한 클라우드 호스티드 서비스입니다.Key Vault is a cloud-hosted service for managing cryptographic keys and other secrets. 이 문서는 키 자격 증명 모음을 사용하여 앱에 대한 구성 설정을 저장하는 방법을 보여줍니다.This article shows how to use Key Vault to store configuration settings for your app.
Tailspin Surveys 애플리케이션에서 다음 설정은 비밀입니다.In the Tailspin Surveys application, the following settings are secret:
- 데이터베이스 연결 문자열.The database connection string.
- Redis 연결 문자열.The Redis connection string.
- 웹 애플리케이션에 대한 클라이언트 암호.The client secret for the web application.
설문 조사 애플리케이션은 다음 위치에서 구성 설정을 로드합니다.The Surveys application loads configuration settings from the following places:
- appsettings.json 파일The appsettings.json file
- 사용자 비밀 저장소(개발 환경에만 해당, 테스트용)The user secrets store (development environment only; for testing)
- 호스팅 환경(Azure 웹앱에서 앱 설정)The hosting environment (app settings in Azure web apps)
- Key Vault(사용 가능한 경우)Key Vault (when enabled)
각각은 이전 것을 재정의하므로 키 자격 증명 모음에 저장된 모든 설정이 우선적으로 적용됩니다.Each of these overrides the previous one, so any settings stored in Key Vault take precedence.
참고
기본적으로 키 자격 증명 모음 구성 공급자는 사용할 수 없습니다.By default, the Key Vault configuration provider is disabled. 애플리케이션을 로컬로 실행하는 데 필요하지 않습니다.It's not needed for running the application locally. 프로덕션 배포에 사용하도록 허용합니다.You would enable it in a production deployment.
시작 시 애플리케이션은 모든 등록된 구성 공급자에서 설정을 읽고 이를 사용하여 강력한 형식의 옵션 개체를 채웁니다.At startup, the application reads settings from every registered configuration provider, and uses them to populate a strongly typed options object. 자세한 내용은 옵션 및 구성 개체 사용을 참조하세요.For more information, see Using Options and configuration objects.

샘플 코드