前提條件
請提前在Okta中註冊一個OIDC應用,並擷取應用的頒發者URL和用戶端ID(Client ID)。本樣本中使用的資料如下:- 頒發者URL:https://dev-xxxxxx.okta.com
- 用戶端ID:0oa294vi1vJoClev****
步驟一:在阿里雲建立OIDC身份供應商
本步驟中將建立一個名為TestOidcProvider
的OIDC身份供應商。頒發者URL為https://dev-xxxxxx.okta.com
,用戶端ID為0oa294vi1vJoClev****
。
- 使用阿里雲帳號登入RAM控制台。
- 在左側導覽列,選擇。
- 在角色SSO頁簽,先單擊OIDC頁簽,然後單擊建立身份供應商。
- 在建立身份供應商頁面,設定身份供應商資訊。
參數 | 說明 |
身份供應商名稱 | 同一個阿里雲帳號下必須唯一。 |
頒發者URL | 頒發者URL由外部IdP提供。頒發者URL必須以https 開頭,符合標準URL格式,但不允許帶有query參數(以? 標識)、fragment片段(以# 標識)和登入資訊(以@ 標識)。 |
驗證指紋 | 為了防止頒發者URL被惡意劫持或篡改,您需要配置外部IdP的HTTPS CA認證產生的驗證指紋。阿里雲會輔助您自動計算該驗證指紋,但是建議您在本地自己計算一次(例如:使用OpenSSL計算指紋),與阿里雲計算的指紋進行對比。如果對比發現不同,則說明該頒發者URL可能已經受到攻擊,請您務必再次確認,並填寫正確的指紋。 |
用戶端ID | 您的應用在外部IdP註冊的時候,會產生一個用戶端ID(Client ID)。當您從外部IdP申請簽發OIDC令牌時必須使用該用戶端ID,簽發出來的OIDC令牌也會通過aud 欄位攜帶該用戶端ID。在建立OIDC身份供應商時配置該用戶端ID,然後在使用OIDC令牌換取STS Token時,阿里雲會校正OIDC令牌中aud 欄位所攜帶的用戶端ID與OIDC身份供應商中配置的用戶端ID是否一致。只有一致時,才允許扮演角色。如果您有多個應用需要訪問阿里雲,您可以配置多個用戶端ID,但最多不能超過20個。 |
備忘 | 身份供應商的描述資訊。 |
- 單擊確定。
步驟二:在阿里雲建立可信實體為OIDC身份供應商的RAM角色
本步驟中將建立一個名為testoidc
的RAM角色,身份供應商選擇步驟一建立的TestOidcProvider
。
使用Resource Access Management員登入RAM控制台。
在左側導覽列,選擇。
在角色頁面,單擊建立角色。
在建立角色頁面,選擇可信實體類型為身份供應商,然後單擊下一步。
輸入角色名稱和備忘。
選擇身份供應商類型為OIDC。
選擇身份供應商並設定限制條件。
支援的限制條件如下表所示:
限制條件關鍵字 | 說明 | 是否必選 | 樣本 |
oidc:iss | OIDC頒發者(Issuer)。用來扮演角色的OIDC令牌中的iss欄位值必須滿足該限制條件要求,角色才允許被扮演。 該限定條件必須使用StringEquals作為條件操作類型,條件值只能是您在OIDC身份供應商中填寫的頒發者URL。該限制條件用於確保只有受信頒發者頒發的OIDC令牌才能扮演角色。 | 是 | https://dev-xxxxxx.okta.com |
oidc:aud | OIDC受眾(Audience)。用來扮演角色的OIDC令牌中的aud欄位值必須滿足該限制條件要求,角色才允許被扮演。 該限定條件必須使用StringEquals作為條件操作類型,您可選擇在OIDC身份供應商中配置的一個或多個用戶端ID(Client ID)作為條件值。該限制條件用於確保只有您設定的Client ID產生的OIDC令牌才能扮演角色。 | 是 | 0oa294vi1vJoClev**** |
oidc:sub | OIDC主體(Subject)。用來扮演角色的OIDC令牌中的sub欄位值必須滿足該限制條件要求時,角色才允許被扮演。 該限定條件可以使用任何String類的條件操作類型,且您可以最多設定10個OIDC主體作為條件值。該限制條件用於進一步限制允許扮演角色的身份主體,您也可以不指定該限制條件。 | 否 | 00u294e3mzNXt4Hi**** |
單擊完成。
單擊關閉。
步驟三:為RAM角色授權
您可以根據實際需要,為步驟二建立的RAM角色testoidc
授予訪問阿里雲資源的許可權。
使用Resource Access Management員登入RAM控制台。
在左側導覽列,選擇。
在角色頁面,單擊目標RAM角色操作列的新增授權。
您也可以選中多個RAM角色,單擊角色列表下方的新增授權,為RAM角色大量授權。
在新增授權頁面,為RAM角色授權。
選擇授權範圍。
整個雲帳號:許可權在當前阿里雲帳號內生效。
指定資源群組:許可權在指定的資源群組內生效。
指定授權主體。
授權主體即需要授權的RAM角色,系統會自動填入當前的RAM角色,您也可以添加其他RAM角色。
選擇權限原則。
說明
每次最多綁定5條策略,如需綁定更多策略,請分次操作。
單擊確定。
單擊完成。
步驟四:在Okta簽發OIDC令牌(OIDC Token)
阿里雲不支援使用OIDC登入控制台,所以您需要使用程式訪問的方式完成OIDC SSO流程。由於產生OIDC Token本質上是個OAuth流程,所以您需要通過標準的OAuth 2.0流程從OIDC IdP(例如:Okta)擷取OIDC Token。OAuth支援多種流程,例如:比較常見的Authorization Code Flow。但由於該流程較為複雜,為示範方便,如下將以比較簡單的Implicit Flow為例,為您介紹擷取OIDC Token並最終完成SSO的流程,其中簡化了標準協議要求的部分步驟。
- 搭建一個用戶端Web應用,用於接收Okta頒發的OIDC Token。
本樣本中,將提供一個使用Java Spring Boot和Thymeleaf搭建的極簡用戶端Web應用。在本機8080連接埠部署Web應用,綁定的localhost指向127.0.0.1,因此在本機通過瀏覽器訪問localhost:8080就可以訪問到該Web應用。相關的範例程式碼如下:
- 登入Okta,向Okta申請簽發OIDC Token。
您需要先登入Okta,然後基於步驟
1搭建的用戶端Web應用,直接構造並訪問URL:
https://dev-xxxxxx.okta.com/oauth2/v1/authorize?client_id=0oa294vi1vJoClev****&scope=openid&response_type=token%20id_token&state=testState&nonce=a_unique_nonce_1&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2FaccessTokenCallback
參數含義如下:
client_id
:Okta中註冊的OIDC應用的用戶端ID。scope
:取值為openid
。response_type
:Implicit Flow流程中取值為token id_token
。state
:表示用戶端的目前狀態,可以指定任意值。nonce
:防止重放攻擊,可以指定任意值。redirect_uri
:接收access_token
或id_token
的回調地址,即步驟1中的用戶端Web應用的地址。
本樣本中已經預先登入了Okta,所以系統會根據使用者佈建的redirect_uri
重新導向到回調地址。如下地址中的id_token
就是OIDC Token。
HTTP/1.1 302 Found
Location: http://localhost:8080/accessTokenCallback#id_token=eyJraWQiOiJ6OUV0e****&access_token=eyJraWQiOiJseEQ3R****&token_type=Bearer&expires_in=3600&scope=openid&state=testState
- 解析OIDC Token。
您可以對步驟2擷取的結果進行簡單地解析,將header
和payload
展開。
請求樣本:
package com.aliyun.oauthtest;
import java.util.Base64;
import java.util.Base64.Decoder;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ClientAppController {
@RequestMapping(value = "/receiveAccessToken", method = {RequestMethod.POST, RequestMethod.GET},
produces = "application/json")
public Map<String, Object> receiveAccessToken(@RequestParam("access_token") String accessToken,
@RequestParam("id_token") String idToken,
@RequestParam("token_type") String tokenType,
@RequestParam("expires_in") Long expireTime,
@RequestParam("scope") String scope,
@RequestParam("state") String state)
{
Map<String, Object> result = new TreeMap<>();
result.put("access_token", accessToken);
result.put("id_token", idToken);
result.put("token_type", tokenType);
result.put("expires_in", "" + expireTime);
result.put("scope", scope);
result.put("state", state);
String[] jwt = idToken.split("\\.");
Decoder decoder = Base64.getDecoder();
result.put(" id token jwt header", JSON.parse(new String(decoder.decode(jwt[0]))));
result.put(" id token jwt payload", JSON.parse(new String(decoder.decode(jwt[1]))));
result.put(" id token jwt signature", jwt[2]);
return result;
}
}
返回樣本:
{
" id token jwt header": {
"kid": "z9EtyT345d-JLIJo2-5ySDO27LG4FPeOotbwJPT****",
"alg": "RS256"
},
" id token jwt payload": {
"at_hash": "KKsdN3prZWTvBEMn-g****",
"sub": "00u294e3mzNXt4Hi****",
"aud": "0oa294vi1vJoClev****",
"ver": 1,
"idp": "0oa294iehxjUCZIO****",
"amr": [
"pwd"
],
"auth_time": 1636373097,
"iss": "https://dev-xxxxxx.okta.com",
"exp": 1636377759,
"iat": 1636374159,
"nonce": "a_unique_nonce_1",
"jti": "ID.lmSU5AD2iKLCVu6_KLMIr52dpCprncxW38v-NCA****"
},
"id token jwt signature": "ZEJEGIv4Zoau63****",
"access_token": "eyJraWQiOiJseEQ3R****",
"expires_in": "3600",
"id_token": "eyJraWQiOiJ6OUV0e****",
"scope": "openid",
"state": "testState",
"token_type": "Bearer"
}
步驟五:使用OIDC Token換取STS Token
您可以直接調用AssumeRoleWithOIDC API,使用從步驟四擷取的未解析的OIDC Token換取STS Token。
請求樣本:
public static void main(String[] args)
{
IAcsClient client = initialization();
String jwtToken = "eyJraWQiOiJ6OUV0e****";
AssumeRoleWithOIDCRequest request = new AssumeRoleWithOIDCRequest();
request.setDurationSeconds(3600L);
request.setOIDCProviderArn("acs:ram::113511544585****:oidc-provider/TestOidcProvider");
request.setOIDCToken(jwtToken);
request.setRoleArn("acs:ram::113511544585****:role/testoidc");
request.setRoleSessionName("TestOidcAssumedRoleSession");
try
{
AssumeRoleWithOIDCResponse resp = client.getAcsResponse(request);
System.out.println("success requestId: " + resp.getRequestId());
System.out.println("success assume role arn: " + resp.getAssumedRoleUser().getArn());
System.out.println("success sts credential accessKey id: " + resp.getCredentials().getAccessKeyId());
System.out.println("success sts credential accessKey secret: " + resp.getCredentials().getAccessKeySecret());
System.out.println("success resp: " + JSON.toJSONString(resp));
}
catch(ClientException | SystemException e)
{
e.printStackTrace();
}
}
返回樣本:
success requestId: 3D57EAD2-8723-1F26-B69C-F8707D8B565D
success assume role arn: acs:ram::113511544585****:role/testoidc/TestOidcAssumedRoleSession
success sts credential accessKey id: STS.NUgYrLnoC37mZZCNnAbez****
success sts credential accessKey secret: CVwjCkNzTMupZ8NbTCxCBRq3K16jtcWFTJAyBEv2****
success resp:
{
"AssumedRoleUser":
{
"Arn": "acs:ram::113511544585****:role/testoidc/TestOidcAssumedRoleSession",
"AssumedRoleId": "33157794895460****:TestOidcAssumedRoleSession"
},
"Credentials":
{
"AccessKeyId": "STS.NUgYrLnoC37mZZCNnAbez****",
"AccessKeySecret": "CVwjCkNzTMupZ8NbTCxCBRq3K16jtcWFTJAyBEv2****",
"Expiration": "2021-10-20T04:27:09Z",
"SecurityToken": "CAIShwJ1q6Ft5B2yfSjIr****"
},
"OIDCTokenInfo":
{
"ClientIds": "0oa294vi1vJoClev****",
"Issuer": "https://dev-xxxxxx.okta.com",
"Subject": "00u294e3mzNXt4Hi****"
},
"RequestId": "3D57EAD2-8723-1F26-B69C-F8707D8B565D"
}
其中Credentials
中的資訊即為STS Token。
步驟六:使用STS Token訪問阿里雲資源
使用從步驟五擷取的STS Token訪問有許可權的阿里雲資源。