Shibboleth-IdP 的 OAuth2 對(duì)接方案詳解

背景

Shibboleth 是一個(gè)支持 SAML2.0 的開(kāi)源 IdP 服務(wù)器。

SAML2.0 是一個(gè)聯(lián)邦式認(rèn)證的標(biāo)準(zhǔn),簡(jiǎn)單來(lái)說(shuō)就是能夠讓應(yīng)用方——也就是資源提供者(Service Provider,簡(jiǎn)稱 SP)與任意的機(jī)構(gòu)內(nèi)部認(rèn)證——也就是身份提供者(Identity Provider)對(duì)接時(shí),能夠均采用相同的協(xié)議標(biāo)準(zhǔn)。這顯然能夠簡(jiǎn)化集成,像 AWS,Azure 等云服務(wù)商都支持 SAML2.0 方式的機(jī)構(gòu)賬號(hào)對(duì)接。同時(shí)這種簡(jiǎn)化也促進(jìn)了資源的共享,并形成了各式各樣的身份聯(lián)盟,比如中國(guó)的 CARSI[1],澳大利亞的 AAF[2],瑞士的 SWITCHaai[3] 等等,并且通過(guò) eduGAIN[4] 將這些聯(lián)盟連接起來(lái)。

Shibboleth 除了支持 SAML 以外,他在 IdP3 開(kāi)始支持 CAS 協(xié)議[5],并且計(jì)劃在 IdP4 開(kāi)始引入 OpenID Connect ,然后在 IdP5 開(kāi)始穩(wěn)定支持 OpenID Connect 說(shuō)實(shí)話這是一個(gè)槽點(diǎn)(另一個(gè)槽點(diǎn)是 CAS 也開(kāi)始支持 SAML,你說(shuō)這兩撥人真是。。。)。然而如果我們要提供 CAS/OAuth2/OpenID Connect 服務(wù)的話,Shibboleth 顯然不是優(yōu)先的選項(xiàng),SAML2.0 才是選擇他的目的。好在 Shibboleth 支持通過(guò)一些外部插件的模式來(lái)進(jìn)行認(rèn)證[6],而 Unicon/shib-cas-authn3[7]是一個(gè)集成 CAS[8] 和 Shibboleth 的插件。OAuth2 的集成即基于此插件修改實(shí)現(xiàn)。

插件版本

由于 Shibboleth IdP 在 3.4.3 之后修改了一個(gè)內(nèi)部的 API 實(shí)現(xiàn),因此插件版本割裂為 3.3.0 和 3.2.3 兩個(gè)版本。3.3.0 插件僅支持 Shibboleth IdP 3.4.6,而 3.2.3 僅支持 IdP 3.4.3。OAuth2 插件基于 shib-cas-authn3 插件的 3.2.3 版本修改,暫時(shí)不支持 IdP 3.4.6。

修改思路

OAuth2 本身只是授權(quán)協(xié)議,但是通常我們會(huì)將其與認(rèn)證結(jié)合使用。因此包含了認(rèn)證的 OAuth2 Server 可以與 CAS Server 進(jìn)行對(duì)比。先看流程部分

步驟 CAS OAuth2 差異
1 回調(diào)到 CAS 認(rèn)證 請(qǐng)求授權(quán)碼,通常回調(diào)至認(rèn)證 相對(duì)一致
2 認(rèn)證完成,獲得 ticket 認(rèn)證完成,獲得 code 相對(duì)一致
3 無(wú) code 更換 token OAuth 獨(dú)有
4 校驗(yàn) ticket 并獲取用戶屬性 使用 token 調(diào)用用戶屬性接口 相對(duì)一致
5 無(wú) refresh token 可以刷新 token OAuth 獨(dú)有

可以看到,不考慮 refresh token,把 OAuth 的第二步和第三步連接起來(lái),OAuth 在流程上是可以和 CAS 保持一致的,這意味著插件可以不用進(jìn)行傷筋動(dòng)骨的修改,只需要針對(duì)性的微調(diào)即可。

其他差異:

  • redirect_uri 校驗(yàn)
  • 用戶名的判斷
  • 屬性接口的標(biāo)準(zhǔn)

