全部產品
Search
文件中心

Identity as a Service:自研應用接入 SSO

更新時間:May 21, 2025

本文介紹如何使用OIDC協議授權碼模式,將自研應用接入IDaaS的單點登入(SSO)服務。協助開發人員輕鬆實現應用的SSO功能,提升使用者體驗和管理效率。

背景介紹

IDaaS 採用標準的 OIDC 協議授權碼模式來支援常規企業自研應用接入。DingTalk、微信等社交身份均採用 OAuth 協議實現掃碼登入。IDaaS 採用的 OIDC 協議是 OAuth 協議的升級版。

說明

相容 OAuth:OIDC(OpenID Connect) 1.0 協議在 OAuth2.0 協議之上建立了使用者身份層,OIDC 協議也因此相容 OAuth2.0 協議。OIDC 授權碼模式和 OAuth2.0 授權碼模式流程一致,區別是 OIDC 對使用者資訊端點進行了標準化,並在 Token 端點會返回使用者的 ID Token。

授權碼流程介紹

自研應用 SSO 對接採用 OIDC 授權碼模式。您的應用只需要完成與 IDaaS 之間的兩個介面互動(授權端點、令牌端點),即可完成 SSO 的主體流程。登入將由 IDaaS 完全託管,您的應用只需解析登入結果即可。

