跳轉到

設定自訂登入驗證

一、何謂自訂登入驗證?

自訂登入驗證就是將登入的 權柄 交給足夠信任的外部系統,藉由兩個系統之間的串接,完成登入的需求。

Image account-tpad-flow

登入流程

自訂登入具備以下功能與優點:

  1. 單一登入
    使用自訂登入,用戶可以使用一組認證資料(如使用者名稱和密碼、多因素驗證等)進行 UOF X 登入。這提供了便利的用戶體驗,無需為 UOF X 輸入不同的帳號和密碼。
  2. 可支援多因素驗證
    登入的 權柄 已經交給外部系統,如果外部系統支援多因素驗證,則可提供額外的安全層,以防止未經授權的訪問。透過結合使用者名稱和密碼以及額外的驗證因素(如簡訊驗證碼、智慧卡等),可以增強帳號的安全性。

自訂登入驗證需要提供 登入頁Callback API 兩個功能,因此部份功能需要程式碼的撰寫才能完成。 您可以透過連結下載範例程式,此範例為 MVC 架構,使用的程式語言為 C#。

範例程式下載: https://github.com/

二、整合自訂登入驗證的優點

  1. 單一身份管理
    整合UOF X與自訂登入驗證後,組織可以使用一個系統作為統一的身份管理平台。用戶只需一組帳號和密碼,即可訪問UOF X系統以及其他整合的應用程式,簡化了用戶的身份管理和登入流程。

  2. 更多彈性的登入方式
    自訂登入驗證將登入 權柄 外放後,組織即可實作任何想要的登入方式,不管是多因素驗證或未來更安全有效的登入方式,都不再受限於 UOF X 是否支援的問題。

三、製作登入頁

首先,我們需要一個用於進行登入驗證的網頁,在範例中提供一個簡易的畫面如下,這是很常見的登入功能,使用者輸入帳號密碼後,按下登入按鈕進行驗證。

Image account-tpad-login

登入頁

登入畫面 sample 位在範例中的 Views > Home > Index.cshtml

假設此登入頁網址為 https://mysystem.com.tw ,當從 UOF X 開啟 dialog 進入此登入頁時,會透過 query-string 傳遞 info 參數,這時網址實際為 https://mysystem.com.tw/uofx/login?info=xxxxxxxxxxxxx,info 參數帶有重要的資訊,不應該做任何修改,其結構如下:

{
  "DomainId": "xxx",
  "Culture": "zH-TW", 
  "PathCode": "ede"   
}

行動裝置的考量

使用者也可以透過 UOF X APP 進行自訂登入驗證,因此在登入畫面的設計上,請考量行動裝置的螢幕大小,以及操作的便利性。

1. 開啟頁面後保留 query-string info 資訊

在範例中 (Controllers > HomeController.cs),當透過 dialog 開啟登入頁後,會先將 info 資訊另行保存,待後續登入成功後須回傳此資訊。

 [HttpGet("uofx/login")]
 public IActionResult Login()
 {
     // 從網址中接收 info 參數
    if (!Request.Query.TryGetValue("info", out var info))
    {
        // 如果沒有 info 參數,返回錯誤
        ViewBag.ErrorMessage = "info is required";
    }

    // 保存 info model 到 TempData,後續登入成功後須回傳
    TempData["InfoModel"] = info.ToString();

    return View("index");
 }

2. 登入成功,產生 access-token

當使用者按下登入按鈕後,假如帳密驗證成功,這時需要產生一組 access-token 供後續流程使用,token 類型與內容沒有限制,但應該包含下列資訊與規範:

  • 需含能代表登入帳號的資訊: 例如範例中把帳號辨識碼 (sid) 放入 token
  • 要有時效性: 無時效性的 token 容易被拿來進行資安攻擊
public ActionResult Login(string username, string password)
{
    //驗證帳號密碼
    ... 

    // 產生要登入的一次性帳號識別碼並放入 Token 中, callback 時用來識別身分 
    // (請依自己需求調整,但不建議把真實帳號放入 Token 中,因為 token 內容是公開的)
    var sid = GetSid(username);

    // 產生短時效 Token (可改成使用自己製作的 token)
    var accessToken = TokenHelper.GenToken(sid, DateTimeOffset.Now.AddMinutes(5));

    ...
}

Note

範例中的 JWT Token 內容是公開的,只要拿到 token 的人都可以輕易取得其內容,因此請勿放敏感資料在其中,不過 token 含有 簽章 來避免被串改,所以不用擔心 token 的公開特性。

3. 返回 UOF X

要完成登入除了 access-token 外,還需要登入時 query-string 的 info 參數,可以透過 JsonSerializer.Deserialize<InfoModel> 轉換為 InfoModel:

//驗證帳號密碼
... 

//產生 access-token
...

//取得 info model
var modelJson = TempData["InfoModel"] as string;
byte[] bytes = Convert.FromBase64String(modelJson);
var infoString = Encoding.UTF8.GetString(bytes);
var infoModel = JsonSerializer.Deserialize<InfoModel>(infoString, new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    PropertyNameCaseInsensitive = true
});

...
接著建立 PostMessageModel 物件,此物件包含回傳給 UOF X 所需要資訊,接著將物件序列化成 JSON 字串。範例中回傳邏輯實作在 LoginSuccess.cshtml,因此我們將 JSON 字串 導向此頁。

//驗證帳號密碼
... 

//產生 access-token
...

//取得 info model
...

//製作 PostMessage Model
var model = new PostMessageModel
{
    Info = infoModel,
    Token = accessToken,
    Message = "TPAD login success.",
    StatusCode = 200  //必須為 200
};

