用於 OAuth 用戶端授權許可的 JSON Web 記號 (JWT)

「用於 OAuth 用戶端授權許可的 JWT」可讓用戶端傳送已簽署的 JWT 記號給「OpenID Connect 提供者」,來交換 OAuth 2.0 存取記號。

「用於 OAuth 用戶端授權許可的 JWT」包含在 openidConnectServer-1.0 特性中。 它可讓用戶端傳送已簽署的 JWT 記號給「OpenID Connect 提供者」,來交換 OAuth 2.0 存取記號。

此功能的範例用法實務可能是,電子公司的客戶授權每月從線上銀行自動付款。 假設電子公司和線上銀行為了履行這類要求,已建立信任關係。 電子公司可以將含有適當聲明的已簽署 JWT 記號,傳送到配置給線上銀行之「OpenID Connect 提供者」的記號端點 URI,以便每個月要求 OAuth 2.0 存取記號。 之後電子公司可以使用存取記號,每月從線上銀行現金付款。

配置成「OpenID Connect 提供者」的 Liberty 伺服器支援部分 JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants 規格。 如果使用者想支援 JWT 用戶端功能,必須使用其自己的應用程式來達到此目的。

授權範圍

「OpenID Connect 用戶端」傳送含有 JWT 的 HTTPS 要求給「OpenID Connect 提供者」的記號端點,以要求存取記號。 在這項程序期間,使用者不會看到關於授權使用範圍的同意表單。 JWT 處理程式會根據下列準則,來處理授權範圍:

  1. 如果要求中沒有指定 scope 參數,「OpenID Connect 提供者」不會在存取記號中指定任何範圍。
  2. 當「OpenID Connect 用戶端」有資格作為「OpenID Connect 提供者」配置中的 autoAuthorized 用戶端,會將用戶端在要求中指定的任何範圍,指定在存取記號的範圍清單中。
  3. 當「OpenID Connect 用戶端」沒有資格作為 autoAuthorized 用戶端時,則需要以用戶端配置中的範圍清單來過濾要求中包含的範圍,且該範圍也必須指定在 preAuthorizedScope 清單中。 如果 HTTPS 要求中的範圍位於用戶端配置的 scopepreAuthorizeScope 清單中,則可以將該範圍指定在存取記號的範圍清單中。

當用戶端沒有資格作為 autoAuthorized 用戶端時,必須在用戶端配置中適當配置可包含在存取記號範圍清單中的範圍。 在「OpenID Connect 提供者」的用戶端配置中,scopepreAuthorizedScope 屬性的值中必須包含該範圍。 在顯示的範例中,存取記號的範圍清單中指定了範圍 profileemail,這是因為兩者都包含在 scopepreAuthorizedScope 值清單中。 如果該範圍未列在用戶端配置的 scope 屬性中,則存取記號的範圍清單中會省略它。 如果範圍列在 scope 屬性中,但未列在用戶端配置內的 preAuthorizedScope 清單中,則授權要求會觸發invalid_grant來自「OpenID Connect 提供者」的回應中發生錯誤。


<openidConnectProvider id="OidcConfigSample" oauthProviderRef="OAuthConfigSample" />
<oauthProvider id="OAuthConfigSample" ... >
        ...
        <localStore>            
            <client name="client01" secret="{xor}..."
                    displayname="client01"
                    scope="profile email phone"
                    preAuthorizedScope="profile email"
                    enabled="true"/>
            ...
        </localStore>
    </oauthProvider>

「JSON Web 記號」中的聲明

必須簽署有效的「JSON Web 記號」。 配置成「OpenID Connect 提供者」的 Liberty 伺服器只支援 HMAC-SHA256 作為記號簽署演算法。 每一個「OpenID Connect 用戶端」的簽章金鑰是「OpenID Connect 提供者」用戶端配置中的 secret 屬性。 在顯示的範例中,使用的簽署金鑰會是 "{xor}LDo8LTor"

<client name="client01" displayname="client01" secret="{xor}LDo8LTor" ... />

「OpenID Connect 提供者」也會驗證 JWT 中的下列聲明:

