2019年9月25日 星期三

使用 MSAL.NET 的 Resource Owner Password Credential ROPC 架構來取得存取權杖但是無須透過瀏覽器來進行身分驗證

使用 MSAL.NET 的 Resource Owner Password Credential ROPC 架構來取得存取權杖但是無須透過瀏覽器來進行身分驗證

更多資訊請點選 : Microsoft 驗證程式庫 (MSAL) 概觀

當想要設計一個系統服務或者要在如 IoT 的環境下,設計一個身分驗證服務,並且接著可以呼叫 Microsoft Graph 所提供的相關 API,將會遇到這樣的問題,因為當要為某個使用者進行身分驗證並且取得 Access Token 存取權杖的時候,因為當時的環境沒有或者無法使用瀏覽器,因此,將會變成不可行;那麼,究竟要如何來解決此一問題呢?這裡的需求那就是想要設計一個程式,他是在背景執行,可以幫使用者,代表該使用者來存取 Office 365 中的相關服務,例如:可以存取行事曆、可以存取信箱內的郵件、或者可以代表某個使用者來發送電子郵件。
想要能夠解決這樣的問題,在這篇文章中,將會使用 Microsoft identity platform 也就是 Microsoft 身分識別平台 (前身為適用於開發人員的 Azure Active Directory) 來進行取得所需要的 Access Token,在官方網站的定義說明為: Microsoft 身分識別平台是 Azure Active Directory (Azure AD) 開發人員平台的演化。 它可讓開發人員建置應用程式以登入所有 Microsoft 身分識別,並取得權杖以呼叫 Microsoft Graph 等 Microsoft API,或開發人員所建置的 API。若對於這個平台所提供的技術還不太孰悉的話,可以參考 Microsoft identity platform 這裡的相關說明文件內容。
大部分的時候,在要進行身分驗證過程,將會使用 OAuth 2.0 和 OpenID Connect 的方式,此時,將會需要透過瀏覽器的幫忙,由使用者輸入其身分憑證,也就是帳號與密碼。
此時,開發者將會需要知道 Microsoft 身分識別平臺支援資源擁有者密碼認證 ROPC 這個技術,授與 讓應用程式可以直接處理其密碼來登入使用者。 ROPC 流程需要高程度的信任和使用者暴露, 而且您應該只在有其他、更安全的流程無法使用時, 才使用此流程。
為了要解決之前提到的需求,將會分成兩篇文章,在這裡,第一篇文章中將會先來說明如何透過使用者的帳號與密碼,就背景取得 Microsoft Graph 的 Access Token,在下一篇文章中 使用 Microsoft.Graph 來存取 Office 365 行事曆的功能,將會說明如何透過這裡取得的 Access Token 來呼叫 Office 365 的相關功能。
在這篇文章所提到的專案原始碼,可以從 GitHub 下載

建立測試專案

在這篇文章中,將會使用一個 Console 主控台應用程式類型的專案,來進行設計這樣的需求,也就是說,在無需與使用者互動,或者當時電腦環境沒有瀏覽器軟體的環境下,可以透過使用者帳號與密碼的方式,取得存取權杖 Access Token。因此,請先建立一個名為 ROPCAuthentication 的 Console 專案,在這裡使用的是 .NET Framework 開發框架。

安裝 MSAL.NET NuGet 套件

接著要來安裝 Microsoft.Identity.Client 套件,如此,開發者無須涉入複雜與繁瑣的 HTTP 通訊協定,就可以使用 MSAL.NET 這個套件所提供的 API,就可以獲得通過身分驗證之後的存取權杖。
  • 滑鼠右擊此專案
  • 選擇 [管理 NuGet 套件] 選項
  • 點選瀏覽標籤頁次
  • 在搜尋文字輸入盒內輸入這個文字 Microsoft.Identity.Client
  • 點選 安裝 按鈕

建立 Azure Active Directory 的應用程式

現在使用的 Microsoft 身分識別平台 (v2.0),想要使用這樣的服務,就需要 使用 Microsoft 身分識別平台來註冊應用程式,因此,請準備好一個 Office 365 的帳號與密碼,準備來建立此應用程式。
請使用瀏覽器開啟 App registration 這個網址,這樣將會看到如下圖的畫面,當然,也可以直接進入到 Microsoft Azure 網頁中,在最左邊的功能清單中,找到 [Azure Active Directory] 這個選項並且點選他,也會看到相同的網頁內容。
  • 請點選 [新增註冊] 按鈕連結
  • 此時將會顯示 [註冊應用程式] 畫面
  • 請在名稱欄位輸入: 我的自動帳密登入
    該名稱為這次要設計的應用程式名稱,當然,開發者可以輸入任何可以代表這個應用程式的代表名稱
  • 對於 [支援的帳戶類型]
    請選擇 [僅此組織目錄中的帳戶 (僅 VulcanLab - 單一租用戶)] 這個選項
  • 最後請點選 [註冊] 按鈕
  • 整個操作過程如同底下螢幕截圖所示

