全部產品
Search
文件中心

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

更新時間:Jun 30, 2024

1. 背景介紹

IDaaS 採用標準的 OIDC 協議授權碼模式來支援常規企業自研應用接入。

如果您對接過微信登入,會發現對接 SSO 和對接微信掃碼登入是一個道理。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 完全託管,您的應用只需解析登入結果即可。

a

2. 對接 SSO

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

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

請管理員參考 3. 自研應用 完成應用建立。

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

秘鑰

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

2.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 安全性漏洞。非必填,但強烈建議填寫。

2.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 一致。

2.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

欄位

必填

樣本

說明

grant_type

authorization_code

固定填寫 authorization_code。

code

n0es***5acc3f0ogp4

即上一步中返回的授權碼 code。

client_id

app_mihar***s3rj7r4e4

IDaaS 預設支援兩種介面認證方式,以驗證調用方可信。

client_secret_post 模式下,需要將 client_id, client_secret 作為 POST 參數傳遞給 IDaaS,進行調用方身分識別驗證。請注意,若您通過 Postman 等工具驗證,請確保您選擇 form-data 格式。這是預設。

若您需要使用另一種 client_secret_basic 模式,請您按照規範傳遞即可,在此不多介紹。

client_secret

CSAuycr***vtqRozS1V1

同上。

redirect_uri

http%3A%2F%2Fwww.example.com%2Fsso%2Fcallback

即上一步使用的 redirect_uri,按照協議要求,必須重新傳入以保障請求連續性。

響應結果如下:

{
  "token_type": "Bearer",
  "access_token": "ATM4SoVDqWgUJHLu3Bg6qF2hccE6cvjKXiKdiJ2Dc8RJZSbzpBDXPZK3gPhGxQs16s3s7MsZ46fEyiYTWG7EGFKi9uzGjRALaRLecPutBLzzQQRVUt6pbuarCbq5hFRje6bzsrW4jTehhCtZM5JneEfcSQ2ViSDVZGNNtMKAA6v7kTeubZrTaWNzosNMyzGXoD4rqPBwF9FsYqwACQ4aJrt9NnS3NpgDKoMtqEQs5TfDsCYMKYmp7Z73F2BJz89jzN1utEbnuj3HnvyRQPCismDiXjS8EPvoUZBrUBMhrnzYmMcT9KmzKoC12sQjDRQYqgPVxQyMKwQKwwHWXV7stEXnoSt524GW8HVrF3WRsM2N1Ykod1irCz7ZasSwk3ZS5mtn6fcSp8NH8",
  "expires_in": 1200,
  "expires_at": 1644843164,
  "id_token": "eyJraWQiOiJLRVkyVHkxcUw2dTIxTkdLbWNjdjNqd2ZkMm5kbWd0UVBuYWciLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyX3V5dmVmb3RqbjdrcGJlamZteG9vczNydG1tIiwianRpIjoiand0X2FhYWFjN3h5aGNsYWM2YXFrZ3RqYXhzdGh3NXlvdG41ZDc3cG1raSIsImlzcyI6Imh0dHBzOi8vcHJlLWVpYW0tYXBpLWNuLWhhbmd6aG91LmFsaXl1bi1pbmMuY29tL3YyL2lkYWFzX2JxZ2xkdnpwcGEyYWw2aTZhYzVxemphcWpxL2FwcF9taHlsZ28zaWFpcmpxamR4NWVvcDZ1YWYzNC9vaWRjIiwiaWF0IjoxNjQ0ODQxOTY1LCJuYmYiOjE2NDQ4NDE5NjUsImV4cCI6MTY0NDg0MjI2NSwiYXVkIjoiYXBwX21oeWxnbzNpYWlyanFqZHg1ZW9wNnVhZjM0IiwiYXRfaGFzaCI6IlhIRWFHcE1vb005enZRWGFNekNORUEifQ.abebHwoSzOi92-QOKO_E38jyfxjzVLpRIK858UsOehe_GzBoKOEl1zQOSljBB7CwZCdQJpqI1rUxqQopwjvHRSfA-O4_cc4sXDpZYXodeVRXUiv1kYB1b4gZ-hStcE1eh_5jJj1dpoGPBsjTTHjp43EgDx1-8M-8ePF3zXZAfqxCjjroGgB9qXtSreRAIUh5ODViyHYRSAis7CNdP7jKG1dU1UNSGwXWNyRcgVaCqL05gCh0LhHrutMXDy8pcKzdXHQMMBaHF-rGkkGdlp4q9KqwjkpzakcWieRmPa2UUXLdQgK1Pgzc5F7mE-fvsvVfMYfh_JgRIadj-frOIRFChA"
}

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

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

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

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