與 CAS 不同,OAuth2 客戶端在使用 code 更換 token 時(shí),還需要附帶自己的 redirect_uri,并且 OAuth2 服務(wù)端根據(jù)標(biāo)準(zhǔn)應(yīng)該要檢驗(yàn)兩個(gè) redirect_uri 是否一致。而 Shibboleth IdP 會(huì)根據(jù) uri 中的 conversation=e1s1 來(lái)區(qū)分會(huì)話,比如 conversation=e1s1 ,conversation=e1s2 。因此在集成中,IdP 的 redirect_uri 必須動(dòng)態(tài)判定的,不能和 CAS 服務(wù)一樣靜態(tài)的指定 /cas/login 來(lái)解決。

另一個(gè)問(wèn)題是用戶名,對(duì)于 CAS 協(xié)議而言,用戶名是一個(gè)標(biāo)準(zhǔn)的字段,他和屬性的釋放是區(qū)分開(kāi)的。例如這個(gè)示例里,用戶名已經(jīng)由 <cas:user>字段標(biāo)記出來(lái),屬性則包含在<cas:attributes>下面:

<cas:serviceResponse xmlns:cas="http://www.yale.edu/tp/cas">
   <cas:authenticationSuccess>
     <cas:user>username</cas:user>
     <cas:attributes>
       <cas:firstname>John</cas:firstname>
       <cas:lastname>Doe</cas:lastname>
       <cas:title>Mr.</cas:title>
       <cas:email>jdoe@example.org</cas:email>
       <cas:affiliation>staff</cas:affiliation>
       <cas:affiliation>faculty</cas:affiliation>
     </cas:attributes>
     <cas:proxyGrantingTicket>PGTIOU-84678-8a9d...</cas:proxyGrantingTicket>
   </cas:authenticationSuccess>
 </cas:serviceResponse>

而 CAS 插件會(huì)講用戶名作為 principalname 傳遞給 shibboleth,因此修改后的 OAuth2 必須也找到一個(gè)唯一確定的用戶名字段來(lái)作為 principalname,這樣才能復(fù)用原本 CAS 插件的很多功能。

因此這就引出了第三個(gè)問(wèn)題,屬性接口的標(biāo)準(zhǔn)。這實(shí)質(zhì)上是 CAS 協(xié)議和 OAuth2 協(xié)議的核心分歧。OAuth2 本質(zhì)上是一個(gè)授權(quán)協(xié)議,他的所有規(guī)范都是針對(duì)授權(quán)過(guò)程的(怎么獲取 token )對(duì)于資源接口沒(méi)有規(guī)定。而 CAS 是一個(gè)認(rèn)證協(xié)議,他在認(rèn)證返回的屬性上有很明確的規(guī)范。因此這里,我們必須人為的給 OAuth2 Server 返回人員屬性的接口進(jìn)行規(guī)定。而這個(gè)規(guī)定實(shí)際上就可以直接參照 OpenID-Connect[9] 內(nèi)關(guān)于 userinfo endpoint 的規(guī)范。例如這樣的一個(gè)返回中,sub 是必須存在的字段,作用類似于 CAS 協(xié)議中的 <cas:user>,其他則是可選的,類似于 CAS 協(xié)議中 <cas:attributes> 下層中的那些屬性

  HTTP/1.1 200 OK
  Content-Type: application/json

  {
   "sub": "248289761001",
   "name": "Jane Doe",
   "given_name": "Jane",
   "family_name": "Doe",
   "preferred_username": "j.doe",
   "email": "janedoe@example.com",
   "picture": "http://example.com/janedoe/me.jpg"
  }

當(dāng)然這只是一個(gè)建議 。在插件中,我們通過(guò)配置 shibcas.oauth2principalname = sub 來(lái)指定 principalname 所指代的屬性字段 ,由用戶來(lái)選擇。接口必須避免層級(jí)嵌套,以確保插件能夠直接的獲取到對(duì)應(yīng)的屬性。

最終的 OAuth2 插件源碼地址在 https://github.com/shanghai-edu/shib-cas-authn3,選擇 tag 3.2.4-oauth 。插件代碼基于北京大學(xué)賴清楠老師的版本進(jìn)一步修改優(yōu)化,特別感謝北京大學(xué) CARSI 項(xiàng)目組團(tuán)隊(duì)的前期工作。

插件安裝文檔詳見(jiàn) CARSI-WiKiSEAC-Document

3.3.0 的 OAuth 版本修改正在工作中,To Be Continued ~~~

實(shí)踐

常見(jiàn)的坑