設定 Azure Active Directory 的應用程式

現在需要把剛剛建立的應用程式進行相關設定,以滿足當初需求,此時,瀏覽器的畫面應該會顯示到剛剛建立的應用程式設定畫面上,類似下圖的螢幕截圖。
在這裡將會需要點選 [概觀] 、 [驗證] 、 [API 權限] 這三個標籤頁次,分別來進行操作
  • 請點選 [概觀] 標籤頁次
  • 在其右邊將會看到 [應用程式 (用戶端) 識別碼] 與其底下的一串 GUID 代碼
    請將這個代碼妥善儲存到適當的地方,等下寫程式的時候會用到
  • 在其右邊將也會看到 [目錄 (租用戶) 識別碼] 與其底下的一串 GUID 代碼
    請將這個代碼妥善儲存到適當的地方,等下寫程式的時候會用到
  • 請點選 [驗證] 標籤頁次
  • 將右半部畫面內容,使用滑鼠捲動到最下方
  • 此時將會看到 [預設用戶端類型] 設定
  • 請將該設定項目切換成為 [是] 選項
  • 最後點選上方的 [儲存] 按鈕
  • 請點選 [API 權限] 標籤頁次
  • 將會看到如下圖的 API 權限畫面
  • 在右半部下方的有個 [代表 VulcanLab 授予管理員同意] 的按鈕,現在是灰色的,這部分的操作等下會在後便有說明。
  • 請點選右半部上方的 [新增權限] 按鈕
  • 當 [要求 API 權限] 畫面出現之後
  • 請使用滑鼠稍微往下捲動,直到看到 Microsoft Graph 選項出現,如下面截圖
  • 請點選 [Microsoft Graph] 這個項目
  • 當出現了 委派的權限 (您的應用程式必須是登入的使用者身分,才能存取 API。) 與 應用程式權限 (您的應用程式正以背景服務或精靈執行,而且不是登入的使用者身分。) 兩個選項
  • 請點選 [委派的權限]
  • 現在將會出現所有 Microsoft Graph API 可以選用的權限清單
  • 請在 [選取權限] 下方的文字輸入盒輸入: Calendar
  • 如此,將會看到與 Office 365 行事曆相關的 API 出現
  • 在此需要點選 [Calendars.ReadWrite] 這個選項
  • 最後,點選 [新增權限] 按鈕
  • 完成這個應用程式的所有設定步驟
不過,現在螢幕畫面的最上方出現了 Permissions have changed, please wait 10 seconds before granting admin consent. Users and/or admins will have to consent even if they have already done so previously. 這段文字,看樣子是剛剛建立與設定的應用程式,需要通過管理者的同意才能夠使用。
為了要解決此一問題,此時需要請具有 Office 365 內擁有 Global Admin 權限的人幫忙處理。請管理者先登入到 Office 365 的系統上,接著請使用瀏覽器開啟 App registration 這個網址
當 Azure 網頁顯示出來之後,請點選左方的 [應用程式註冊] 選項,如此就會在右方看到剛剛建立的應用程式。
請點選這個應用程式項目
接著點選左邊清單選項中的 [API 權限] ,現在將會看到最下方的 [代表 VulcanLab 授與管理者同意] 按鈕不再是灰色的了
請點選 [代表 VulcanLab 授與管理者同意] 這個按鈕
當出現對話窗,顯示這樣內容
要代表 VulcanLab 中的所有帳戶對要求的權限授與同意嗎? 這會更新此應用程式現有的系統管理員同意記錄,以與下列記錄吻合。
請點選 [是] 按鈕
此時,在右半部的上方,將會顯示出 已成功對要求的權限授與系統管理員同意 文字,並且在 API/權限名稱 清單中,將會看到 Calendars.ReadWrite API 權限已經在清單內,最後則是狀態欄位中,看到亮起綠燈,顯示出 已授與 VulcanLab 文字,這表示了這個 API 已經可以使用了。

開始設計獲取 Token 的程式碼