重要

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

2.5. 通過程式解析 id_token

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

為了方便理解,您可以將 id_token 完整內容,粘貼到 https://jwt.io 網站中,查看其包含的內容。

內容樣本如下:

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

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

2.5.1. 擷取驗簽公開金鑰

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

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

驗簽端點

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

樣本如下:

{
  "keys": [
    {
      "kty": "RSA",
      "e": "AQAB",
      "use": "sig",
      "kid": "KEYkYnc55GJjD6y7VeCTvT7So44RGDYdbfs",
      "n": "pXmYkIpy1vaNjTMclU86BQjfmDhjlqMAX8ySVvh9gO-nae4ayvG_aCRYQL3qGCpFLZYrG3Jjoa0ktCn8PTTRi-v4gP27T7u6bUy0GXTlh3eKE0v1LYB81nfqjF2uazlPwPR5yYOhhWcK-gMNByLfE3CnkDc1YGwA3dZmIz-ZjOCKy8xLaBuqjrvwn5tpMpAoYEEaH4jIm7unTdhbKEKspNR-UXKD8q9RppMh5Tn2sB6oPHlQANudJDgqSwEOevIrdmHU0Zqxrb9cscGH9hH0QjmYEu72yI8BVeliPo3jK6JIoqCIcj5K_t8BJlFQ9QLJ8_o9tmd3BFv5_LVsh4BKGw"
    }
  ]
}

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

2.5.2. 驗簽並登入

您可以通過 https://jwt.io/libraries 找到對應語言的工具,並在代碼中使用工具對 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 {
        String issuer = "https://eiam-api-cn-hangzhou.aliyuncs.com/v2/idaas_padyrlux3mphrlsex4uonyqhxu/app_mkif4dwlpeh6dns4pxpzbasqmu/oidc";
        String appId = "app_mkif4*****pxpzbasqmu";
        // 請參照如下方式,設定解析用的應用公開金鑰
        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";

        JsonWebKeySet jsonWebKeySet = new JsonWebKeySet(jwkJson);
        JwtConsumer jwtConsumer = createJwtConsumer(jsonWebKeySet, issuer, appId);

        JwtClaims jwtClaims = jwtConsumer.processToClaims(jwt);
        // 已經驗簽完成,列印輸出 id_token 中包含的使用者資訊
        System.out.println(jwtClaims);
    }

    // 驗簽工具方法
    public static JwtConsumer createJwtConsumer(JsonWebKeySet jsonWebKeySet, String issuer, String appId) {
        final JwtConsumerBuilder jwtConsumerBuilder = new JwtConsumerBuilder();
        jwtConsumerBuilder.setExpectedIssuer(issuer);
        jwtConsumerBuilder.setRequireIssuedAt();
        jwtConsumerBuilder.setRequireExpirationTime();
        jwtConsumerBuilder.setAllowedClockSkewInSeconds(60);
        jwtConsumerBuilder.setExpectedAudience(appId);
        jwtConsumerBuilder.setVerificationKeyResolver((jws, nestingContext) -> {
            final String signKeyId = jws.getKeyIdHeaderValue();
            for (JsonWebKey jsonWebKey : jsonWebKeySet.getJsonWebKeys()) {
                if (signKeyId.equals(jsonWebKey.getKeyId())) {
                    return jsonWebKey.getKey();
                }
            }
            throw new RuntimeException("Cannot find verification key: " + signKeyId);
        });
        return jwtConsumerBuilder.build();
    }
}

輸出樣本如下:

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

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

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

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

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

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 端點中返回。

3. 其他進階設定

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

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

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

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

角括弧中的參數如下:

欄位

說明

樣本

idaas-api-domain

使用者門戶地址

https://nfaaacn.aliyunidaas.com

instance_id

執行個體 ID

idaas_maaaaaaaaaaar2ed22e6m

application_id

應用 ID

app_maaaaaaaaaaaaaaajy6rbau

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公開金鑰端點

3.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 1234 5678

phone_number_verified

phone

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

email

email

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

email_verified

email

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

name

profile

使用者顯示名稱

preferred_username

profile

使用者的 username

updated_at

profile

使用者資料最後更新時間

3.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 規範。

3.4. 應用 Client Secret 輪轉

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

相關標準