全部產品
Search
文件中心

Resource Access Management:使用OIDC進行角色SSO的樣本

更新時間:Feb 05, 2024

本文提供一個Okta與阿里雲進行OIDC角色SSO的樣本,使Okta中的應用通過臨時身份憑證(STS Token)安全訪問阿里雲資源。

前提條件

請提前在Okta中註冊一個OIDC應用,並擷取應用的頒發者URL和用戶端ID(Client ID)。本樣本中使用的資料如下:
  • 頒發者URL:https://dev-xxxxxx.okta.com
  • 用戶端ID:0oa294vi1vJoClev****

步驟一:在阿里雲建立OIDC身份供應商

本步驟中將建立一個名為TestOidcProvider的OIDC身份供應商。頒發者URLhttps://dev-xxxxxx.okta.com用戶端ID0oa294vi1vJoClev****

  1. 使用阿里雲帳號登入RAM控制台
  2. 在左側導覽列,選擇整合管理 > SSO管理
  3. 角色SSO頁簽,先單擊OIDC頁簽,然後單擊建立身份供應商
  4. 建立身份供應商頁面,設定身份供應商資訊。
    參數說明
    身份供應商名稱同一個阿里雲帳號下必須唯一。
    頒發者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個。

    備忘身份供應商的描述資訊。
  5. 單擊確定

步驟二:在阿里雲建立可信實體為OIDC身份供應商的RAM角色

本步驟中將建立一個名為testoidc的RAM角色,身份供應商選擇步驟一建立的TestOidcProvider

  1. 使用Resource Access Management員登入RAM控制台

  2. 在左側導覽列,選擇身份管理 > 角色

  3. 角色頁面,單擊建立角色

  4. 建立角色頁面,選擇可信實體類型為身份供應商,然後單擊下一步

  5. 輸入角色名稱備忘

  6. 選擇身份供應商類型為OIDC

  7. 選擇身份供應商並設定限制條件。

    支援的限制條件如下表所示:

    限制條件關鍵字

    說明

    是否必選

    樣本

    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****

  8. 單擊完成

  9. 單擊關閉

步驟三:為RAM角色授權

您可以根據實際需要,為步驟二建立的RAM角色testoidc授予訪問阿里雲資源的許可權。

  1. 使用Resource Access Management員登入RAM控制台

  2. 在左側導覽列,選擇身份管理 > 角色

  3. 角色頁面,單擊目標RAM角色操作列的新增授權

    您也可以選中多個RAM角色,單擊角色列表下方的新增授權,為RAM角色大量授權。

  4. 新增授權頁面,為RAM角色授權。

    1. 選擇授權範圍。

      • 整個雲帳號:許可權在當前阿里雲帳號內生效。

      • 指定資源群組:許可權在指定的資源群組內生效。

        說明

        指定資源群組授權生效的前提是該雲端服務已支援資源群組。更多資訊,請參見支援資源群組的雲端服務

    2. 指定授權主體。

      授權主體即需要授權的RAM角色,系統會自動填入當前的RAM角色,您也可以添加其他RAM角色。

    3. 選擇權限原則。

      說明

      每次最多綁定5條策略,如需綁定更多策略,請分次操作。

  5. 單擊確定

  6. 單擊完成

步驟四:在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的流程,其中簡化了標準協議要求的部分步驟。

  1. 搭建一個用戶端Web應用,用於接收Okta頒發的OIDC Token。
    本樣本中,將提供一個使用Java Spring Boot和Thymeleaf搭建的極簡用戶端Web應用。在本機8080連接埠部署Web應用,綁定的localhost指向127.0.0.1,因此在本機通過瀏覽器訪問localhost:8080就可以訪問到該Web應用。相關的範例程式碼如下:
    • 靜態頁面範例程式碼

      按照OAuth 2.0協議要求Okta回調給用戶端Web應用的資訊是通過錨點(fragment)來傳遞的,您可以通過一個Web頁面,直接提取出錨點參數來擷取回調的OIDC Token。假設您製作了如下這個簡單的靜態頁面,直接進行參數透傳。該頁面的完整地址為http://localhost:8080/accessTokenCallback,也就是Okta應用配置的回調地址redirect_uri

      <!DOCTYPE HTML>
      <html xmlns:th="http://www.thymeleaf.org">
          <head>
              <script>
                  window.onload = function () {
                      let fragment = window.location.hash.substring(1);
                      window.location.href = "/receiveAccessToken?" + fragment;
                  };
              </script>
          </head>
      </html>
    • 類範例程式碼

      建立一個類,作為上述靜態頁面的控制器。

      package com.aliyun.oauthtest;
      
      import org.springframework.stereotype.Controller;
      import org.springframework.web.bind.annotation.RequestMapping;
      
      @Controller
      public class CallbackController {
          @RequestMapping("accessTokenCallback")
          public String callback() {
              return "accessTokenCallback";
          }
      }
  2. 登入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_tokenid_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
  3. 解析OIDC Token。

    您可以對步驟2擷取的結果進行簡單地解析,將headerpayload展開。

    請求樣本:

    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****"; //從Okta擷取的未解析的id_token。
    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訪問有許可權的阿里雲資源。