1. 背景介紹
IDaaS 采用標準的 OIDC 協議授權碼模式來支持常規企業自研應用接入。
如果您對接過微信登錄,會發現對接 SSO 和對接微信掃碼登錄是一個道理。釘釘、微信等社交身份均采用 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 提供了多樣的、不同安全級別的登錄能力,包括釘釘掃碼登錄、短信登錄等。詳情參考:登錄方式。
登錄成功后,瀏覽器會 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