實(shí)際上正是由于 OAuth2 缺乏 userinfo 的規(guī)范,導(dǎo)致 OAuth2 協(xié)議對(duì)接時(shí),通常需要少量的代碼層定制。這反過(guò)來(lái)導(dǎo)致了一些開(kāi)發(fā)商在提供 OAuth2 產(chǎn)品時(shí)的隨意和不規(guī)范。以下是我碰到過(guò)的幾個(gè)反面例子:

  • Token endpoint 不支持 POST 請(qǐng)求
    OAuth2 的 RFC[10] 明確的要求客戶端在請(qǐng)求 token endpoint 創(chuàng)建 token 時(shí)應(yīng)該采用 POST 方法,并將請(qǐng)求參數(shù)以 application/x-www-form-urlencoded 編碼放在 body 內(nèi)傳輸。這很自然,POST 創(chuàng)建資源嘛。但實(shí)際上我們實(shí)現(xiàn)的往往會(huì)選擇支持 GET 請(qǐng)求,因?yàn)檫@樣會(huì)更容易調(diào)試,雖然這樣就很不 REST 了。小米[11],微信[12] 等大廠的開(kāi)放平臺(tái),也均提供 GET 方式的接口和文檔,這顯然是出于調(diào)試方便的考慮。然而多支持一個(gè) GET 模式,和只支持 GET 顯然不是一回事。。??偛荒馨牙蠈?shí)遵從 RFC 的客戶端給拒絕了對(duì)吧。
  • 注冊(cè) redirect_uri 的校驗(yàn)過(guò)于嚴(yán)格
    redirect_uri 當(dāng)然是要校驗(yàn)的。但通常而言,校驗(yàn)到域名,或者校驗(yàn)到 url 路徑足矣。要求 url 參數(shù)也完全一致的,那其實(shí)就根本沒(méi)在好好校驗(yàn)了,因?yàn)檫@就是在做簡(jiǎn)單的字符串匹配。在很多時(shí)候我們還需要一些動(dòng)態(tài)的參數(shù)來(lái)提供一些回調(diào)頁(yè)面的個(gè)性化支持,這也是 RFC 所允許的。
  • userinfo 接口過(guò)于復(fù)雜
    OAuth2 是一個(gè)授權(quán)規(guī)范,他對(duì)接口設(shè)計(jì)沒(méi)有規(guī)定,當(dāng)然可以任意的來(lái)發(fā)揮。但是當(dāng)我們把 OAuth2 用于認(rèn)證時(shí),至少應(yīng)該在反應(yīng)用戶基本屬性——即 userinfo 這個(gè)接口上,盡量的簡(jiǎn)化。建議參照 openid-connect 關(guān)于 userinfo 的規(guī)范來(lái)實(shí)現(xiàn)這個(gè)接口。

快速測(cè)試

oauth-server-lite

oauth-server-lite[13] 是一個(gè)輕量級(jí)的 OAuth2 服務(wù)器,認(rèn)證部分對(duì)接 LDAP ,并將 LDAP 的屬性映射為 userinfo 接口。因此可用于 IdP 的 OAuth2 對(duì)接測(cè)試。

oauth-server-lite 的認(rèn)證部分支持驗(yàn)證碼和IP地址封禁,以對(duì)抗暴力破解。因此也將其直接和 IdP 打包在一起部署,作為 IdP 的安全加固手段之一應(yīng)用。

oauth-server-lite 的 /oauth/v1/userinfo 接口實(shí)現(xiàn)了 OpenID-Connect 的規(guī)范,它會(huì)將用戶名作為 sub 字段默認(rèn)插入,并將 ldap 返回的屬性作為其他字段輸出。ldap 的多值部分以 ; 連接為字符串,例如:

{
  "cn": "小馮馮", 
  "uid": "11116666", 
  "memberOf": "教職工", 
  "mail": "qfeng@exampe.org", 
  "sub": "11116666"
}

oauth-server-lite 的事情,留到下回再說(shuō)吧

以上

參考文獻(xiàn)

[1] CERNET Authentication and Resource Sharing Infrastructure
[2] Australian Access Federation
[3] SWITCH Authentication and Authorization Infrastructure
[4] eduGAIN
[5] Shibboleth Implemented Protocols and Profiles
[6] Shibboleth RemoteUserAuthnConfiguration
[7] A Shibboleth IdP v3.X plugin for authentication via an external CAS Server
[8] CAS Enterprise Single Sign-On
[9] OpenID Connect Core 1.0 incorporating errata set 1
[10] OAuth2 RFC
[11] 小米開(kāi)發(fā)平臺(tái)
[12] 微信開(kāi)發(fā)平臺(tái)
[13] shanghai-edu/oauth-server-lite

轉(zhuǎn)載授權(quán)

CC BY-SA

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容