後端工程師面試考什麼 — 從 SSO (Single Sign-On) 到 SAML 2.0

Posted on  Oct 6, 2024  in  後端面試準備  by  Amo Chen  ‐ 7 min read

談到登入認證時,SSO 和 OAuth 等技術名詞經常出現,這些名詞不僅可能在面試中聊到,它們也常是日常工作的一部分。

本文將從 SSO (Single Sign-On) 開始,深入探討 SAML (Security Assertion Markup Language) 如何實現 SSO,並最後釐清 SSO 與 OAuth 之間的差異。

SSO (Single Sign-On) / 單一登入簡介

為了更好地理解什麼是 SSO (Single Sign-On),我們可以先觀察一下 Gmail 右上角的應用程式列表,該畫面列出各式各樣的應用程式:

apps.png

當我們點選這些應用程式後,會發現不需要再次登入就可以無縫使用它們,甚至有些應用程式屬於完全不同的網域(例如 youtube.com ),我們也不需要執行額外的登入流程。

這種只需在一個服務進行登入驗證(例如輸入使用者名稱、密碼等),就能夠無縫使用其他多個應用程式的技術,就是 SSO (Single Sign-On)。

SSO 技術不僅為整合各種應用程式提供了可能性,還改善了使用者在切換應用程式時的體驗與安全性。因為第三方應用程式無法取得使用者的帳號密碼,這也大大降低了帳號密碼從第三方應用程式洩漏的風險。

p.s. 如果是企業組織的話,帳號管理員也可以集中管理帳號,因此 SSO 也很常用於企業內部環境。

目前知名的 SSO 技術解決方案有 Microsoft Entra 單一登入Okta Identity Cloud 等等。

知道 SSO 的概念之後,就可以進一步了解如何做到 SSO,目前常見可以做到 SSO 的方式有:

  • OpenID Connect
  • OAuth
  • SAML (Security Assertion Markup Language)

其中,SAML 就是我們接下來要介紹的協定。

SAML (Security Assertion Markup Language) 簡介

SAML 是一項 XML 標準,目前 SAML 標準的版本為 2.0 (支援向下相容)。

簡單來說,SAML 是一種 XML 格式,用於在身份提供者(Identity Provider, IdP)和服務提供者(Service Provider, SP)之間傳送身份驗證請求或交換使用者資訊。使用者的帳號和密碼等機敏資料由 IdP 負責保管,而 SP 只能從 IdP 取得經過驗證的身份資訊(例如姓名、電子郵件),而非具體的帳號密碼。

我們同樣舉 Gmail 與 Youtube 兩項服務為例(此處僅為假設,真實運作可能並非本文所述),當我們在 Gmail 點擊使用 Youtube 的時候,Gmail 就扮演身份提供者(IdP)的角色,而 Youtube 則是服務提供者的角色(SP),而 Youtube 與 Gmail 之間溝通的格式使用的則是 SAML 格式。

至於 SAML 這項標準的實際運作過程,相信大家不管查詢 Google 或是問 ChatGPT 都很難馬上得到滿意的答案,以下將盡可能地以圖表並輔助實際範例進行說明。

兩種不同的 SSO 模式

SAML 2.0 標準定義兩種不同的 SSO 模式:

  • IdP-initiated SSO
  • SP-initiated SSO

這兩種模式大同小異,主要是使用者從何處進行資源(resource)存取權限檢查(access check)的差別,

This high-level description indicated that the user had first authenticated at the IdP before accessing a protected resource at the SP. This scenario is commonly referred to as an IdP-initiated web SSO scenario. While IdP-initiated SSO is useful in certain cases, a more common scenario starts with a user visiting an SP site through a browser bookmark, possibly first accessing resources that require no special authentication or authorization. In a SAML-enabled deployment, when they subsequently attempt to access a protected resource at the SP, the SP will send the user to the IdP with an authentication request in order to have the user log in. Thus this scenario is referred to as SP-initiated web SSO. Once logged in, the IdP can produce an assertion that can be used by the SP to validate the user’s access rights to the protected resource. SAML V2.0 supports both the IdP-initiated and SP-initiated flows.

從以下兩張流程圖可以看出 IdP-initiated SSO 與 SP-initiated SSO 的差異。

IdP-initiated SSO

使用者在 IdP 處做完 access check 再到 SP 處存取資源,就稱為 IdP-initiated SSO。

idp-initiated-sso.png

ref

SP-initiated SSO

使用者在 SP 處做完 access check 再到 IdP 處驗證身份後回到 SP 存取資源,就稱為 IdP-initiated SSO。

sp-initiated-sso.png

ref

值得注意的是,在這兩種模式中,推薦使用 SP-initiated SSO。因為 IdP-initiated SSO 有可能遭受 Login CSRF 攻擊,惡意人士可能誘騙使用者在不知情的情況下使用有問題的身份登入應用程式。IdP-initiated SSO 存在 Login CSRF 攻擊風險的根本原因在於,SP 無法確認 SSO 流程是否真正由使用者發起;而 SP-initiated SSO 則可以使用 CSRF token 等方式,確保此一行為確實由使用者發起,從而防禦這類攻擊。