'iss'(發證者)
這是 JWT 中的必要聲明。 iss 聲明必須符合「OpenID Connect 提供者」用戶端配置中的 name 屬性或 redirect 屬性。 在下列範例中,iss 聲明必須符合 client01http://op201406.ibm.com:8010/oauthclient/redirect.jsp
<client name="client01" redirect="http://op201406.ibm.com:8010/oauthclient/redirect.jsp" scope="openid profile email" ... />
'sub'(主體)
這是 JWT 中的必要聲明。 主體的值必須是「OpenID Connect 提供者」伺服器使用者登錄中的有效使用者名稱。
'aud'(對象)
這是 JWT 中的必要聲明。 如果 openidConnectProvider 配置中指定了 issuerIdentifier 屬性,對象聲明的值是 issuerIdentifier 的名稱。 如果 openidConnectProvider 配置中沒有指定 issuerIdentifier 屬性,對象必須是「OpenID Connect 提供者」的記號端點 URI。 在下列範例中,對象聲明的值是 "OpenIDConnectProviderID1"
<openidConnectProvider id="OidcConfigSample" oauthProviderRef="OAuthConfigSample" issuerIdentifier="OpenIDConnectProviderID1" />
'exp'(有效期限)
這是 JWT 中的必要聲明,用來限制可以使用 JWT 的時間範圍。 「OpenID Connect 提供者」會根據其系統時鐘,配合容許的時間偏差,來驗證 exp
'nbf'(非之前)
這是選用聲明。 當呈現此項時,記號必須在這項聲明指定的時間過後才生效。 「OpenID Connect 提供者」會根據其系統時鐘,配合容許的時間偏差,來驗證此時間。
'iat'(簽發時間)
依預設,這是選用聲明。 不過,如果 jwtGrantType 元素的 iatRequired 屬性設為 true,則所有 JWT 都必須包含 iat 聲明。 當呈現此項時,iat 聲明指出 JWT 的簽發時間。 JWT 的簽發時間不能超過 maxTokenLifetime
'jti' (JWT ID)
這是選用聲明,為 JWT 記號的唯一 ID。 當呈現此項時,發證者不能重複使用相同的 JWT ID。 例如,如果 client01 發出 jtiid6098364921的 JWT ,則 client01 發出的任何其他 JWT 都不能具有 jti id6098364921。 如果 JWT 的 jti 聲明與另一個 JWT 相同,會被視為重播攻擊。 配置成「OpenID Connect 提供者」的 Liberty 伺服器會在伺服器上設定 jti 快取。 快取的大小取決於 jwtGrantType 配置中的 maxJtiCacheSize。 會根據任何送入的新 jti ID,來檢查快取中保留的 jti ID。 除非快取已滿,不然不會捨棄儲存在快取中的 jti ID。

提交「JSON Web 記號」要求

如果要提交 JWT 要求,最好的作法是使用 HTTPS 通訊協定,而非 HTTP。 「OpenID Connect 提供者」的記號端點用來處理 HTTPS JWT 要求。 如果要判斷「OpenID Connect 提供者」的記號端點,請參閱 呼叫 OpenID Connect 記號端點 OAuth 端點 URL

要求必須包含下列參數:

  • grant_type - 這個參數的值必須是 "urn:ietf:params:oauth:grant-type:jwt-bearer"
  • assertion - 這個參數的值必須包含單一已簽署的 JWT 記號。
  • scope - 這是選用參數。 如果省略 scope,傳回的存取記號不包含任何範圍。 會根據「OpenID Connect 提供者」配置,來檢查 scope 參數中列出的範圍值。 如需相關資訊,請參閱先前的「授權範圍」區段。
  • client_id - 這個參數的值必須符合「OpenID Connect 提供者」用戶端配置中的 name 屬性。
  • client_secret - 這個參數的值必須符合「OpenID Connect 提供者」用戶端配置中的 secret 屬性。

範例 HTTPS 要求:

POST /token.oauth2 HTTP/1.1
    Host: oidc.ibm.com
    Content-Type: application/x-www-form-utlencoded

    client_id=client01
    &client_secret=secret     
    &grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer     
    &assertion=eyJhbGc[---omitted---]kIn0.eyJpc[---ommitted---]A4fQ.MB6ZFlCsHg5MJ-weIHZYz6xgF1jdSZn7ErchHs8-8Rk     
    &scope=profile email

此 Java 範例是建立已簽署的 JWT 記號:

package com.ibm.sample;

import java.security.SignatureException;
import com.google.gson.JsonObject;
import net.oauth.jsontoken.crypto.HmacSHA256Signer;

import net.oauth.jsontoken.SystemClock;
import net.oauth.jsontoken.JsonToken;
import org.joda.time.Duration;
import org.joda.time.Instant;

public class SampleJWTToken {
        private static final Duration SKEW = Duration.standardMinutes(5);

        JsonToken jwtToken = null;
        String[] allPayloadKeys = { "iss", "sub", "aud", "exp", // required
                                    "nbf", "iat", "jti" }; // optional

        public SampleJWTToken(String clientId, 
                              String keyId,
                              String signKey,
                              String audience, 
                              String subject, // user
                              String jtiId) throws Exception { // InvalidKeyException

                byte[] hs256Key = signKey.getBytes();
                HmacSHA256Signer hmacSha256Signer = new HmacSHA256Signer(
                                clientId, keyId, hs256Key);
                // _rsaSha256Signer = new RsaSHA256Signer(clientId, _keyId,
                //                                        _privateKey);
                SystemClock clock = new SystemClock(SKEW);
                jwtToken = new JsonToken(hmacSha256Signer, clock);
                JsonObject headerObj = jwtToken.getHeader();
                JsonObject payloadObj = jwtToken.getPayloadAsJsonObject();

                headerObj.addProperty("alg", "HS256");

                Instant instantExp = clock.now().plus(600000); // 10 minutes
                jwtToken.setExpiration(instantExp);
                jwtToken.setAudience(audience);
                payloadObj.addProperty("iss", clientId);
                payloadObj.addProperty("sub", subject);

                // optional
                payloadObj.addProperty("jti", jtiId);
                jwtToken.setIssuedAt(clock.now()); // issued at time
        }

        public String getJWTTokenString() throws Exception {
                String signedAndSerializedString = null;
                try {
                        signedAndSerializedString = jwtToken.serializeAndSign();
                } catch (SignatureException e) {
                        throw e;
                }
                return signedAndSerializedString;
        }
}