想要透過 Microsoft identity platform 獲取到 Access Token ,使這個應用程式可以呼叫 Microsoft Graph 相關 API,在這裡將會是要存取指定使用者的行事曆,並且無須透過瀏覽器網頁來進行身分驗證,可以透過底下的程式碼來做到。
在程式一開始,需要先來定義四個參數,那分別是
  • clientId : 請將建立這個應用程式時候,所看到的 [應用程式 (用戶端) 識別碼] 內容填入到這個變數
  • authority : 請將建立這個應用程式時候,所看到的 [目錄 (租用戶) 識別碼] 內容填入到這個變數
  • account : Office 365 使用者的電子郵件信箱
  • password : Office 365 使用者的密碼
設定完成之後,就可以使用 PublicClientApplicationBuilder.Create 方法來產生出一個 IPublicClientApplication 的執行個體 app;接下來,需要將所提供的密碼,使用 SecureString 以加密的方式儲存,最後將會使用 app.AcquireTokenByUsernamePassword 方法,採用將使用者帳號與密碼的資訊方式,進行獲取存取權杖的動作。
若成功獲得的存取權杖,將會回傳一個非空值的 AuthenticationResult result 物件,而該物件的 Account.Username 就是這個登入驗證成功的使用者電子郵件信箱,而 AccessToken 屬性將會是我們想要取得的存取權杖。
該存取權杖是一個 JWT 的 Token,透過 jwt.io 網站來解碼解析出這個 Token,將會看到類似底下的內容。
{
  "aud": "00000003-0000-0000-c000-000000000000",
  "iss": "https://sts.windows.net/0e...........b/",
  "iat": 1568871825,
  "nbf": 1568871825,
  "exp": 1568875725,
  "acct": 0,
  "acr": "1",
  "aio": "42FgYFin+P+gdmXXvoKbcdF7J839FvaA673cbVvu5TKlYhsNJRsA",
  "amr": [
    "pwd"
  ],
  "app_displayname": "我的自動帳密登入",
  "appid": "32b..........a7",
  "appidacr": "0",
  "family_name": "account",
  "ipaddr": "1.......1",
  "name": "account",
  "oid": "222381e8-d70a-4918-971a-2ece91be672e",
  "platf": "3",
  "puid": "100320006E98B137",
  "scp": "Calendars.ReadWrite User.Read profile openid email",
  "sub": "YRgQR-gtHrtY_vGaxA_LzC_O5n8Dfs_YvEJgCSMiMAo",
  "tid": "0e..........db",
  "unique_name": "account@MyVulcan.onmicrosoft.com",
  "upn": "account@MyVulcan.onmicrosoft.com",
  "uti": "emLgFi0oVUGaQ6LmjXdXAA",
  "ver": "1.0",
  "xms_st": {
    "sub": "MD3a8TYO3PvHerAmzQyLxoMBZ5hcAJWB80pNUSUueuo"
  },
  "xms_tcdt": 1568696878
}
這是這篇文章使用到的程式碼
C Sharp / C#
class Program
{
    static string clientId = "應用程式 (用戶端) 識別碼";
    static string authority = "目錄 (租用戶) 識別碼";
    static string account = "Office 365 使用者的電子郵件信箱";
    static string password = "Office 365 使用者的密碼";

    static void Main(string[] args)
    {
        GetMicrosoftGraphAccessTokeyAsync().Wait();
    }

    static async Task GetMicrosoftGraphAccessTokeyAsync()
    {
        string[] scopes = new string[] { "user.read" };
        IPublicClientApplication app;
        app = PublicClientApplicationBuilder.Create(clientId)
              .WithAuthority($"https://login.microsoftonline.com/{authority}")
              .Build();

        AuthenticationResult result = null;

        try
        {
            #region 將所提供的密碼,使用 SecureString 以加密的方式儲存
            // SecureString 代表應該將文字保密,例如於不再使用時將它從電腦記憶體刪除。 
            var securePassword = new SecureString();
            foreach (char c in password)     
                securePassword.AppendChar(c);  
            #endregion

            // 使用使用者的帳號與密碼憑證,來獲取存取權杖
            result = await app.AcquireTokenByUsernamePassword(scopes, account, securePassword)
                               .ExecuteAsync();
        }
        catch (MsalException ex)
        {
            Console.WriteLine(ex.Message);
        }

        Console.WriteLine(result.Account.Username);
        Console.WriteLine($"Access Token : {result.AccessToken}");
        foreach (var item in result.Scopes)
        {
            Console.WriteLine($"Scope :{item}");
        }
    }
}

更多資訊請點選 : Microsoft 驗證程式庫 (MSAL) 概觀


沒有留言:

張貼留言