用實際範例理解 SP-initiated SSO 流程

接下來,我們會以實際範例介紹 SP-initiated SSO 的流程。

sp-initiated-sso.png

ref

以下標題的數字對應上圖每個步驟的說明。

1. Access Check

當使用者使用瀏覽器存取 Service Provider 的資源時,Service Provider 會進行相關的檢查,例如 session id 等,確認使用者能否存取資源。如果使用者處於未登入狀態,則會進入第 2 步驟。

2. Redirect With <AuthnRequest>

第 2 步驟會將未登入驗證的使用者以 HTTP 轉址(redirect)的方式轉至 IdP 進行驗證。此時,SP 需要提供 SAML 格式的 XML 給 IdP,讓 IdP 知道它需要幫 SP 驗證該使用者,其 XML 格式範例如下:

<samlp:AuthnRequest
  xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
  xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
  ID="identifier_1"
  Version="2.0"
  IssueInstant="2004-12-05T09:21:59Z"
  AssertionConsumerServiceIndex="1">
  <saml:Issuer>https://sp.example.com/SAML2</saml:Issuer>
  <samlp:NameIDPolicy
    AllowCreate="true"
    Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"/>
</samlp:AuthnRequest>

要將上述 XML 資料以轉址方式傳給 IdP 的方式的話,需要將該 XML 以 DEFLATE 編碼(encoding)後,再使用 Base64 編碼,將值放在 URL 參數 SAMLRequest 中,並且將使用者欲存取的資源放在 URL 參數 RelayState 中。

上述完整的編碼方式可以參考以下 Python 程式碼:

def deflate_and_base64_encode(string_val):
    """
    Deflates and the base64 encodes a string

    :param string_val: The string to deflate and encode
    :return: The deflated and encoded string
    """
    if not isinstance(string_val, bytes):
        string_val = string_val.encode("utf-8")
    return base64.b64encode(zlib.compress(string_val)[2:-4])

舉未驗證的使用者欲存取 SP 的資源 /file/9527 為例,SP 向使用者瀏覽器發出的轉址位置可能會是:

https://idp.example.org/SAML2/SSO/Redirect?SAMLRequest=<編碼過後的 AuthnRequest XML>&RelayState=/file/9527

我們舉實際的 Microsoft Entra 單一登入 為例,下面紅線部分可以看到跟上述 URL 格式規定相同的部分:

microsoft-sso-smal2.png

3. Challenge for credentials & 4. User login

如果使用者在 IdP 處於未登入驗證的狀態,就會看到類似上圖的畫面,提示使用者必須先進行登入驗證。

5. Signed <Response> in HTML form

在這個步驟,Idp 會回應一個 HTML 表單,該表單至少含有會有 2 個隱藏欄位,分別為:

  • SAMLResponse,其值也是 XML 資料經過 DEFLATE 加 Base64 編碼後的結果,內含使用者資料(或簽章),這些使用者資料正是 SP 所需要的資料。
  • RelayState,其值為第 2 步的 RelayState,IdP 不會做任何修改。

HTML 表單範例如下:

<form method="post" action="https://sp.example.com/SAML2/SSO/POST" ...>
<input type="hidden" name="SAMLResponse" value="response" />
<input type="hidden" name="RelayState" value="token" />
...
<input type="submit" value="Submit" />
</form>

p.s. 值得注意的是,該表單使用 POST 方法。

而經過編碼前的 SAMLResponse 範例如下:

<samlp:Response
  xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
  xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
  ID="identifier_2"
  InResponseTo="identifier_1"
  Version="2.0"
  IssueInstant="2004-12-05T09:22:05Z"
  Destination="https://sp.example.com/SAML2/SSO/POST">
  <saml:Issuer>https://idp.example.org/SAML2</saml:Issuer>
  <samlp:Status>
    <samlp:StatusCode
      Value="urn:oasis:names:tc:SAML:2.0:status:Success" />
  </samlp:Status>
  <saml:Assertion
    xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
    ID="identifier_3"
    Version="2.0"
    IssueInstant="2004-12-05T09:22:05Z">
    <saml:Issuer>https://idp.example.org/SAML2</saml:Issuer>
    <!-- a POSTed assertion MUST be signed -->
    <ds:Signature
      xmlns:ds="http://www.w3.org/2000/09/xmldsig#">...</ds:Signature>
    <saml:Subject>
      <saml:NameID
        Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">
        3f7b3dcf-1674-4ecd-92c8-1544f346baf8
      </saml:NameID>
      <saml:SubjectConfirmation
        Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
        <saml:SubjectConfirmationData
          InResponseTo="identifier_1"
          Recipient="https://sp.example.com/SAML2/SSO/POST"
          NotOnOrAfter="2004-12-05T09:27:05Z" />
      </saml:SubjectConfirmation>
    </saml:Subject>
    <saml:Conditions
      NotBefore="2004-12-05T09:17:05Z"
      NotOnOrAfter="2004-12-05T09:27:05Z">
      <saml:AudienceRestriction>
        <saml:Audience>https://sp.example.com/SAML2</saml:Audience>
      </saml:AudienceRestriction>
    </saml:Conditions>
    <saml:AuthnStatement
      AuthnInstant="2004-12-05T09:22:00Z"
      SessionIndex="identifier_3">
      <saml:AuthnContext>
        <saml:AuthnContextClassRef>
          urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
        </saml:AuthnContextClassRef>
      </saml:AuthnContext>
    </saml:AuthnStatement>
  </saml:Assertion>
