分享方式:


逐步解說:多組織用戶共享伺服器對伺服器驗證

 

發佈日期: 2017年1月

適用對象: Dynamics 365 (online)

此逐步解說將說明建立多組織用戶共享 Web 應用程式的步驟,可使用 Microsoft Visual Studio 2015 MVC Web 應用程式範本連接至 Microsoft Dynamics 365 (Online) 的 2016 年 12 月更新 用戶。

需求

  • 已安裝 Web 開發人員工具的 Visual Studio 2015

  • Microsoft Dynamics 365 (Online) 的 2016 年 12 月更新 用戶,與您的 Azure Active Directory (Azure AD) 用戶相關聯。

  • 另一個 Microsoft Dynamics 365 (Online) 的 2016 年 12 月更新 用戶,與不同的 Azure AD 用戶相關聯。 此用戶代表您應用程式的訂閱者。 這可以是試用 Microsoft Dynamics 365 (Online) 的 2016 年 12 月更新 訂閱。

此逐步解說的目標

完成此逐步解說後,您將擁有 MVC Web 應用程式,其使用 WhoAmIRequest Class擷取有關使用者的資料,應用程式會使用該使用者連線至 Dynamics 365 (線上) 用戶。

當您成功執行應用程式時,會看見 [登入] 命令在右上角。

The sign in command in the app

按一下 [登入] 命令,就會將您導向 Azure AD 提供您的認證。

登入之後,您會看到有一個 [WhoAmI] 命令。

The WhoAmI command

按一下 [WhoAmI],您應該會看到下列內容:

Results of a WhoAmI request

當您查詢您的 Dynamics 365 用戶時,會看見從 WhoAmI 訊息傳回的結果,指出您為 Web 應用程式設定要使用的特定應用程式使用者帳戶,而不是您目前使用的使用者帳戶。

驗證 Azure AD 用戶

在您開始之前,連線至 Office 365 系統管理中心https://portal.office.com,然後在 [管理中心] 下拉式清單中,確認 Dynamics 365 和 Azure AD 都存在。

Admin Centers with Azure Active Directory and Dynamics 365

如果您的 Azure AD 訂閱未與 Dynamics 365 訂閱相關聯,您就無法授與權限讓應用程式存取 Dynamics 365 資料。

如果您為看見此選項,請參閱註冊免費的 Azure Active Directory 訂閱,了解如何註冊以取得 Azure AD 訂閱的資訊。

如果您已有 Azure 訂閱,但是它未與 Microsoft Office 365 帳戶相關聯,請參閱將 Office 365 帳戶與 Azure AD 相關聯,以建立和管理應用程式。

建立 MVC Web 應用程式

使用 Visual Studio 2015 可建立新的 MVC Web 應用程式,並透過 Azure AD 用戶將它註冊。

  1. 開啟 [Visual Studio 2015]。

  2. 確定您登入的 Microsoft 帳號 與能夠存取要用來註冊應用程式的 Azure AD 用戶的身分相同。

  3. 按一下 [新增專案] 並選取 [ .NET Framework 4.6.1] 和 [ASP.NET Web 應用程式] 範本。

    按一下 [確定],然後在 [新的 ASP.NET 專案] 對話方塊中選取 [MVC]。

  4. 按一下 [變更驗證] 按鈕,然後在對話方塊中選取 [工作和學校帳戶]。

  5. 在下拉式清單中,選取 [雲端 – 多個組織]。

    ASP.NET MVC Change Authentication Dialog

  6. 按一下 [確定],完成初始化專案。

    備註

    以這種方式建立 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 Web 應用程式中的步驟執行,應該會發現您在 Visual Studio 中建立的 Web 應用程式專案已在 Azure AD 應用程式中註冊。 但是還有一個步驟必須在 Azure AD 入口網站中執行。

  1. 前往 https://portal.azure.com 並選取 [Azure Active Directory]。

  2. 按一下 [應用程式註冊] 並尋找您使用 Visual Studio 建立的應用程式。 在 [一般] 區域中,驗證屬性:

    Application registration data in Azure Active Directory

  3. 確認 [應用程式識別碼] 屬性符合 Web.Config appSettings 中新增的 ClientId 值。

  4. [首頁 URL] 值應符合 Visual Studio 專案中的 SSL URL 屬性,且應導向 localhost URL,也就是 https://localhost:44392/。

    備註

    您稍後將需要變更此項,當您實際發行應用程式時。 但是,您需要將此設為正確的 localhost 值才能進行偵錯。

  5. 您需要授與應用程式存取 Dynamics 365 資料的權限。 在 [API 存取] 區域中,按一下 [必要權限]。 您會看見它已有 Windows Azure Active Directory 的權限。

  6. 按一下 [新增],然後 [選取 API]。 在清單中選取 [Dynamics 365],然後按一下 [選取] 按鈕。

  7. 在 [選取權限] 中,選取 [以組織使用者身分存取 Dynamics 365]。 然後按一下 [選取] 按鈕。

  8. 按一下 [完成],新增這些權限。 完成時,您應該會看到權限已套用:

    Dynamics 365 permissions applied to application in Azure Active Directory

  9. 在 [API 存取] 區域中,確認已新增 [金鑰] 值。 應用程式建立後,[金鑰] 值在 Azure 入口網站中看不見,但此值會新增至您的 Web.Config appSettings 做為 ClientSecret