對接 SSO

  1. 建立自研(或 OIDC 協議)應用

    您需要在 IDaaS 中建立一個自研應用或標準協議(OIDC)應用,並擷取應用密鑰。若您已經擷取到,可以跳過這個步驟。請參考自研應用完成應用建立。

    在該應用的通用配置標籤頁中,即可擷取到 client_idclient_secret。這對密鑰將用於後續介面請求。

    若希望對密鑰進行管理或替換,請參考應用的 基本配置

  2. 請求授權端點 Authorization Endpoint

    下面步驟需要應用開發人員處理。

    在使用者嘗試訪問您的應用時,應用需要判斷當前是否有可用的已登入身份。

    若使用者需要登入,您需要向 IDaaS 發起授權登入的請求。您可以在應用 > 登入訪問標籤頁,下方的應用配置資訊中擷取應用的授權端點。

    請參照如下樣本,在授權端點地址的基礎上,拼裝出完整的授權請求訪問地址,並在瀏覽器中發起 302 跳轉。

    {{授權端點 Authorization Endpoint}}?
      client_id=app_***&
      redirect_uri=http%3A%2F%2Flocalhost%3A3000%2F***&
      response_type=code&
      scope=openid&
      state=525f49cc-***

    欄位

    必填

    樣本

    說明

    client_id

    app_michs7r****6pye

    上一步驟擷取到的 client_id。

    scope

    openid email profile

    自研應用預設配置為 openid email profile,意味著應用可擷取到已登入賬戶的 ID、使用者名稱、郵箱資訊。若您使用 OIDC 標準模板應用,管理員還可在配置頁選擇 phone。scope 對應的欄位範圍,可見文檔末尾說明。

    response_type

    code

    此值固定為 code,代表採用授權碼模式。

    redirect_uri

    http://localhost:3000/user/oauth2/aliyunidaas/callback

    使用者登入完成後,IDaaS 嚮應用返回登入結果的重新導向地址。該地址應該為應用接收 IDaaS 參數的中繼地址,並能接收授權碼 code

    state

    525f49cc-87c4-4655-b79c-4c4f971b1ad1

    state 是由應用產生的隨機字串,建議長度 32 位以上。該 state 值會在後續步驟返回給應用,應用屆時應驗證 state 值是否與發起傳入的一致,以確保同一會話,以規避 XSRF 安全性漏洞。非必填,但強烈建議填寫。

  3. 使用者自助登入

    若請求順利,嚮應用授權端點的請求,會跳轉到 IDaaS 登入頁。

    使用者可以通過任意已配置的登入方式,完成身分識別驗證。IDaaS 提供了多樣的、不同安全層級的登入能力,包括DingTalk掃碼登入、簡訊登入等。詳情參考:通用配置

    登入成功後,瀏覽器會 302 跳回應用指定的 redirect_uri,並在 URL 參數中攜帶 code 和 state 參數。

    參考樣本:

    {{redirect_uri}}?
      code=CO***&
      state=525f49cc-***

    欄位

    樣本

    說明

    code

    COE59pkCTm4A9nmowJUsfsfarGEaiShj3TuDc7NCzLCYu9

    即授權碼。在擷取令牌的請求中使用。

    state

    525f49cc-87c4-4655-b79c-4c4f971b1ad1

    應用接收到後,應確保與調用授權端點時傳入的 state 一致。

  4. 請求令牌端點 Token Endpoint

    上一步接收到 code 授權碼後,應用應使用獲得的 code 向令牌端點(Token Endpoint)發起 POST 請求。

    與上述授權端點一樣,令牌端點也可以在應用 > 登入訪問標籤頁下方的應用配置資訊中擷取到。

    請求樣本如下:

    POST /v2/<instance_id>/<app_id>/oauth2/token HTTP/1.0
    Host: eiam-api-cn-hangzhou.aliyuncs.com
    Content-Type: application/x-www-form-urlencoded
    
    grant_type=authorization_code
      &code=n0esc3N*****5acc3f0ogp4
      &client_id=s6BhdR*****kqt3
      &client_secret=7Fjfp0ZBr1*****KtDRbnfVdmIw
      &redirect_uri=http%3A%2F%2Fwww.example.com%2Fsso%2Fcallback

    響應結果如下:

    {
      "token_type": "Bearer",
      "access_token": "ATM4SoVDqWgUq***********wk3ZS5mtn6fcSp8NH8",
      "expires_in": 1200,
      "expires_at": 1644843164,
      "id_token": "eyJraWQiOiJLRVkyV************gRIadj-frOIRFChA"
    }

    至此,您的使用者已經成功完成登入。您可以選擇以下兩種方式,進一步擷取當前登入身份資訊,並完成應用側登入態的建立:

    1. 使用響應結果中的 id_token,經過驗證後,直接拿到使用者標識。

    2. 使用響應結果中的 access_token,調用 IDaaS 使用者端點,擷取當前已登入使用者資訊。

    具體擷取方式請參考下面章節。

    重要

    可以擷取到的使用者資料範圍,由第一步授權端點請求中的 scope 參數指定。

  5. 通過程式解析id_token

    id_token是一個包含身份資訊的簽名令牌資訊(JWT 格式)。IDaaS 簽發的id_token中,包含著解碼即明文可見的使用者資料,以及簽名Signature。

    為了方便理解,您可以將id_token完整內容,粘貼到JWT 解碼網站中,查看其包含的內容。

    內容樣本如下:

    {
      "kid": "KEY2Ty1qL6u21NGKmccv3jwfd2ndmgtQPnag",
      "alg": "RS256"
    }.{
      "sub": "user_uyvefotjn7kpbejfmxoos3rtmm",
      "jti": "jwt_aaaac7xyhclac6aqkgtjaxsthw5yotn5d77pmki",
      "iss": "https://pre-eiam-api-cn-hangzhou.aliyun-inc.com/v2/********/oidc",
      "iat": 1644841965,
      "nbf": 1644841965,
      "exp": 1644842265,
      "aud": "app_mhylgo3iairjqjdx5eop6uaf34",
      "at_hash": "XHEaGpMooM9zvQXaMzCNEA",
      "name": "testuser",
      "preferred_username": "testuser",
    }.[Signature]

    在使用其中內容進行應用登入前,您需要對簽名 [Signature] 進行驗證,以確保令牌是 IDaaS 簽發,而非任何其他三方,以保障登入的安全性。這一安全性是必須的。

    1. 擷取驗簽公開金鑰

      在進行簽名驗證之前,需要首先獲得 IDaaS 公開的驗簽公開金鑰端點。

      仍然在應用 > 登入訪問標籤頁下方的應用配置資訊中擷取,我們可以看到應用的驗簽公開金鑰端點。

      應用可通過訪問這一端點,擷取到當前的公開金鑰資訊。您也可以直接將此地址在瀏覽器中開啟,即可展示公開金鑰資訊。

      樣本如下:

      {
        "keys": [
          {
            "kty": "RSA",
            "e": "AQAB",
            "use": "sig",
            "kid": "KEYkYnc55G********CTvT7So44RGDYdbfs",
            "n": "pXmYkIpy1vaNjTMclU86BQjfmDhjlqMAX8ySVvh9gO-nae4ayvG_*********-v4gP27T7u6bUy0GXTlh3eKE0v1LYB81nfqjF2uazlPwPR5yYOhhWcK-gMNByLfE3CnkDc1YGwA3dZmIz-ZjOCKy8xLaBuqjrvwn5tpMpAoYEEaH4jIm7unTdhbKEKspNR-UXKD8q9RppMh5Tn2sB6oPHlQANudJDgqSwEOevIrdmHU0Zqxrb9cscGH9hH0QjmYEu72yI8BVeliPo3jK6JIoqCIcj5K_t8BJlFQ9QLJ8_o9tmd3BFv5_LVsh4BKGw"
          }
        ]
      }

      接下來您即可以通過程式碼完成驗簽並擷取 id_token 中的內容了。

    2. 驗簽並登入

      您可以通過JWT官方庫列表找到對應語言的工具,並在代碼中使用工具對 id_token 進行驗簽與解析。

      下面以 Java 庫:org.bitbucket.b_c:jose4j 為例。

      首先加入對應的 Maven 依賴:

      <dependency>
        <groupId>org.bitbucket.b_c</groupId>
        <artifactId>jose4j</artifactId>
        <version>0.7.12</version>
      </dependency>

      程式碼範例如下:

      import org.jose4j.jwk.JsonWebKey;
      import org.jose4j.jwk.JsonWebKeySet;
      import org.jose4j.jwt.JwtClaims;
      import org.jose4j.jwt.consumer.JwtConsumer;
      import org.jose4j.jwt.consumer.JwtConsumerBuilder;
      
      public class IdTokenTest {
      
          public static void main(String[] args) throws Exception {
              // EIAM的Issuer標識(OIDC簽發者URL)
              String issuer = "https://eiam-api-cn-hangzhou.aliyuncs.com/v2/idaas_padyrlux3mphrlsex4uonyqhxu/**********/oidc";
              // 當前應用的唯一標識(從EIAM擷取)
              String appId = "app_mkif4*****pxpzbasqmu";
              // 請參照如下方式,設定解析用的應用公開金鑰(JSON格式,用於驗證JWT簽名)
              String jwkJson = "{\n" + "  \"keys\": [\n" + "    {\n" + "      \"kty\": \"RSA\",\n" + "      \"e\": \"AQAB\",\n" + "      \"use\": \"sig\",\n" + "      \"kid\": \"KEY2H82C2at57itnW4onT3p1ySjwH4nirjCk\",\n" + "      \"n\": \"w7Jl3fAUJp_9GuxV*****QsOA4lnXR5OD4kF4QbIeBiDiH8_MThrFi9k2MB6YMkSzf5JfIkpAS3JCqZ7k6Wooydp4pzaZNZAk3SGzdsa022RmAT" + "-Iayi4Yj6J9tSdTQCjwh2XkzzsIxA_Hla8rWiQ8Vhw1" +
                  "-7QArgObfe67nSR7LxD55MFLxk9FU0*****RlGhrQGE_0LUuGWtCJG1r1e6aKquyswfxxAr3Rvj8QGIeJrG0R1Pv8m8d1_5OdULhB7149VqjM6D98WFjab0U2SNv0UlREZXTcS4p-2QNm_1egYRRpJEY_00FZqNSYsmErMGepYhO_61KoGqd8cphWQ\"\n" + "    }\n" + "  ]\n" + "}";
              String jwt = "eyJraWQiOiJLRVkySDgyQzJhdD*****uaXJqQ2siLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2Vy*****lmNjRjZjR3amFrbnBieGpjd3V1IiwianRpIjoiand0X2FhYWFkYWllYTc2eWg1cW0zcm11bnoyeGg0eHd5aTJzZHBoNjR6aSIsImlzcyI6Imh0dHBzOi8vZWlhbS1hcGktY24taGFuZ3pob3UuYWxpeXVuY3MuY29tL3YyL2lkYWFzX3BhZHlybHV4M21waHJsc2V4NHVvbnlxaHh1L2FwcF9ta2lmNGR3bHBlaDZkbnM0cHhwemJhc3FtdS9vaWRjIiwiaWF0IjoxNjUzNjMwMDQxLCJuYmYiOjE2NTM2MzAwNDEsImV4cCI6MTY1MzYzMDM0MSwiYXVkIjoiYXBwX21raWY0ZHdscGVoNmRuczRweHB6YmFzcW11IiwibmFtZSI6InRlc3QiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ0ZXN0IiwidXBkYXRlZF9hdCI6MTY1MzYyODU5MH0.pAsUNB8OkdpIxJMZRfLJ7Pa31tsJyl44a1jVIlvdQxwOtPULAwrFxnB0X3eQx89hUGCdvYWl9FO9o-5kT7L-RER0wJYz9YNKqrVNBnaRwINRZyeYLRVurWMMzODQz-V0ULd9raM1M_i2f_SoWFs1gPFtYh_ijUARHISi7Q3q93ZfAuY8Lq2Nq07QunmDbosvioUd5wJG7WCxW5XXZYDUQe9p5IEYd1MSvnWuTOLbg7rKn0Vm4dNYGWjz1WuoAyCsc_QxOCqpmQ_2czoqPeN-SvPJAQ2CykLk7DSnGpABw1aNrjDidLS9Beqsga9VDCth86sk_0lyTZOaORtUrfVTtQ";
              //解析JWK公開金鑰集合
              JsonWebKeySet jsonWebKeySet = new JsonWebKeySet(jwkJson);
              //建立JWT驗證器
              JwtConsumer jwtConsumer = createJwtConsumer(jsonWebKeySet, issuer, appId);
              //執行驗簽並解析claims
              JwtClaims jwtClaims = jwtConsumer.processToClaims(jwt);
              // 已經驗簽完成,列印輸出 id_token 中包含的使用者資訊
              System.out.println(jwtClaims);
          }
      
          // 驗簽工具方法
          public static JwtConsumer createJwtConsumer(JsonWebKeySet jsonWebKeySet, String issuer, String appId) {
              // 使用建造者模式配置JWT驗證器
              final JwtConsumerBuilder jwtConsumerBuilder = new JwtConsumerBuilder();
              // 必須驗證的欄位配置
              jwtConsumerBuilder.setExpectedIssuer(issuer);
              jwtConsumerBuilder.setRequireIssuedAt();
              jwtConsumerBuilder.setRequireExpirationTime();
              jwtConsumerBuilder.setAllowedClockSkewInSeconds(60);
              jwtConsumerBuilder.setExpectedAudience(appId);
              // 設定公開金鑰解析器(通過kid匹配JWK)
              jwtConsumerBuilder.setVerificationKeyResolver((jws, nestingContext) - > {
                  // 從JWT頭部擷取kid
                  final String signKeyId = jws.getKeyIdHeaderValue();
                  // 遍曆JWK集合尋找匹配的密鑰
                  for (JsonWebKey jsonWebKey: jsonWebKeySet.getJsonWebKeys()) {
                      if (signKeyId.equals(jsonWebKey.getKeyId())) {
                          return jsonWebKey.getKey();
                      }
                  }
                  throw new RuntimeException("Cannot find verification key: " + signKeyId);
              });
              // 構建驗證器執行個體
              return jwtConsumerBuilder.build();
          }
      }
      重要

      請將代碼中的issuer、appId、jwkJson和jwt部分替換為您從EIAM擷取的實際資訊。

      輸出樣本如下:

      JWT Claims Set:{sub=user_dt6kj6yf64cf4wjaknpbxjcwuu, 
                      jti=jwt_aaaadaiea76yh5qm3rmunz2xh4xwyi2sdph64zi, 
                      iss=https://eiam-api-cn-hangzhou.aliyuncs.com/v2/idaas_padyrlux3mphrlsex4uonyqhxu/app_**********/oidc, 
                      iat=1653630041, 
                      nbf=1653630041, 
                      exp=1653630341, 
                      aud=app_**********, 
                      name=test, 
                      preferred_username=test, 
                      updated_at=1653628590
                     }

      由此擷取到 IDaaS 中已登入身份資訊,應用可用其順利登入。

  6. 通過UserInfo端點擷取使用者資訊

    除了可以通過解析 id_token 擷取使用者資訊外,還可以通過 UserInfo 端點(使用者資訊端點)擷取使用者資訊。

    仍然在應用 > 登入訪問標籤頁下方的應用配置資訊中擷取,我們可以看到應用的驗簽公開金鑰端點。

    UserInfo請求遵循標準 RFC6750,請求樣本如下:

    GET /v2/<instance_id>/<app_id>/oauth2/userinfo HTTP/1.0
    Host: eiam-api-cn-hangzhou.aliyuncs.com
    Authorization: Bearer <AccessToken>
    
    返回參數樣本:
    {
        "sub": "user_dt6kj6yf64cf4wjaknpbxjcwuu",
        "name": "test",
        "preferred_username": "test",
        "updated_at": 1653899948
    }
    說明

    UserInfo 端點返回的業務字元與 id_token 中的欄位是保持一致的,即在 “擴充 id_token” 中配置的欄位也會在 UserInfo 端點中返回。

