OIDC為中央授權中心主推協議,它是位于OAuth2.0協議之上的身份層。
原相信OAuth將會停止對新注冊的頻道支持服務。 對舊頻道賬號的系統提供了 登錄界面兼容處理

中央授權中心OIDC支持

分類 授權類型 支持情況 說明
有用戶參與 授權碼模式
authorization code
支持 最常用的安全模式。點擊查看流程圖
此模式下中央授權中心同時支持OAuth2.0擴展協議 PKCE
密碼模式
password
支持,不對外開放 涉及到用戶相信密碼,不對外開放。
隱式授權模式
implicit
不支持 redirect_uri直接帶上Token,不安全。 說明
混合模式
hybrid
不支持
經資安考量后可適當支持
相當于authorization code與implicit模式混合。
同樣redirect_uri直接帶上Token,不安全。 說明
無用戶參與 客戶端模式
client credentials
支持 僅可得到AccessToken,無IDToken、RefreshToken。
無須刷新Token。

中央授權中心業務流程圖如下:

中央授權中心包含一些職責和功能:

  • 保護你的資源(包括個人身份資訊、集團網站的WebAPI):為客戶端(集團接入系統)提供WebAPI的Bearer安全驗證模式   了解更多>>
  • 使用多種驗證方式對用戶進行身份驗證,如:相信掃碼、相信動態安全碼、TOTP碼、FIDO2(安全密鈅)設備、短信驗證碼、郵件驗證碼、人臉識別以及相信密碼等
  • 相信環境下獲得當前用戶身份驗證,以及手機端調起相信APP進行身份驗證
  • 提供會話管理和單點登錄(SSO)
  • 管理和驗證客戶端(集團接入系統)
  • 向客戶端(集團接入系統)發放身份和訪問令牌(即:ID Token和Access Token)
  • 驗證令牌


1)發現端點(Discovery Endpoint)

請求方式:GET

發現端點可用于檢索有關中央授權中心的元數據 - 它返回發布者名稱,密鑰材料,支持的范圍等信息。有關詳細信息,請參閱此處

中央授權中心的發現端點為:https://sso.foxconn.com/.well-known/openid-configuration



2)授權端點(Authorize Endpoint)

標準地址:https://sso.foxconn.com/connect/authorize
請求方式:瀏覽器直接請求(非接口調用)
其它地址
地址1:https://sso.foxconn.com/Account/Login
* 此地址不建議。請使用標準地址,可設定參數prompt=login 來達到同等效果。
地址2:https://sso.foxconn.com/connect/iframe_authz
* 將登入二維碼顯示在一個Iframe框中。非特殊情況也不建議使用。
* 針對一些只支持IE的老舊系統,使用<iframe>標籤嵌入地址2時,建議將IE默認版設置成IE 9及以上,設定如下:
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <!-- 頁面頭部添加 -->
* 目前此方式對IE 6 ~ 8支持不理想,IE5及以下版本不支持。
* 使用Edge模擬IE環境時,非<iframe>標籤嵌入模式使用不受影響。

擴展功能1: JS調用掃碼登錄
擴展功能2: 自定義掃碼登錄

【討論議題】標準地址的意義以及window.open方法是否可用?
【演示地址】公司內網演示點擊進入

授權端點可用于通過瀏覽器請求令牌或授權碼。此過程通常涉及最終用戶的身份驗證和可選的同意。

注意 中央授權中心支持OpenID Connect和OAuth 2.0授權請求參數的子集。有關完整列表, 請參見此處
《OAuth 2.0授权框架》中文文檔