</samlp:Response>

6. POST signed <Response>

當使用者按下 submit 按鈕時,這些資料就會 POST 到 SP 的 SAML endpoint,SP 會在此端點對 Idp 傳送的 XML 資料進行解析(此端點稱為 Assertion Consumer Service URL,或簡稱 ACS),SP 就是藉由此手段從 IdP 取得使用者資料,而 IdP 也是藉由 POST 表單的方式,將使用者轉回 SP。

以下是本文實際從瀏覽器側錄到的 POST 情況,可以看到 payload 中確實含有 SAMLResponse

saml-response-example.png

解碼(decoding) IdP 傳送的資料,可以參考以下 Python 程式碼,簡單來說是原本編碼的步驟反向進行:

def decode_base64_and_inflate(string):
    """base64 decodes and then inflates according to RFC1951

    :param string: a deflated and encoded string
    :return: the string after decoding and inflating
    """

    return zlib.decompress(base64.b64decode(string), -15)

以上就是 SAML 2.0 的運作流程介紹,IdP-initiated SSO 流程大同小異,故不多加贅述。

番外篇,SAML 開發工具

SAML 開發過程其實會牽扯編碼、解碼、簽章以及驗證等步驟,除錯過程可以藉由以下工具幫忙:

SAML Tools

SAML 2.0 格式

本文專注在解說 SAML 認證(authentication)的運作流程,故對於 SAML 2.0 的 XML 格式較少著墨,如果對 SAML 2.0 的格式有興趣的話,可以詳閱 Security Assertion Markup Language (SAML) V2.0 Technical Overview 的 SAML Architecture 章節。

SSO vs. OAuth

SSO 與 OAuth 都可以做到一組帳號登入多個服務,所以這兩個概念非常容易產生混淆!

實際上,兩者最大的差異在於 OAuth 是透過授權的方式,讓第三方應用程式可以存取你的個人資料或者其他個人資源,所以使用者需要一個明確授權的動作;而 SSO 則是透過 SP 與 IdP 之間的同盟/合作關係,直接讓使用者可以用一組帳號無縫使用各種應用程式,就像本文一開始提到的 Gmail 可以無縫切換使用 Youtube 等服務的體驗(不需要額外授權)。

只要理解這個差異,就不再容易產生混淆。

總結

SAML 常見於企業級軟體產品,或是需要整合多種登入認證解決方案的職缺,例如,這些職缺可能會要求具備 SAML 知識或經驗,像是 Identity & Access Management Engineer、Authentication Engineer 等職位(在台灣,這類工作多半由後端工程師負責)。

儘管面試中較少會直接遇到 SAML 相關問題,但它仍是後端工程師可能接觸的技術之一,值得深入了解。

此外,了解 SSO 與 OAuth 之間的差異也是非常重要的,畢竟容易混淆的技術特別好用來辨別面試者對於相關技術的理解程度。

以上!

Enjoy!

References

什麼是 SAML?| SAML 認證如何運作

SAML 式單一登入 (SSO) 技術總覽 - Cloud Identity說明

Configure SAML Identity Provider-Initiated Single Sign-On

Security Assertion Markup Language (SAML) V2.0 Technical Overview

Security Assertion Markup Language

對抗久坐職業傷害

研究指出每天增加 2 小時坐著的時間,會增加大腸癌、心臟疾病、肺癌的風險,也造成肩頸、腰背疼痛等常見問題。

然而對抗這些問題,卻只需要工作時定期休息跟伸展身體即可!

你想輕鬆改變現狀嗎?試試看我們的 PomodoRoll 番茄鐘吧! PomodoRoll 番茄鐘會根據你所設定的專注時間,定期建議你 1 項辦公族適用的伸展運動,幫助你打敗久坐所帶來的傷害!

贊助我們的創作

看完這篇文章了嗎? 休息一下,喝杯咖啡吧!

如果你覺得 MyApollo 有讓你獲得實用的資訊,希望能看到更多的技術分享,邀請你贊助我們一杯咖啡,讓我們有更多的動力與精力繼續提供高品質的文章,感謝你的支持!