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 完全託管,您的應用只需解析登入結果即可。
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 參數的中繼地址,並能接收授權碼 |
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 | 應用接收到後,應確保與調用授權端點時傳入的 |
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 | 是 | 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"
}
至此,您的使用者已經成功完成登入。您可以選擇以下兩種方式,進一步擷取當前登入身份資訊,並完成應用側登入態的建立:
使用響應結果中的 id_token,經過驗證後,直接拿到使用者標識。
使用響應結果中的 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請求遵循標準 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 |
|
token_endpoint | 令牌端點 |
revocation_endpoint | 令牌吊銷端點 |
userinfo_endpoint | 使用者資訊端點 |
jwks_uri | JWK公開金鑰端點 |
3.2. Scope 與欄位許可權的對應關係
OIDC 中 id_token 中包含的使用者資訊與 scope 的對應關係如下:
欄位 | scope | 說明 |
sub | openid | 使用者的 |
jti | openid | JWT 令牌ID,輔助欄位 |
iss | openid | JWT 簽發的 |
iat | openid | JWT 簽發時間,輔助欄位 |
nbf | openid | JWT 令牌有效開始時間,輔助欄位 |
exp | openid | JWT 令牌到期時間,輔助欄位 |
aud | openid | 即應用的 ClientID,輔助欄位 |
at_hash | openid | AccessToken 雜湊值,輔助欄位 |
phone_number | phone | 電話號碼,比如 |
phone_number_verified | phone | 電話號碼是否被驗證過,目前預設電話號碼是已驗證 |
電子郵箱,比如 | ||
email_verified | 電子郵箱是否被驗證過,目前預設電子郵箱是已驗證 | |
name | profile | 使用者顯示名稱 |
preferred_username | profile | 使用者的 |
updated_at | profile | 使用者資料最後更新時間 |
3.3. 令牌端點支援的認證方式
根據 OIDC 協議指明,IDaaS 提供靈活性,允許以下 4 種不同方式進行身分識別驗證。
在發現端點中返回的欄位 token_endpoint_auth_methods_supported
指定了支援的認證方法。
取值 | 說明 |
none | 用於 Public 用戶端,通過 |
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 輪轉
請參考 基本配置 中密鑰輪轉章節說明。
相關標準
RFC6749 - The OAuth 2.0 Authorization Framework
RFC6750 - The OAuth 2.0 Authorization Framework: Bearer Token Usage
RFC7009 - OAuth 2.0 Token Revocation
RFC7515 - JSON Web Signature (JWS)
RFC7517 - JSON Web Key (JWK)
RFC7518 - JSON Web Algorithms (JWA)
RFC7519 - JSON Web Token (JWT)
RFC7636 - Proof Key for Code Exchange by OAuth Public Clients
RFC8252 - OAuth 2.0 for Native Apps
RFC8628 - OAuth 2.0 Device Authorization Grant
OpenID Connect Core 1.0
OpenID Connect Discovery 1.0