參數說明

  • client_id
    客戶的標識符(必需)。

  • scope
    一個或多個注冊范圍(必需)

  • redirect_uri
    必須與該客戶端允許的重定向URI之一完全匹配(必需)

  • response_type
    響應類型(RFC 6749標準要求“必需”,中央授權中心為“可選”項),可參考OIDC文檔附錄

  • response_mode
    響應方式,回傳response_type對應數據的方式。(可選)

    • query 默認方式,將授權碼(或令牌)響應查詢字符串的形式附加到重定向 URI(Redirect URI)中。
    • form_post 將授權碼(或令牌)響應作為表單發送而不是片段編碼重定向。
    • fragment 將授權碼(或令牌)響應以 URL 片段的形式(#code=[code值])附加到重定向 URI 中。
  • state
    狀態值,回傳response_type對應數據的同時會回傳此狀態值。(推薦的)
    此參數為了安全而設計,針對客戶端和中央授權中心之間的往返狀態,增強返回值的安全性(即:集團接入系統可以核對此值前后是否一致),防止跨站請求偽造(CSRF)攻擊,但它也可以用于在授權流程完成后定位到應用程序(網站)應該顯示的內容。
    使用此參數時請確保不包含敏感信息。

  • nonce
    中央授權中心將回傳身份令牌中的nonce值,這樣可增加返回值的安全。(可選)
    通過隱式授權對身份令牌是必需的,但隱式授權中央授權中心不支持。
    此值將寫入身份令牌(id_token)中,與stateredirect_uri界面傳值不同。

  • prompt
    是否每次都顯示登錄UI。(可選)

    • none不需要時不顯登錄UI。此設定不影響用戶正常登錄時的UI顯示和同意界面顯示。
    • login即使用戶已登錄并具有有效會話,也會顯示登錄UI。但中央授權中心會顯示用戶已登錄的狀態。
    • force_login強行要求用戶登錄驗證。若用戶已登錄中央授權中心會移除登錄狀態,自動登出。
  • code_challenge
    發送PKCE的代碼質詢(可選)

  • code_challenge_method
    發送PKCE的代碼是否進行哈希處理。(可選) [說明]

    • plain 默認值,表示挑戰是使用純文本(不推薦)。
    • login S256表示使用SHA256對挑戰進行哈希處理。

  • login_hint
    可用于預先填寫登錄頁面上的用戶名字段(可選)

  • login_type
    用于設定登錄方式(可選)
    * 目前主要針對二次驗證(多因子驗證、Multi-factor authentication)功能設定,“非中央授權中心二次驗證”功能請不要使用此參數。
    * 用戶在中央授權中心身份驗證成功(即:登錄成功)之后,此值將與授權代碼(authorization_code)一起傳入redirect_uri中。
    本參數值包括:

    • 2FA 中央授權中心自身兩次驗證。即兩次驗證全由中央授權中心負責完成。(推薦使用)
    • Mfa 中央授權中心提供第二次驗證。即中央授權中心只負責兩次驗證的后次驗證。
    • MyVendor 外部供應商第二次驗證。
    • 【說明】
    • 1. 當login_type = 2FA 時,將由中央授權中心負責完成兩次驗證。
      授權中心登錄方式分為相信設備與非相信設備兩大類。此登錄方式下,要求用戶必須在每類登錄方式中選擇一種。 具體用戶選擇了哪些登錄方式,將在JWT的Token的amr數組中體現。點擊查看amr編碼說明
      1. 可選擇的驗證方式
        1. 相信設備類:相信掃碼、相信動態安全碼、相信密碼。
        2. 非相信設備類:TOTP、FIDO2設備、短信驗證碼、郵箱驗證碼、Web人臉。
      2. 此類登錄方式,支持接入系統間的用戶信息共享,即完全支持單點登錄功能。推薦使用。
    • 2. 當login_typeMfa MyVendor 時,為第三方系統登錄之后中央授權中心再進行第二次驗證。
      1. 此類登錄方式,參數 login_hint 必須得有值。
      2. 在使用此值得到的code調令牌端點(Token Endpoint)時,也必須帶上login_type參數且值必須一致。(本文不另作說明)。
      3. 此類登錄方式,授權中心不保留登錄狀態,不能實現接入系統間的用戶信息共享(即:沒有單點登錄功能)。
    • 3. 當login_type = Mfa 時,我們提供了多種驗證方式,供用戶任意選擇一種。
      1. 可以通過fa參數來指定用戶選擇的驗證方式選項及選項順序。見fa參數值說明
      2. 也可以將值Mfa改成以下值,來指定一種驗證方式。
        • MfaFxOtp 指定用相信動態安全碼作為第二次驗證。
        • MfaScan 指定用相信掃碼作為第二次驗證。
        • MfaTotp 指定用TOTP作為第二次驗證。
        • MfaFido 指定用Fido2作為第二次驗證。
        • MfaMail 指定用郵箱驗證碼作為第二次驗證。
        • MfaSms 指定用手機短信驗證碼作為第二次驗證。
        • MfaBelieve 指定用相信掃碼或相信動態安全碼作為第二次驗證。
    • 4. 當login_type = MyVendor 時,
      1. 對應的scope只能包含openid profile;
      2. 對應的login_hint格式必須為“[clientId]:[系統賬號]”如:channel01:13800138000
      3. 必須設定 signature = SHA1(redirect_uri值+login_hint值+簽名KEY),即將三個值拼湊起來生成SHA1的簽名。簽名KEY請從 我的客戶端 -> 修改資料 -> 用戶設定 查看。(界面效果圖)
      4. MyVendor驗證方式,以TOTP為主,在擴展功能中提供了郵箱、短信驗證碼方式驗證。擴展功能需額外開通。 (使用方式說明)

  • lang
    修改登錄界面的語言(可選)
    值有:

    • cn 簡體中文
    • tw 繁體中文
    • en 英文
    • jp 日文
    • vn 越南語

  • mobile_return
    手機端非相信環境調相信APP登錄驗證后,返回至原界面。(可選)

    • 瀏覽器環境中,此值則應為http(s)的url,如:http://yourbg.foxconn.com/start.html
    • 其它APP環境中,此值則應為app scheme開頭的uri,如:appbgx://yourbg.foxconn.com/start.html
    注意*:此為一種臨時解決方案,若要中央授權中心登錄之后直接跳至APP Scheme Uri地址,請在“我的客戶端”中配置RedirectUris值。

GET /connect/authorize?
    client_id=client1&
    scope=openid profile foxconn civet.api.msg&
    response_type=code&
    redirect_uri=https://mybu.foxconn.com/callback&
    state=abc&
    nonce=xyz

(刪除了URL編碼,并添加了換行符以提高可讀性)

在線操作試一試



3)令牌端點(Token Endpoint)

