網關協議——OpenID Connect(身份認證+OAuth2授權)入門指南

OpenID Connect

如果要談單點登錄和身份認證,就不得不談OpenID Connect (OIDC)。最典型的使用實例就是使用Google賬戶登錄其他應用,這一經典的協議模式,爲其他廠商的第三方登錄起到了標杆的作用,被廣泛參考和使用。

OpenID Connect簡介

OpenID Connect是基於OAuth 2.0規範族的可互操作的身份驗證協議。它使用簡單的REST / JSON消息流來實現,和之前任何一種身份認證協議相比,開發者可以輕鬆集成。

OpenID Connect允許開發者驗證跨網站和應用的用戶,而無需擁有和管理密碼文件。OpenID Connect允許所有類型的客戶,包括基於瀏覽器的JavaScript和本機移動應用程序,啓動登錄流動和接收可驗證斷言對登錄用戶的身份。

OpenID的歷史是什麼?

OpenID Connect是OpenID的第三代技術。首先是原始的OpenID,它不是商業應用,但讓行業領導者思考什麼是可能的。OpenID 2.0設計更爲完善,提供良好的安全性保證。然而,其自身存在一些設計上的侷限性,最致命的是其中依賴方必須是網頁,但不能是本機應用程序;此外它還要依賴XML,這些都會導致一些應用問題。

OpenID Connect的目標是讓更多的開發者使用,並擴大其使用範圍。幸運的是,這個目標並不遙遠,現在有很好的商業和開源庫來幫助實現身份驗證機制。

OIDC基礎

簡要而言,OIDC是一種安全機制,用於應用連接到身份認證服務器(Identity Service)獲取用戶信息,並將這些信息以安全可靠的方法返回給應用。

在最初,因爲OpenID1/2經常和OAuth協議(一種授權協議)一起提及,所以二者經常被搞混。

  • OpenIDAuthentication,即認證,對用戶的身份進行認證,判斷其身份是否有效,也就是讓網站知道“你是你所聲稱的那個用戶”;
  • OAuthAuthorization,即授權,在已知用戶身份合法的情況下,經用戶授權來允許某些操作,也就是讓網站知道“你能被允許做那些事情”。
    由此可知,授權要在認證之後進行,只有確定用戶身份只有才能授權。

(身份驗證)+ OAuth 2.0 = OpenID Connect

OpenID Connect是“認證”和“授權”的結合,因爲其基於OAuth協議,所以OpenID-Connect協議中也包含了client_idclient_secret還有redirect_uri等字段標識。這些信息被保存在“身份認證服務器”,以確保特定的客戶端收到的信息只來自於合法的應用平臺。這樣做是目的是爲了防止client_id泄露而造成的惡意網站發起的OIDC流程。

舉個例子。某個用戶使用Facebook應用*“What online quiz best describes you?”* ,該應用可以通過Facebook賬號登錄,則你可以在應用中發起請求到“身份認證服務器”(也就是Facebook的服務器)請求登錄。這時你會看到如下界面,詢問是否授權。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-4KqxC3or-1591176861582)(https://upload-images.jianshu.io/upload_images/3297585-48d5253819fb8924.png)]

image.png

OAuth中,這些授權被稱爲scopeOpenID-Connect也有自己特殊的scopeopenid ,它必須在第一次請求“身份鑑別服務器”(Identity Provider,簡稱IDP)時發送過去。

OIDC流程

OAuth2提供了Access Token來解決授權第三方客戶端訪問受保護資源的問題;相似的,OIDC在這個基礎上提供了ID Token來解決第三方客戶端標識用戶身份認證的問題。OIDC的核心在於在OAuth2的授權流程中,一併提供用戶的身份認證信息(ID-Token)給到第三方客戶端,ID-Token使用JWT格式來包裝,得益於JWTJSON Web Token)的自包含性,緊湊性以及防篡改機制,使得ID-Token可以安全的傳遞給第三方客戶端程序並且容易被驗證。應有服務器,在驗證ID-Token正確只有,使用Access-TokenUserInfo的接口換取用戶的更多的信息。

有上述可知,OIDC是遵循OAuth協議流程,在申請Access-Token的同時,也返回了ID-Token來驗證用戶身份。

相關定義

  • EU:End User,用戶。
  • RP:Relying Party ,用來代指OAuth2中的受信任的客戶端,身份認證和授權信息的消費方;
  • OP:OpenID Provider,有能力提供EU身份認證的服務方(比如OAuth2中的授權服務),用來爲RP提供EU的身份認證信息;
  • ID-Token:JWT格式的數據,包含EU身份認證的信息。
  • UserInfo Endpoint:用戶信息接口(受OAuth2保護),當RP使用ID-Token訪問時,返回授權用戶的信息,此接口必須使用HTTPS