建立應用程式使用者

使用a77637f4-420a-4686-9084-d0288d9154af#bkmk_ManuallyCreateUser中的步驟建立應用程式使用者,並包含來自應用程式註冊的 [應用程式識別碼] 值,該值也與 Web.Config 中的 ClientId 值相同。

新增組件

將下列 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 訊息並確認應用程式是使用應用程式使用者帳戶身分識別。

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();
        }
    }
}

備註

在您的 Web 應用程式或服務中,通常不會預期您允許匿名存取。 單純起見,這裡使用匿名存取。 此逐步解說並未涵蓋控制應用程式的存取。

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

新增下列 CrmSdkController.cs 至 Controllers 資料夾。 此程式碼會執行 WhoAmI 訊息

  1. 用滑鼠右鍵按一下 [Controllers] 資料夾,並選取 [新增] > [控制器...]

  2. 在 [新增 Scaffold] 對話方塊中選取 [MVC5 控制器 – 空]

  3. 按一下 [新增]

  4. 貼入下列程式碼,將 <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 的新檢視表。

  1. 用滑鼠右鍵按一下 [CrmSdk] 資料夾,並選取 [新增] > [檢視表...]

  2. 在 [新增檢視表] 對話方塊中,設定下列值:

    MVC Add View Dialog

  3. 按一下 [新增]

  4. 將產生的程式碼取代為下列:

    @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 Web 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 管理員身分登入時,請執行下列步驟:

  1. 當您偵錯應用程式時,開啟另一個 InPrivate 或無痕模式視窗。

  2. 在視窗的網址欄位中輸入應用程式的 URL 中,也就是 https://localhost:44392/

  3. 按一下 [登入] 按鈕,就會提示您提供同意。

    Azure Active Directory consent form

在您同意之後,就會返回應用程式,但是還不能使用。 如果您這時按一下 [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 用戶的應用程式將會新增至訂閱者的 Active Directory 用戶的應用程式中。

在訂閱者用戶中建立自訂資訊安全角色

您需要建立的應用程式使用者必須與定義其權限的自訂資訊安全角色相關聯。 對於這個手動測試步驟,您應該先手動建立自訂資訊安全角色。其他資訊:TechNet:建立或編輯資訊安全角色

備註

應用程式使用者無法與其中一個預設的 Dynamics 365 資訊安全角色產生關聯。 您必須建立自訂資訊安全角色,才能與應用程式使用者建立關聯。

建立訂閱者應用程式使用者

基於此逐步解說的用途,我們將手動建立應用程式使用者,以驗證來自不同用戶的連線。 當您部署實際的訂閱者時,您會想要將此程序自動化。其他資訊:a77637f4-420a-4686-9084-d0288d9154af#bkmk_PrepareMethodToDeployAppUser

您手動建立應用程式使用者所使用的值,與用於建立應用程式使用者中開發組織的值相同。 例外狀況是,您必須先完成授與同意的步驟。 當您儲存使用者時,[應用程式識別碼 URI] 和 [Azure AD 物件識別碼] 值將會設定。 如果您未先授與同意,就無法儲存使用者。

最後,將應用程式使用者與您在前一個步驟中新增的自訂資訊安全角色建立關聯。

測試訂閱者連線

重複偵錯應用程式中的步驟,但改用其他 Dynamics 365 用戶的使用者認證。

另請參閱

使用多組織用戶共享伺服器對伺服器驗證
使用單一組織用戶伺服器對伺服器驗證
使用伺服器對伺服器 (S2S) 驗證建立 Web 應用程式
連線至 Microsoft Dynamics 365

Microsoft Dynamics 365

© 2017 Microsoft. 著作權所有,並保留一切權利。 著作權