地址:https://sso.foxconn.com/connect/token
請求方式:POST
Content-Type:application/x-www-form-urlencoded

令牌端點可用于以編程方式請求令牌。它支持authorization_codeclient_credentialspasswordrefresh_token的類型。

注意 中央授權中心支持OpenID Connect和OAuth 2.0授權請求參數的子集。有關完整列表,請參見此處

  • client_id
    客戶標識符(必需)

  • client_secret
    客戶端密鑰(必需)
    密鑰為開發者設置的原始值,即申請填寫的密鑰及修改密鑰填寫的值。而非系統顯示的加密后的HASH值。

  • grant_type
    授權類型(必填)
    值有: authorization_codeclient_credentialspasswordrefresh_token
    注意:client_credentials為集團接入系統后臺直接調用,產生的AccessToken默認一個小時內有效。建議集團接入系統后臺存儲AccessToken,切莫頻繁調用令牌端點(Token Endpoint)。

  • scope
    一個或多個注冊范圍。(client_credentialspassword授權類型所需)
    如果未指定,將發出所有明確允許范圍的標記。 使用authorization_code授權類型時未指定,則為本次請求“授權端點(Authorize Endpoint)”所指定的scope值。

  • redirect_uri
    指定的重載地址(authorization_code授權類型所需)
    在使用authorization_code授權類型時必須與本次請求“授權端點(Authorize Endpoint)”獲得code時傳入的redirect_uri值一致。

  • code
    授權代碼(authorization_code授權類型所需)
    在使用authorization_code授權類型,此值為本次請求“授權端點(Authorize Endpoint)”獲得的code值,code值系統設定為5分鐘內有效,且只能成功地使用一次,Token獲得成功之后不能用其重復調用此令牌端點(Token Endpoint)。

  • code_verifier
    PKCE證明密鑰(PKCE協議所需) [說明]

  • username
    資源所有者用戶名(password授權類型所需)

  • password
    資源所有者密碼(password授權類型所需)

  • refresh_token
    刷新令牌(refresh_token授權類型所需)
    刷新令牌指刷新“有用戶參與”模式下AccessToken,避免因令牌過期而要求用戶重新登錄的情況。
    只有AccessToken搭載數據中scope的值包含offline_access,中央授中心才會同時發放RefreshToken值,集團接入系統可使用RefreshToken值再調用令牌端點(Token Endpoint)刷新AccesToken。
    注意:刷新令牌僅需在AccessToken快過期前調用一次即可,切莫在AccessToken有效期頻繁調用。