下面我們來看看OIDC的具體協議流程。
根據應用客戶端的不同,OIDC的工作模式也應該是不同的。和OAuth類似,主要看是否客戶端能保證client_secret的安全性。

如果是JS應用,其所有的代碼都會被加載到瀏覽器而暴露出來,沒有後端可以保證client_secret的安全性,則需要是使用默認模式流程(Implicit Flow)。

如果是傳統的客戶端應用,後端代碼和用戶是隔離的,能保證client_secret的不被泄露,就可以使用授權碼模式流程(Authentication Flow)。

此外還有混合模式流程(Hybrid Flow),簡而言之就是以上二者的融合。

OAuth2中還有口令模式和“應有訪問模式”的方式來獲取Access Token(關於OAuth2的內容,可以參見OAuth2.0 協議入門指南),爲什麼OIDC沒有擴展這些方式呢?
"口令模式"是需要用戶提供賬號和口令給RP的,既然都已經有用戶名和口令了,就不需要在獲取什麼用戶身份了。至於“應有訪問模式”,這種方式不需要用戶參與,也就無需要認證和獲取用戶身份了。這也能反映授權和認證的差異,以及只使用OAuth2來做身份認證的事情是遠遠不夠的,也是不合適的。

授權碼模式流程

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-xPDyBBGX-1591176861583)(https://upload-images.jianshu.io/upload_images/3297585-1211ac3e32d000cc.png)]

授權碼模式流程

OAuth認證流程類似

  1. RP發送一個認證請求給OP,其中附帶client_id
  2. OP對EU進行身份認證;
  3. OP返回響應,發送授權碼給RP;
  4. RP使用授權碼向OP索要ID-Token和Access-Token,RP驗證無誤後返回給RP;
  5. RP使用Access-Token發送一個請求到UserInfo EndPoint; UserInfo EndPoint返回EU的Claims。

基於Authorization Code的認證請求

RP使用OAuth2的Authorization-Code的方式來完成用戶身份認證,所有的Token都是通過OP的Token EndPoint(OAuth2中定義)來發放的。構建一個OIDC的Authentication Request需要提供如下的參數:

  • scope:必須。OIDC的請求必須包含值爲“openid”的scope的參數。
  • response_type:必選。同OAuth2。
  • client_id:必選。同OAuth2。
  • redirect_uri:必選。同OAuth2。
  • state:推薦。同OAuth2。防止CSRF, XSRF。

示例如下:

GET /authorize?
    response_type=code
    &scope=openid%20profile%20email
    &client_id=s6BhdRkqt3
    &state=af0ifjsldkj
    &redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb HTTP/1.1
  Host: server.example.com

基於Authorization Code的認證請求的響應

在OP接收到認證請求之後,需要對請求參數做嚴格的驗證,具體的規則參見http://openid.net/specs/openid-connect-core-1_0.html#AuthRequestValidation,驗證通過後引導EU進行身份認證並且同意授權。在這一切都完成後,會重定向到RP指定的回調地址(redirect_uri),並且把codestate參數傳遞過去。比如:

  HTTP/1.1 302 Found
  Location: https://client.example.org/cb?
    code=SplxlOBeZQQYbYS6WxSbIA
    &state=af0ifjsldkj

獲取ID Token

RP使用上一步獲得的code來請求Token EndPoint,這一步桶OAuth2,就不再展開細說了。然後Token EndPoint會返回響應的Token,其中除了OAuth2規定的部分數據外,還會附加一個id_token的字段。id_token字段就是上面提到的ID Token。例如:

  HTTP/1.1 200 OK
  Content-Type: application/json
  Cache-Control: no-store
  Pragma: no-cache

  {
   "access_token": "SlAV32hkKG",
   "token_type": "Bearer",
   "refresh_token": "8xLOxBtZp8",
   "expires_in": 3600,
   "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOWdkazcifQ.ewogImlzc
     yI6ICJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwKICJzdWIiOiAiMjQ4Mjg5
     NzYxMDAxIiwKICJhdWQiOiAiczZCaGRSa3F0MyIsCiAibm9uY2UiOiAibi0wUzZ
     fV3pBMk1qIiwKICJleHAiOiAxMzExMjgxOTcwLAogImlhdCI6IDEzMTEyODA5Nz
     AKfQ.ggW8hZ1EuVLuxNuuIJKX_V8a_OMXzR0EHR9R6jgdqrOOF4daGU96Sr_P6q
     Jp6IcmD3HP99Obi1PRs-cwh3LO-p146waJ8IhehcwL7F09JdijmBqkvPeB2T9CJ
     NqeGpe-gccMg4vfKjkM8FcGvnzZUN4_KSP0aAp1tOJ1zZwgjxqGByKHiOtX7Tpd
     QyHE5lcMiKPXfEIQILVq0pc_E2DzL7emopWoaoZTF_m0_N0YzFC6g6EJbOEoRoS
     K5hoDalrcvRYLSrQAZZKflyuVCyixEoV9GfNQC3_osjzw2PAithfubEEBLuVVk4
     XUVrWOLrLl0nx7RkKU8NXNHq-rvKMzqg"
  }

其中看起來一堆亂碼的部分就是JWT格式的ID-Token。在RP拿到這些信息之後,需要對id_token以及access_token進行驗證(具體的規則參見http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidationhttp://openid.net/specs/openid-connect-core-1_0.html#ImplicitTokenValidation)。至此,可以說用戶身份認證就可以完成了,後續可以根據UserInfo EndPoint獲取更完整的信息。

安全令牌 ID-Token

上面提到過OIDCOAuth2最主要的擴展就是提供了ID-Token。下面我們就來看看ID-Token的主要構成:

  • iss = Issuer Identifier:必須。提供認證信息者的唯一標識。一般是Url的host+path部分;
  • sub = Subject Identifier:必須。iss提供的EU的唯一標識;最長爲255個ASCII個字符;
  • aud = Audience(s):必須。標識ID-Token的受衆。必須包含OAuth2的client_id;
  • exp = Expiration time:必須。ID-Token的過期時間;
  • iat = Issued At Time:必須。JWT的構建的時間。
  • auth_time = AuthenticationTime:EU完成認證的時間。如果RP發送認證請求的時候攜帶max_age的參數,則此Claim是必須的。
  • nonce:RP發送請求的時候提供的隨機字符串,用來減緩重放攻擊,也可以來關聯ID-Token和RP本身的Session信息。
  • acr = Authentication Context Class Reference:可選。表示一個認證上下文引用值,可以用來標識認證上下文類。
  • amr = Authentication Methods References:可選。表示一組認證方法。
  • azp = Authorized party:可選。結合aud使用。只有在被認證的一方和受衆(aud)不一致時才使用此值,一般情況下很少使用。
{
   "iss": "https://server.example.com",
   "sub": "24400320",
   "aud": "s6BhdRkqt3",
   "nonce": "n-0S6_WzA2Mj",
   "exp": 1311281970,
   "iat": 1311280970,
   "auth_time": 1311280969,
   "acr": "urn:mace:incommon:iap:silver"
  }

另外ID Token必須使用JWT(JSON Web Token)進行簽名和JWE(JSON Web Encryption)加密,從而提供認證的完整性、不可否認性以及可選的保密性。關於JWT的更多內容,請參看JSON Web Token - 在Web應用間安全地傳遞信息

默認模式流程

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-GUcfwNQC-1591176861584)(https://upload-images.jianshu.io/upload_images/3297585-d0525bca9a519b6a.png)]

默認模式流程

默認流程和OAuth中的類似,只不過也是添加了ID-Token的相關內容。

這裏需要說明的是:OIDC的說明文檔裏很明確的說明了用戶的相關信息都要使用JWT形式編碼。在JWT中,不應該在載荷裏面加入任何敏感的數據。如果傳輸的是用戶的User ID。這個值實際上不是什麼敏感內容,一般情況下被知道也是安全的。

但是現在工業界已經不推薦使用OAuth默認模式,而推薦使用不帶client_Secret授權碼模式

混合模式

混合模式簡而言之就是以上提到的兩種模式的混合,不過也有一些小的改變,就是允許直接向客戶端返回Access-Token

業界普遍認爲,後端傳遞Token(比如服務器之間通信)要比前端(比如頁面之間)可靠,所以如果直接返回令牌的情況下會把令牌的過期時間設置較短,但是比較

UserInfo Endpoint

可能有的讀者發現了,ID-Token只有sub是和EU相關的,這在一般情況下是不夠的,必須還需要EU的用戶名,頭像等其他的資料,OIDC提供了一組公共的cliams,來提供更多用戶的信息,這就是——UserIndo EndPoin。

在RP得到Access Token後可以請求此資源,然後獲得一組EU相關的Claims,這些信息可以說是ID-Token的擴展,ID-Token中只需包含EU的唯一標識sub即可(避免ID Token過於龐大和暴露用戶敏感信息),然後在通過此接口獲取完整的EU的信息。此資源必須部署在TLS之上,例如:

  GET /userinfo HTTP/1.1
  Host: server.example.com
  Authorization: Bearer SlAV32hkKG

成功之後響應如下:

  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": "[email protected]",
   "picture": "http://example.com/janedoe/me.jpg"
  }

其中sub代表EU的唯一標識,這個claim是必須的,其他的都是可選的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章