연습: 다중 테넌트 서버 간 인증 사용
게시 날짜: 2017년 1월
적용 대상: Dynamics 365 (online)
이 연습에서는 Microsoft Visual Studio 2015 MVC 웹 응용 프로그램 템플릿을 사용하여 Microsoft Dynamics 365용 2016년 12월 업데이트(온라인)에 연결할 수 있는 다중 테넌트 웹 응용 프로그램을 만드는 단계를 설명합니다.
요구 사항
웹 개발자 도구가 설치된 Visual Studio 2015
Azure Active Directory(Azure AD) 테넌트와 연결된 Microsoft Dynamics 365용 2016년 12월 업데이트(온라인) 테넌트.
다른 Azure AD 테넌트와 연결된 두 번째 Microsoft Dynamics 365용 2016년 12월 업데이트(온라인) 테넌트. 이 테넌트는 응용 프로그램의 구독자를 나타냅니다. 평가판 Microsoft Dynamics 365용 2016년 12월 업데이트(온라인) 구독일 수 있습니다.
이 연습의 목표
이 연습을 완료하면 Dynamics 365(온라인) 테넌트에 연결하기 위해 응용 프로그램을 사용하는 사용자에 대한 데이터를 가져오기 위해 WhoAmIRequest Class를 사용하는 MVC 웹 응용 프로그램을 만들 수 있습니다.
응용 프로그램을 성공적으로 실행하면 오른쪽 상단 모서리에 있는 로그인 명령이 표시됩니다.
로그인 명령을 클릭하면 자격 증명을 위해 Azure AD에 연결됩니다.
로그인 한 후에 WhoAmI 명령이 표시됩니다.
WhoAmI를 클릭하면 다음이 표시됩니다.
Dynamics 365 테넌트를 쿼리할 때 WhoAmI 메시지가 반환하는 결과가 현재 사용하고 있는 사용자 계정이 아닌 웹 응용 프로그램에 대해 귀하가 구성한 특정 응용 프로그램 사용자 계정을 참조하는 것을 볼 수 있습니다.
Azure AD 테넌트 확인
시작하기 전에 Office 365 관리 센터https://portal.office.com으로 연결하여 관리 센터 드롭다운에서 Dynamics 365와 Azure AD가 모두 표시되는지 확인하십시오.
Azure AD 구독이 Dynamics 365 구독에 연결되어 있지 않은 경우 귀하의 응용 프로그램에 Dynamics 365 데이터 액세스 권한을 부여할 수 없습니다.
이 옵션이 표시되지 않는 경우 Azure AD 구독을 얻기 위해 등록하는 방법에 대한 내용은 무료 Azure Active Directory 구독 등록을 참조하십시오.
이미 Azure 구독이 있지만 Microsoft Office 365 계정과 연결되지 않은 경우 Office 365 계정에 Azure AD 연결을 참조하여 응용 프로그램을 만들고 관리하십시오.
MVC 웹 응용 프로그램 만들기
Visual Studio 2015를 사용하여 새 MVC 웹 응용 프로그램을 만들고 Azure AD 테넌트로 등록할 수 있습니다.
Visual Studio 2015를 엽니다.
로그인한 Microsoft 계정가 응용 프로그램을 등록하는 데 사용하고자 하는 Azure AD 테넌트 액세스 ID와 동일한 것인지 확인하십시오.
새 프로젝트 를 클릭하고 .NET Framework 4.6.1 및 ASP.NET 웹 응용 프로그램 템플릿을 선택합니다.
확인을 클릭하고 새 ASP.NET 프로젝트 대화 상자에서 MVC를 선택합니다.
인증 변경 단추를 클릭하고 대화 상자에서 직장 및 학교 계정을 선택합니다.
드롭다운에서 클라우드 - 여러 조직을 선택합니다.
확인을 클릭하고 프로젝트 초기화를 완료합니다.
참고
Visual Studio 프로젝트를 이런 식으로 만들면 Azure AD 테넌트로 응용 프로그램을 등록하고 Web.Config appSettings에 다음 키를 추가합니다.
<add key="ida:ClientId" value="baee6b74-3c39-4c04-bfa5-4414f3dd1c26" /> <add key="ida:AADInstance" value="https://login.microsoftonline.com/" /> <add key="ida:ClientSecret" value="HyPjzuRCbIl/7VUJ2+vG/+Gia6t1+5y4dvtKAcyztL4=" />
Azure AD에 응용 프로그램 등록
MVC 웹 응용 프로그램 만들기에 나와 있는 단계를 수행한 경우, Visual Studio에서 만든 웹 응용 프로그램이 Azure AD 응용 프로그램에 이미 등록되어 있는지 찾아야 합니다. 그러나 Azure AD 포털 내에서 수행해야 하는 단계가 하나 더 있습니다.
https://portal.azure.com으로 이동하여 Azure Active Directory를 선택합니다.
응용 프로그램 등록을 클릭하고 Visual Studio를 사용하여 만든 응용 프로그램을 찾습니다.일반 영역에서 속성을 확인하십시오.
응용 프로그램 ID 속성이 Web.Config appSettings에 추가된 ClientId 값과 일치하는지 확인합니다.
홈 페이지 URL 값은 Visual Studio 프로젝트의 SSL URL 속성과 일치해야 하며 localhost URL(https://localhost:44392/)로 리디렉션되어야 합니다.
참고
나중에 실제로 응용 프로그램을 게시할 때 이를 변경할 필요가 있습니다. 그렇지만 디버깅에 대해 올바른 localhost 값을 위해 이렇게 설정해야 합니다.
응용 프로그램에 Dynamics 365 데이터 액세스 권한을 부여해야 합니다.API 액세스 영역에서 필요한 권한을 클릭합니다. Windows Azure Active Directory에 대한 사용 권한을 이미 갖고 있는지 표시됩니다.
추가를 클릭하고 API 선택합니다. 목록에서 Dynamics 365를 선택한 후 선택 단추를 클릭합니다.
권한 선택에서 조직 사용자로 Dynamics 365 액세스를 선택합니다. 그런 다음 선택 단추를 클릭합니다.
완료를 클릭하여 이러한 권한을 추가합니다. 완료되면 적용된 권한이 표시됩니다.
API 액세스 영역에서 키 값이 추가된 것을 확인합니다. 응용 프로그램을 만든 후에 키 값은 Azure 포털에서 보이지 않지만 이 값은 Web.Config appSettings에 ClientSecret로 추가됩니다.
응용 프로그램 사용자 만들기
수동으로 Dynamics 365 응용 프로그램 사용자 만들기에 나와 있는 단계를 따라 Web.Config의 ClientId 값과 동일한 응용 프로그램 등록의 응용 프로그램 ID 값의 응용 프로그램 사용자를 만들 수 있습니다.
어셈블리 추가
다음 NuGet 패키지를 프로젝트에 추가
패키지 |
버전 |
---|---|
Microsoft.CrmSdk.CoreAssemblies |
최신 버전 |
Microsoft.IdentityModel.Clients.ActiveDirectory |
2.22.302111727 |
Microsoft.IdentityModel.Tokens |
5.0.0 |
Microsoft.Azure.ActiveDirectory.GraphClient |
2.1.0 |
참고
Microsoft.IdentityModel.Clients.ActiveDirectory 어셈블리를 최신 버전으로 업데이트하지 마십시오. 이러한 어셈블리의 버전 3.x에서는 Microsoft.CrmSdk.CoreAssemblies가 나타나는 인터페이스기 변경되었습니다.
NuGet 패키지 관리에 대한 내용은 NuGet 설명서: UI를 사용하여 NuGet 패키지 관리를 참조하십시오.
MVC 템플릿에 코드 변경 내용 적용
다음 코드 변경은 Dynamics 365WhoAmI 메시지를 사용하여 응용 프로그램 사용자 계정 ID가 응용 프로그램에서 사용되고 있는지 확인하는 기본 기능을 제공합니다.
Web.config
appSettings에 다음 키를 추가합니다.
<add key="ida:OrganizationHostName" value="https://{0}.crm.dynamics.com" />
ida:OrganizationHostName 문자열은 구독자의 Dynamics 365 온라인 조직 이름을 자리 표시자에 추가하여 올바른 서비스에 액세스할 수 있도록 합니다.
<add key="owin:appStartup" value="<your app namespace>.Startup" />
owin:appStartup 문자열은 이 프로젝트에서 OWIN 미들웨어가 Startup 클래스를 사용하는 것을 보장합니다. 그렇지 않으면 다음 오류 메시지가 나타납니다.
- No assembly found containing an OwinStartupAttribute.
- No assembly found containing a Startup or [AssemblyName].Startup class.
자세한 정보: ASP.NET: OWIN 시작 클래스 검색
Controllers/HomeController.cs
AllowAnonymous 데코레이터를 Index 작업에 추가합니다. 이렇게 하면 인증하지 않고 기본 페이지에 액세스할 수 있습니다.
using System.Web.Mvc;
namespace SampleApp.Controllers
{
[Authorize]
public class HomeController : Controller
{
[AllowAnonymous]
public ActionResult Index()
{
return View();
}
public ActionResult About()
{
ViewBag.Message = "Your application description page.";
return View();
}
public ActionResult Contact()
{
ViewBag.Message = "Your contact page.";
return View();
}
}
}
참고
웹 응용 프로그램 또는 서비스에는 익명 액세스를 허용하지 않는 것이 좋습니다. 간단하게 익명 액세스가 사용됩니다. 응용 프로그램에 대한 액세스 제어는 이 연습에서 다루는 내용이 아닙니다.
Views/Shared/_Layout.cshtml
인증된 사용자에 대해 명령 링크 WhoAmI를 표시하려면 이 파일을 편집해야 합니다.
navbar-collapse collapse 클래스를 사용하는 div 요소를 찾아 아래 코드를 포함하도록 편집합니다.
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li>@Html.ActionLink("Home", "Index", "Home")</li>
<li>@Html.ActionLink("About", "About", "Home")</li>
<li>@Html.ActionLink("Contact", "Contact", "Home")</li>
@if (Request.IsAuthenticated)
{
<li>@Html.ActionLink("WhoAmI", "Index", "CrmSdk")</li>
}
</ul>
@Html.Partial("_LoginPartial")
</div>
App_Start/Startup.Auth.cs
다음과 같이 변경하면 응용 프로그램에 새 테넌트가 로그인할 때 승인 프레임워크를 호출합니다.
public partial class Startup
{
private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
private string appKey = ConfigurationManager.AppSettings["ida:ClientSecret"];
//Not used
//private string graphResourceID = "https://graph.windows.net";
private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
private string authority = aadInstance + "common";
private ApplicationDbContext db = new ApplicationDbContext();
//Added
private string OrganizationHostName = ConfigurationManager.AppSettings["ida:OrganizationHostName"];
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions { });
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
{
/*
instead of using the default validation
(validating against a single issuer value, as we do in line of business apps),
we inject our own multitenant validation logic
*/
ValidateIssuer = false,
},
Notifications = new OpenIdConnectAuthenticationNotifications()
{
SecurityTokenValidated = (context) =>
{
return Task.FromResult(0);
},
AuthorizationCodeReceived = (context) =>
{
var code = context.Code;
ClientCredential credential = new ClientCredential(clientId, appKey);
string tenantID = context
.AuthenticationTicket
.Identity
.FindFirst("https://schemas.microsoft.com/identity/claims/tenantid")
.Value;
/* Not used
string signedInUserID = context
.AuthenticationTicket
.Identity
.FindFirst(ClaimTypes.NameIdentifier)
.Value;
*/
//Added
var resource = string.Format(OrganizationHostName, '*');
//Added
Uri returnUri = new Uri(
HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)
);
/* Changed below
AuthenticationContext authContext =
new AuthenticationContext(
aadInstance + tenantID,
new ADALTokenCache(signedInUserID)
);
*/
//Changed version
AuthenticationContext authContext =
new AuthenticationContext(aadInstance + tenantID);
/* Changed below
AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(
code,
new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)),
credential,
graphResourceID);
*/
//Changed version
AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(
code,
new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)),
credential,
resource);
return Task.FromResult(0);
},
AuthenticationFailed = (context) =>
{
context.OwinContext.Response.Redirect("/Home/Error");
context.HandleResponse(); // Suppress the exception
return Task.FromResult(0);
}
}
});
}
}
Controllers/CrmSdkController 추가
Controllers 폴더에 다음 CrmSdkController.cs를 추가합니다. 이 코드는 WhoAmI 메시지를 실행합니다.
Controllers 폴더를 마우스 오른쪽 단추로 클릭하고 추가 > 컨트롤러...를 선택합니다.
스캐폴드 추가 대화에서 MVC5 컨트롤러 - 비어 있음을 선택합니다.
추가를 클릭합니다.
응용 프로그램의 네임스페이스로 <Your app namespace>를 대체하는 다음 코드를 붙여 넣습니다.
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.WebServiceClient;
using System; using System.Configuration;
using System.Linq;
using System.Security.Claims;
using System.Web.Mvc;
namespace <Your app namespace>
{
[Authorize]
public class CrmSdkController : Controller
{
private string clientId =
ConfigurationManager.AppSettings["ida:ClientId"];
private string authority =
ConfigurationManager.AppSettings["ida:AADInstance"] + "common";
private string aadInstance =
ConfigurationManager.AppSettings["ida:AADInstance"];
private string OrganizationHostName =
ConfigurationManager.AppSettings["ida:OrganizationHostName"];
private string appKey =
ConfigurationManager.AppSettings["ida:ClientSecret"];
// GET: CrmSdk
public ActionResult Index()
{
string tenantID = ClaimsPrincipal
.Current
.FindFirst("https://schemas.microsoft.com/identity/claims/tenantid")
.Value;
// Clean organization name from user logged
string organizationName = User.Identity.Name.Substring(
User.Identity.Name.IndexOf('@') + 1,
User.Identity.Name.IndexOf('.') - (User.Identity.Name.IndexOf('@') + 1)
);
//string crmResourceId = "https://[orgname].crm.microsoftonline.com";
var resource = string.Format(OrganizationHostName, organizationName);
// Request a token using application credentials
ClientCredential clientcred = new ClientCredential(clientId, appKey);
AuthenticationContext authenticationContext =
new AuthenticationContext(aadInstance + tenantID);
AuthenticationResult authenticationResult =
authenticationContext.AcquireToken(resource, clientcred);
var requestedToken = authenticationResult.AccessToken;
// Invoke SDK using using the requested token
using (var sdkService =
new OrganizationWebProxyClient(
GetServiceUrl(organizationName), false)
)
{
sdkService.HeaderToken = requestedToken;
OrganizationRequest request = new OrganizationRequest() {
RequestName = "WhoAmI"
};
OrganizationResponse response = sdkService.Execute(request);
return View((object)string.Join(",", response.Results.ToList()));
}
}
private Uri GetServiceUrl(string organizationName)
{
var organizationUrl = new Uri(
string.Format(OrganizationHostName, organizationName)
);
return new Uri(
organizationUrl +
@"/xrmservices/2011/organization.svc/web?SdkClientVersion=8.2"
);
}
}
}
Views/CrmSdk
Index라는 새 보기를 추가합니다.
CrmSdk 폴더를 마우스 오른쪽 단추로 클릭하고 추가 > 보기...를 선택합니다.
보기 추가 대화 상자에서 다음 값을 설정합니다.
추가를 클릭합니다.
생성된 코드를 다음으로 바꿉니다.
@model string @{ ViewBag.Title = "SDK Connect"; } <h2>@ViewBag.Title.</h2> <p>Connected and executed sdk command WhoAmI.</p> <p>Value: @Model</p>
응용 프로그램 디버깅
F5를 눌러 응용 프로그램을 디버깅했을 때 SSL을 사용하는 localhost에 대한 액세스 인증서를 신뢰할 수 없는 오류가 발생할 수 있습니다 다음은 Visual Studio 및 IIS Express와 관련하여 이러한 문제를 해결하기 위한 몇 가지 링크입니다.
참고
이 단계에서 Azure AD 테넌트와 연결된 Microsoft 계정와 이에 연결된 Dynamics 365 테넌트를 사용할 수도 있습니다. 다중 테넌트 시나리오는 실제로 시연하지 않습니다. 이 내용은 다음 단계에서 다룰 것입니다. 현재 단계는 추가적으로 복잡한 실제 다중 테넌트 기능 테스트를 소개하기 전에 코드가 작동하는지 확인하는 단계입니다.
이 연습의 목표에 설명된 단계를 참조하여 응용 프로그램을 테스트하십시오.
이 시점에서 응용 프로그램 사용자 계정이 사용되었는지 확인할 수 있습니다. 이 내용을 간편하게 확인하려면 Dynamics 365 웹 API를 사용합니다. 별도의 탭 또는 창에 다음 URL을 입력하여 응용 프로그램에서 UserId 값을 대체합니다.
[Organization URI]/api/data/v8.2/systemusers(<UserId value>)?$select=fullname
JSON 응답은 다음과 같습니다.fullname 값은 응용 프로그램에 로그인하는 데 사용된 Dynamics 365 사용자가 아닌 응용 프로그램 사용자 만들기 단계에서 만든 응용 프로그램 사용자에 대한 것입니다.
{
"@odata.context": "[Organization Uri]/api/data/v8.2/$metadata#systemusers(fullname)/$entity",
"@odata.etag": "W/\"603849\"",
"fullname": "S2S User",
"systemuserid": "31914b34-be8d-e611-80d8-00155d892ddc",
"ownerid": "31914b34-be8d-e611-80d8-00155d892ddc"
}
테스트 구독자 구성
응용 프로그램이 동작하는지 확인했으므로 이제 다른 Dynamics 365(온라인) 테넌트에 대한 연결을 테스트할 차례입니다. 다른 Dynamics 365(온라인) 조직을 사용하여 다음 단계를 수행해야 합니다.
테넌트 구독 동의
동의를 부여하려면 Azure AD 관리자로 로그인하는 동안 다음 단계를 수행합니다.
응용 프로그램을 디버깅하는 동안 별도 InPrivate 또는 시크릿 창을 엽니다.
창의 주소 필드에 응용 프로그램 URL(예: https://localhost:44392/)을 입력합니다.
로그인 단추를 클릭하면 권한 승인에 동의하라는 메시지가 나타납니다.
권한 승인에 동의한 후 해당 응용 프로그램에 돌아와도 아직 사용할 수 없습니다. 이 시점에서 WhoAmI를 클릭하면 다음과 같은 예외를 기대할 수 있습니다.
System.ServiceModel.Security.MessageSecurityException
HResult=-2146233087
Message=The HTTP request is unauthorized with client authentication scheme 'Anonymous'. The authentication header received from the server was 'Bearer authorization_uri=https://login.windows.net/4baaeaaf-2771-4583-99eb-7c7e39aa1e74/oauth2/authorize, resource_id=https://<org name>.crm.dynamics.com/'.
InnerException.Message =The remote server returned an error: (401) Unauthorized.
동의를 부여함으로써 Azure AD 테넌트의 응용 프로그램이 구독자의 활성 디렉터리 테넌트의 응용 프로그램에 추가됩니다.
구독자 테넌트에서 사용자 지정 보안 역할 만들기
만들어야 할 응용 프로그램 사용자는 자신의 권한을 정의하는 사용자 지정 보안 역할과 연결되어야 합니다. 이 수동 테스트 단계를 수행하려면 먼저 사용자 지정 보안 역할을 수동으로 만들어야 합니다.추가 정보:TechNet: 보안 역할 만들기 또는 편집
참고
응용 프로그램 사용자는 기본 Dynamics 365 보안 역할 중 하나와 연결할 수 없습니다. 사용자 지정 보안 역할을 만들어 응용 프로그램 사용자와 연결해야 합니다.
구독자 응용 프로그램 사용자 만들기
이 연습의 목적을 위해 응용 프로그램 사용자를 수동으로 만들어 다른 테넌트에서의 연결을 확인합니다. 실제 구독자에게 배포할 때 이 과정을 자동화하고자 할 수 있습니다.추가 정보:응용 프로그램 사용자를 배포하는 방법 준비
응용 프로그램 사용자 만들기에서 개발 조직에 사용된 동일한 값을 사용하여 수동으로 응용 프로그램 사용자를 만듭니다. 다만 먼저 동의를 부여하는 단계를 완료해야 하는 점이 다릅니다. 사용자를 저장하면 응용 프로그램 ID URI 및 Azure AD 개체 ID 값이 설정됩니다. 먼저 동의를 부여하지 않은 경우 사용자를 저장할 수 없습니다.
마지막으로 이전 단계에서 만든 사용자 지정 보안 역할에 응용 프로그램 사용자를 연결합니다.
구독자 연결 테스트
응용 프로그램 디버깅의 단계를 반복합니다. 다만 여기에서는 다른 Dynamics 365 테넌트에서 사용자 자격 증명을 사용합니다.
참고 항목
다중 테넌트 서버 간 인증 사용
단일 테넌트 서버 간 인증 사용
서버 간(S2S) 인증을 사용하여 웹 응용 프로그램 구축
Microsoft Dynamics 365에 연결
Microsoft Dynamics 365
© 2017 Microsoft. All rights reserved. 저작권 정보