POST /connect/token

    client_id=client1&
    client_secret=secret&
    grant_type=authorization_code&
    code=[授權端點重載頁面時帶上的code參數值]&
    redirect_uri=https://mybu.foxconn.com/callback

AccessToken還廣泛用于非『中央授權中心』的第三方接口授權。為了更好地使用AccessToken,可參考《怎樣獲得『中央授權中心』的AccessToken》

4)UserInfo端點(UserInfo Endpoint)

地址:https://sso.foxconn.com/connect/userinfo
請求方式:GET
Content-Type:application/json
Authorization: Bearer <access_token>

UserInfo端點可用于檢索有關用戶的身份信息 (請參閱OIDC規范)

調用者需要發送代表用戶的有效訪問令牌。根據授予的范圍,UserInfo端點將返回映射的聲明(至少需要openid作用域)。

示例

GET /connect/userinfo
        Authorization: Bearer <access_token>
        HTTP/1.1 200 OK
        Content-Type: application/json
        
        {
            "sub": "FX000001",
            "emp_no": "FX000001",
            "name": "相信大使",
            "area":"龍華",
            "bg":"中央單位",
            "bu":"中央資訊總處",
            "company":"富泰華工業(深圳)有限公司",
            "cost_code":"DAAB07",
            "birthdate":"2000/01/01",
            "class":"師1",
            "job_title":"大使",
            "hiredate":"2015-08-10",
            "email":"civet-helpdesk@mail.foxconn.com",
            "picture":"https://icivetmedia.foxconn.com/group1/M00/39/67/CoaWx14kDPaENfGQAAAAALnmmck62.jpeg",
            "role": [
                "簡單角色設定值1",
                "簡單角色設定值2"
            ]
        }
注:同樣的方法,可使用不同scope範圍的access_token,去保護集團的任何WebAPI。 如相信開放接口v2 beta版


5)結束會話端點(End Session Endpoint)

地址:https://sso.foxconn.com/connect/endsession
請求方式:瀏覽器直接請求(非接口調用)

結束會話端點可用于觸發單點注銷 (請參閱規范)。

要使用結束會話端點,客戶端應用程序會將用戶的瀏覽器重定向到結束會話URL。用戶在會話期間通過瀏覽器登錄的所有應用程序都可以參與注銷。

  • id_token_hint
    當前用戶的IDToken,此值用來繞過系統提示,無此值時系統會提示用戶是否真的想要注銷。
    即:有值時用戶無需再確認是否退出。
    此方法中不判斷傳入的用戶IDToken是否過期

  • post_logout_redirect_uri
    如果id_token_hint有效傳遞了,則客戶端也可以發送post_logout_redirect_uri參數。這可用于允許用戶在注銷后重定向回客戶端。該值必須與客戶端預先配置的PostLogoutRedirectUris。

  • state
    如果post_logout_redirect_uri有效,則客戶端也可以發送state參數。在用戶重定向回客戶端后,這將作為查詢字符串參數返回給客戶端。這通常由客戶端用于跨重定向的往返狀態。

GET /connect/endsession?
    id_token_hint=<當前用戶的IDToken>&
    post_logout_redirect_uri=https://myapp/logout&
    state=abc&

(刪除了URL編碼,并添加了換行符以提高可讀性)



6)刷新令牌之撤消端點(Revocation Endpoint)

地址:https://sso.foxconn.com/connect/revocation
請求方式:POST
Content-Type:application/x-www-form-urlencoded

此端點允許撤消刷新令牌。它實現了令牌撤銷規范(RFC 7009)。

結束會話端點可用于觸發單點注銷(請參閱規范)。

要使用結束會話端點,客戶端應用程序會將用戶的瀏覽器重定向到結束會話URL。用戶在會話期間通過瀏覽器登錄的所有應用程序都可以參與注銷。

  • token
    要撤銷的令牌(必填)

  • token_type_hint
    令牌類型提示(可選)   值有:access_tokenrefresh_token(可選)