// 將 model 序列化成 JSON 字串
var result = JsonSerializer.Serialize(model, new JsonSerializerOptions
{
    PropertyNamingPolicy = new LowercaseFirstLetterPolicy(), // 將屬性名稱第一個字母轉為小寫
    WriteIndented = true
});
return View("LoginSuccess", result); //將結果導向 LoginSuccess

LoginSuccess.cshtml 並無 UI ,僅處理回傳給 UOF X 的任務,在此分成兩種情境:

  1. 透過瀏覽器登入
  2. 透過 UOF X APP 登入

如是透過 瀏覽器登入,則使用 Post Message 的方式傳遞資料,須注意最後要自己關閉畫面 window.close()。如果是透過 UOF X APP 登入,因為 APP 特性需透過約定的 Postback Function 來傳遞資料,請在你的系統中同時支援此兩種情境,使用者方能透過兩種管道登入。

<script>

    //這是給 app 使用的 Postback function
    function Postback() {
        var model = @Html.Raw(Model);
        return model;
    }

    //判斷是透過 web 或 app 連線
    if (window.opener) {
        //如果是 web 則使用 postMessage 回傳資料去 UOF X
        var model = @Html.Raw(Model);
        window.opener.postMessage(model, '*');
        window.close();
    }else{
        //如果是 app 則使用約定的 Postback function 回傳資料
        Postback();
    }

</script>

資料傳遞

@Html.Raw(Model) 是 ASP.NET MVC 的 Razor 語法,用於將伺服器端的 Model 物件轉換為原始的 HTML 字串,並插入到 JavaScript 變數 model 中。此作法確保資訊能正確被解析,並避免編碼問題。

四、製作 Callback API

在流程中最後會透過 Callback API 來驗證 access-token 的正確性,並取得真正的使用者資訊加密回傳,此方式有一些好處:

  1. 避免在 post message 暴露太多登入資訊
  2. 二次驗證加強安全性,避免 access-token 被偽造
  3. 透過加密隱藏真正要登入的使用者

Note

登入頁和 Callback API 並不一定要在同一個站台

1. Callback API 規格

我們需要一個支援 web-api 的站台,並提供一個 API 給 UOF X 使用

項目 備註
method GET
URL 自訂

API 回應結果

狀態碼 備註
200 加密字串 成功回應
所有非200 不限 失敗回應

凡是非狀態碼 200 的回應,UOF X 皆會以 callback 異常處理,並在 log 中紀錄狀態碼和 response body。

在範例中 (Controllers > HomeController.cs) 有實作 API ( [HttpGet("uofx/accountkey")]),後續將以此進行說明。

2. 驗證 access-token

access-token 會在呼叫 API 時,透過 query-string 傳送,因此第一步要取得 token 並驗證其正確性

API: GET https://mycallback.com.tw/uofx/accountkey?t=xxxxxx

// 從網址中接收 token 參數 (UOFX 會以 query-string 't' 傳遞過來)
if (!Request.Query.TryGetValue("t", out var accessToken))
    throw new Exception("Token not found");

// 驗證 Token,並從 Token 中取得 sid
if (!TokenHelper.VirtyfyAndGetData(accessToken, out var sid))
    throw new Exception("Invalid Token");

在驗證 token 的過程中,也把之前放在 token 的帳號辨識碼 (sid) 取出

3. 取得使用者資訊

接著藉由帳號辨識碼 (sid) 取得使用者帳號 (accountKey),accountKey 會事先在 UOF X 中使用者身上個別設定。 再來產生要回傳的 model CallbackResponseModel,此 model 有兩個屬性:

屬性 類型 備註
AccountKey string 使用者帳號
Timestamp long 當下的時間戳
// 根據 sid 取得帳號
var accountKey = GetAccountBySid(sid);

// 產生要回傳的 model
var result = new CallbackResponseModel()
{
    AccountKey = accountKey,
    Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds() //務必放當下時間,會依此時間來判斷是否過期
};

時間戳

時間戳是用來確保此 api 的回應內容具有 時效性 ,避免被有心人士以同樣的內容在不同時間進行非法登入。

4. 加密並回傳

在加密之前,我們需要先有一個 HashKeyHashKey 是一把亂數產生的金鑰,您可以自己產生 (長度建議要有 64 字元), 請留意 HashKey 是與 UOF X 系統約定的 專用金鑰,應避免外流或共用。

API 的回應 (response) 內容 (body) 需要先透過 AES 加密,其規格如下:

項目 類型 備註
加密演算法 AES(Advanced Encryption Standard)
填充模式 PKCS7
加密模式 CBC(Cipher Block Chaining)
反饋大小 128 位元

接下來要透過 HashKey 轉換成 AES 加密所需的 KeyIV,轉換方式如下:

  1. Key: 透過 SHA256 hash HashKey 成為 Key
  2. IV: 取 Key 的後 16 個 byte 作為 IV
// 使用 AES 加密 model
byte[] aeskeyBytes = HashHelper.SHA256ToBytes(_HashKey);
var aesKey = aeskeyBytes;   // HashKey 的 SHA256 Hash
var aesIv = aeskeyBytes.Skip(16).ToArray(); // HashKey 的 SHA256 Hash 後 16 bytes
var encodeResult = AesHelper.EncodeData(JsonSerializer.Serialize(result), aesKey, aesIv);
return Ok(encodeResult);

五、UOF X 自訂登入驗證設定

公司管理員 可以新增多組自訂登入驗證,以及是否要啟用此設定;在新增驗證方式的設定頁面,你可以替該組驗證方式指定一個名稱,以區分其他驗證方式,命名可以是描述性的,例如「以 XX 系統帳密登入驗證」等。

💡[管理者首頁>系統管理>系統設定>帳號安全和登入>自訂登入驗證]