其他進階設定

若您對 OIDC 協議有深入瞭解,您可能會用到下列概念或能力,供參考。

  1. OIDC Discovery 應用發現端點說明

    OIDC 應用的 issuer 是對令牌發行方(即 IDaaS)的唯一標識,格式如下:

    https://<idaas-api-domain>/v2/<instance_id>/<application_id>/oidc

    角括弧中的參數如下:

    欄位

    說明

    樣本

    idaas-api-domain

    使用者門戶地址

    https://******.aliyunidaas.com

    instance_id

    執行個體 ID

    idaas_m********r2ed22e6m

    application_id

    應用 ID

    app_m********jy6rbau

    IDaaS 支援 OpenID Connect Discovery 1.0 標準,在 issuer 後再加上 /.well-known/openid-configuration 就是該應用的 OIDC 發現端點地址。

    通過請求發現端點,您可以自動探索如下端點資訊。所有的請求端點都可以直接從應用配置資訊中擷取。

    端點

    說明

    authorization_endpoint

    授權端點

    device_authorization_endpoint

    裝置模式 需要標準OIDC應用支援該功能,自研應用暫時不支援裝置碼流登入

    token_endpoint

    令牌端點

    revocation_endpoint

    令牌吊銷端點

    userinfo_endpoint

    使用者資訊端點

    jwks_uri

    JWK公開金鑰端點

  2. Scope 與欄位許可權的對應關係

    OIDC 中 id_token 中包含的使用者資訊與 scope 的對應關係如下:

    欄位

    scope

    說明

    sub

    openid

    使用者的 userId

    jti

    openid

    JWT 令牌ID,輔助欄位

    iss

    openid

    JWT 簽發的 issuer,輔助欄位

    iat

    openid

    JWT 簽發時間,輔助欄位

    nbf

    openid

    JWT 令牌有效開始時間,輔助欄位

    exp

    openid

    JWT 令牌到期時間,輔助欄位

    aud

    openid

    即應用的 ClientID,輔助欄位

    at_hash

    openid

    AccessToken 雜湊值,輔助欄位

    phone_number

    phone

    電話號碼,比如 +86 130****5678

    phone_number_verified

    phone

    電話號碼是否被驗證過,目前預設電話號碼是已驗證

    email

    email

    電子郵箱,比如 al***@example.com

    email_verified

    email

    電子郵箱是否被驗證過,目前預設電子郵箱是已驗證

    name

    profile

    使用者顯示名稱

    preferred_username

    profile

    使用者的 username

    updated_at

    profile

    使用者資料最後更新時間

  3. 令牌端點支援的認證方式

    根據 OIDC 協議指明,IDaaS 提供靈活性,允許以下 4 種不同方式進行身分識別驗證。

    在發現端點中返回的欄位 token_endpoint_auth_methods_supported 指定了支援的認證方法。

    取值

    說明

    none

    用於 Public 用戶端,通過 none 認證方式認證時 grant_type 不能是 client_credentials

    client_secret_basic

    按規範 RFC 6749 - The OAuth 2.0 Authorization Framework 實現

    client_secret_post

    按規範 RFC 6749 - The OAuth 2.0 Authorization Framework 實現

    client_secret_jwt

    按規範 OpenID Connect Core 1.0 實現

    上一步接收到 code 授權碼,並驗證請求合法(驗證 state 與發起請求傳入的一致)後,應用的後端服務,應使用獲得的 code 向令牌端點(Token Endpoint)發起 POST 請求,請求樣本如下:

    client_secret_basic 為例,令牌端點請求範例為:

    POST /token HTTP/1.0
    Host: api.aliyunidaas.com
    Authorization: Basic YXBwX21pY2hzN3I0*******cHllOkNTKioqKioq
    
    grant_type=authorization_code&
    code=COE59pkCTm4J*******arGEaiShj7NCzLCYu9

    更多說明參看 OIDC Core 1.0 規範。

  4. 應用 Client Secret 輪轉

    請參考 基本配置 中密鑰輪轉章節說明。

相關標準