POST /connect/revocation HTTP/1.1
                Host: server.example.com
                Content-Type: application/x-www-form-urlencoded
                Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
                
                token=45ghiukldjahdnhzdauz&token_type_hint=refresh_token

(刪除了URL編碼,并添加了換行符以提高可讀性)



7)驗證令牌有效性 (很重要)

公鑰地址:https://sso.foxconn.com/.well-known/openid-configuration/jwks
JWT在線工具:https://jwt.io/
注意:此處著重講“JWT令牌”的驗證方法,與“內省端點”功能有所區別。
內省端點(Introspection Endpoint) https://sso.foxconn.com/connect/introspect ,是 RFC 7662的實現。
能驗證“引用令牌”及“JWT令牌”有效性。 (中央授權中心默認使用JWT令牌)
但內省端點只針對API Resource的Scope驗證,且對應的API Resource需設定 APISecret。端點調用說明

中央授權中心的令牌為JWT標準格式,并采用RS256的簽名。

如果您想直接使用JWT搭載的信息(如:sub)做為用戶身份認別,應對JWT簽名進行驗證。WebAPI在使用AccessToken進行權限認證時,都必須驗證JWT令牌有效性。授權中心UserInfo端點已對JWT令牌有效性進行了嚴格的驗證。

客戶端驗證令牌有效性主要有以下幾步:

  • 1) 從JWT搭載的信息中判斷令牌是否在有效期內。

  • 2) 通過公開的公鑰地址獲得公鑰,對JWT簽名部分進行驗證。

