用於 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 處理程式會根據下列準則,來處理授權範圍:
- 如果要求中沒有指定 scope 參數,「OpenID Connect 提供者」不會在存取記號中指定任何範圍。
- 當「OpenID Connect 用戶端」有資格作為「OpenID Connect 提供者」配置中的 autoAuthorized 用戶端,會將用戶端在要求中指定的任何範圍,指定在存取記號的範圍清單中。
- 當「OpenID Connect 用戶端」沒有資格作為 autoAuthorized 用戶端時,則需要以用戶端配置中的範圍清單來過濾要求中包含的範圍,且該範圍也必須指定在
preAuthorizedScope
清單中。 如果 HTTPS 要求中的範圍位於用戶端配置的scope
和preAuthorizeScope
清單中,則可以將該範圍指定在存取記號的範圍清單中。
當用戶端沒有資格作為 autoAuthorized 用戶端時,必須在用戶端配置中適當配置可包含在存取記號範圍清單中的範圍。 在「OpenID Connect 提供者」的用戶端配置中,scope
和 preAuthorizedScope
屬性的值中必須包含該範圍。 在顯示的範例中,存取記號的範圍清單中指定了範圍 profile
和 email
,這是因為兩者都包含在 scope
和 preAuthorizedScope
值清單中。 如果該範圍未列在用戶端配置的 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
聲明必須符合client01
或http://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
發出jti
為id6098364921
的 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;
}
}