參考代碼如下:

  1. using System;  
  2. using System.Text;  
  3. using System.Security.Cryptography;  
  4.   
  5. namespace sso.foxconn.com  
  6. {  
  7.     class JwtTest  
  8.     {  
  9.         static void Main(string[] args)  
  10.         {  
  11.             //access_token 或 id_token 值      
  12.             string jwt = "eyJhbGciOiJSUzI1NiIsImtpZCI6IkY2M0Y0MTU0QkREMUQwQzAyMkVGREUyOTIxMzFDRUQzM0ZBMDJDRTRSUzI1NiIsInR5cCI6ImF0K2p3dCIsIng1dCI6IjlqOUJWTDNSME1BaTc5NHBJVEhPMHotZ0xPUSJ9.eyJuYmYiOjE2NzMyMjk1NzIsImV4cCI6MTY3MzIzMzE3MiwiaXNzIjoiaHR0cHM6Ly9zc28uZm94Y29ubi5jb20iLCJjbGllbnRfaWQiOiJ2b3RlMDEiLCJzdWIiOiJGMzIxODQ2NiIsImF1dGhfdGltZSI6MTY3MzIyNTYyMywiaWRwIjoibG9jYWwiLCJyb2xlcyI6ImFkbWluaXN0cmF0b3JzLGRldmVsb3BlcnMiLCJqdGkiOiJGMzc2QTU4ODVBNDJBMUJDMTQ5NTQ3NUUzNDQ3NDhDRCIsInNpZCI6IjM1Qzg5MEVBQkVBRjI5MERDODJBOUNFMEQyRTg0RTc0IiwiaWF0IjoxNjczMjI5NTcyLCJzY29wZSI6WyJvcGVuaWQiLCJwcm9maWxlIiwiZm94Y29ubiJdLCJhbXIiOlsicXJjb2RlIl19.HxsoMF9u-MQich8QzE4exGUlMzbln4XoVq0KECxd7D6T5Yieku42E7UtzjCbb-wIFgHTeLero6Txo_0wqgi8U_gbh1PGBy0oGA5eqtRJX9dP9Br_SZCIs6M6TRdFzh8tOs-wKw9cTOEoBY4CBL8kYSb26Kl9bP7r8sQ9X9U7jCmfthfSQBDIo9CGb8SRlQfF_r5aEzXViOOHaAEhbxX2x5Ti8Y2Q0QKQyGVmiQZgbcof7rlSGan2sDtgpGougFfanwBhzAQpnEkqB3uTgdZ1F6mVtHIgzF2pZ_e52mZcga2lI-lpSaywBHatGlZ1_0C_p7OQMOGuWpHFa7UCYs0nhg";  
  13.             //jwt header中有相關設定值,包括kid      
  14.             string jwt_header = Encoding.UTF8.GetString(UnBase64(jwt.Split('.')[0]));  
  15.             Console.WriteLine(jwt_header);  
  16.             //從 jwt header 解碼為JSON格式,獲得kid  此功能略    
  17.   
  18.             //// 從 jwt payload (JSON格式),獲得 exp: 過期時間截    
  19.             //// 與當前時間比較,驗證時間是否過期  此功能略     
  20.             //string jwt_payload = Encoding.UTF8.GetString(UnBase64(jwt.Split('.')[1]));    
  21.   
  22.             // 從 https://sso.foxconn.com/.well-known/openid-configuration/jwks      
  23.             // 找到對應kid的e、n值      
  24.             string e = "AQAB";  
  25.             string n = "iTB_vfI_jUHqJcPY9NnuR8oeHyUdWzi00C1OD-yBKm2zKO30FWySnjY8x_xfCYFHjeJKDuqnk07eb8z7_PHIvqDetEkN2n1XzjftgrEb8mZ63-stvI6sh2C_JUfmOtsFsaoODYjD2ApIwbN7-CLpENWCwDTlYhEGUqs6lD8muH03BljjnPxzXtkUkLn6NjPLQE2WZFch_doJf4ZMTi7c42MzNK80hKu_tuBvLD4-bu0F6y_Cwg0OG6Vm2IAYLtlL27pdlmR8bvVNUfz_xZC12NLDTTJSpcvLUEUIh-8M2c8sQ7NUUlK--35CZolNoaahUsDtNbH6-kEbVJ5iCpgB-w";  
  26.   
  27.             Console.Write(CheckJWT(jwt, e, n));  
  28.         }  
  29.   
  30.         /// <summary>          
  31.         /// 驗證JWT          
  32.         /// </summary>          
  33.         /// <param name="jwt">Token (JWT格式)</param>          
  34.         /// <param name="exponent">公鑰-Exponent(e)</param>          
  35.         /// <param name="modulus">公鑰-Modulus(n)</param>  
  36.         /// <returns></returns>        
  37.         public static bool CheckJWT(string jwt, string exponent, string modulus, Encoding encoding = null)  
  38.         {  
  39.             string[] strs = jwt.Split('.');  
  40.             string jwt_header = strs[0];  
  41.             string jwt_payload = strs[1];  
  42.             string jwt_sign = strs[2];  
  43.             return Check(jwt_header + "." + jwt_payload, jwt_sign, exponent, modulus);  
  44.         }  
  45.   
  46.   
  47.         static bool Check(string OrginString,  
  48.             string EncryptString,  
  49.             string exponent,  
  50.             string modulus,  
  51.             Encoding encoding = null)  
  52.         {  
  53.             RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();  
  54.             rsa.ImportParameters(  
  55.               new RSAParameters()  
  56.               {  
  57.                   Modulus = UnBase64(modulus),  
  58.                   Exponent = UnBase64(exponent)  
  59.               });  
  60.             encoding = encoding ?? Encoding.UTF8;  
  61.             SHA256 sha256 = SHA256.Create();  
  62.             byte[] hash = sha256.ComputeHash(encoding.GetBytes(OrginString)); //header + '.' + content          
  63.             RSAPKCS1SignatureDeformatter rsaDeformatter = new RSAPKCS1SignatureDeformatter(rsa);  
  64.             rsaDeformatter.SetHashAlgorithm("SHA256");  
  65.             return rsaDeformatter.VerifySignature(hash, UnBase64(EncryptString));  
  66.         }  
  67.   
  68.         static byte[] UnBase64(string value)  
  69.         {  
  70.             string result = value.Replace("-""+").Replace(" ""+").Replace("_""/").Replace(".""/");  
  71.             int lastLen = result.Length % 4;  
  72.             if (lastLen != 0)  
  73.             {  
  74.                 result += new string('=', 4 - lastLen);  
  75.             }  
  76.             return Convert.FromBase64String(result);  
  77.         }  
  78.     }  
  79. }  

復制代碼,在線運行一下



意見反饋/咨詢