Closed47

Box の JWT によるサーバー認証を試してみる

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

このスクラップについて

先ほどまでクライアント資格情報によるサーバー認証を試していたのだが as-user ヘッダーを使ってユーザーの代理アクセスを行うには JWT によるサーバー認証が必要となる可能性が高いことがわかった。

このスクラップでは Box の JWT によるサーバー認証を手を動かして試してみると共に as-user ヘッダーを使ってユーザーの代理アクセスができるかどうかを検証しようと思う。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

関連スクラップ

クライアント資格情報許可によるサーバー認証については下記のスクラップにまとめた。

https://zenn.dev/tatsuyasusukida/scraps/92690df756d575

Box の Business プランを契約する手続きについては下記のスクラップにまとめた。

https://zenn.dev/tatsuyasusukida/scraps/42155b55231f4b

Box の Individual プラン(無料)で試したことについては下記のスクラップにまとめた。

https://zenn.dev/tatsuyasusukida/scraps/0293205105d96c

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

この図を見てて気になった

もしかしてアプリアクセスレベルの設定を変更すればクライアント資格情報許可でも as-user ヘッダーを使える?

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

できなかった

アプリアクセスレベルを変更して再承認したがやはりクライアント資格情報許可ではできなかった。

エラーメッセージが少し変化した。

The API returned an error code [403 | .018e3a24895df8d8bf031cbe6c730b149]

前は下記が含まれていた。

access_denied_insufficient_permissions - Access denied - insufficient permission

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

はじめての JWT カスタムアプリ作成

開発者コンソールのアプリページへ移動してアプリの新規作成ボタンを押す。

カスタムアプリを押す。

アプリ名(例:My First JWT App)を入力する。

目的は自動化にしておく。

認証方法がサーバー認証(JWT 使用)であることを確認してアプリの作成ボタンを押す。

無事にカスタムアプリが作成された。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

公開/秘密キーペアの生成

https://ja.developer.box.com/guides/authentication/jwt/jwt-setup/#公開キーと秘密キーのペア

公開キーの追加と管理セクションにある公開/秘密キーペアを生成ボタンを押す。

2 段階認証のコードを入力する。

再び公開/秘密キーペアを生成ボタンを押すと JSON 形式の構成ファイルのダウンロードが始まる。

構成ファイルの中身は下記のような感じ。

構成ファイル(例)
{
  "boxAppSettings": {
    "clientID": "vvvv",
    "clientSecret": "wwww",
    "appAuth": {
      "publicKeyID": "xxxx",
      "privateKey": "-----BEGIN ENCRYPTED PRIVATE KEY-----\nyyyy\n-----END ENCRYPTED PRIVATE KEY-----\n",
      "passphrase": "zzzz"
    }
  },
  "enterpriseID": "1234567890"
}
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

権限はそのままにしておく

ユーザーの代理アクセスをするには設定を変更する必要がある。

設定についてはソースコードを作成してから変更しようと思う。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

カスタムアプリの承認

危ない危ない、忘れるところだった、ドキュメントを読んでいて気が付いた。

https://ja.developer.box.com/guides/authentication/jwt/jwt-setup/#アプリ承認

管理コンソールのカスタムアプリマネージャのページでアプリの追加ボタンを押す。

クライアント ID を入力して次へボタンを押す。

承認ボタンを押す。

承認が完了してアプリが追加された。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

ようやくコーディング

ドキュメントはこちら。

https://ja.developer.box.com/guides/authentication/jwt/with-sdk/

GitHub ページの方が詳細でわかりやすい。

https://github.com/box/box-java-sdk/blob/v4.1.0/doc/authentication.md#server-authentication-with-jwt

コード例
Reader reader = new FileReader("src/example/config/config.json");
BoxConfig boxConfig = BoxConfig.readFrom(reader);
BoxDeveloperEditionAPIConnection api = BoxDeveloperEditionAPIConnection.getAppEnterpriseConnection(boxConfig);

BoxDeveloperEditionAPIConnection というのが気になるけど今のところは気にしないでおこう。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

コーディング

src/main/kotlin/Main.kt
import com.box.sdk.BoxConfig
import com.box.sdk.BoxDeveloperEditionAPIConnection
import com.box.sdk.BoxFolder
import java.io.FileReader

fun main(args: Array<String>) {
    val fileName = System.getenv("BOX_CONFIG_FILE")
    val reader = FileReader(fileName)
    val boxConfig = BoxConfig.readFrom(reader)
    val api = BoxDeveloperEditionAPIConnection.getAppEnterpriseConnection(boxConfig)
    val rootFolder = BoxFolder.getRootFolder(api)

    for (info in rootFolder) {
        println("%s".format(info.name))
    }
}

構成ファイルへのパスは環境変数を使って与えるようにした。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

エラーが発生

下記の例外が発生、少し長いが全文を記載しておく。

Exception in thread "main" com.box.sdk.BoxAPIException: Error parsing PKCS private key for Box Developer Edition.
at com.box.sdk.BoxDeveloperEditionAPIConnection.decryptPrivateKey(BoxDeveloperEditionAPIConnection.java:568)
at com.box.sdk.BoxDeveloperEditionAPIConnection.constructJWTAssertion(BoxDeveloperEditionAPIConnection.java:503)
at com.box.sdk.BoxDeveloperEditionAPIConnection.authenticate(BoxDeveloperEditionAPIConnection.java:327)
at com.box.sdk.BoxDeveloperEditionAPIConnection.getAppEnterpriseConnection(BoxDeveloperEditionAPIConnection.java:182)
at com.box.sdk.BoxDeveloperEditionAPIConnection.getAppEnterpriseConnection(BoxDeveloperEditionAPIConnection.java:216)
at MainKt.main(Main.kt:11)
Caused by: org.bouncycastle.pkcs.PKCSException: unable to read encrypted data: 1.2.840.113549.1.5.13 not available: Illegal key size
at org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo.decryptPrivateKeyInfo(Unknown Source)
at com.box.sdk.BoxDeveloperEditionAPIConnection.decryptPrivateKey(BoxDeveloperEditionAPIConnection.java:557)
... 5 more
Caused by: org.bouncycastle.operator.OperatorCreationException: 1.2.840.113549.1.5.13 not available: Illegal key size
at org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder$1.get(Unknown Source)
... 7 more
Caused by: java.security.InvalidKeyException: Illegal key size
at javax.crypto.Cipher.checkCryptoPerm(Cipher.java:1039)
at javax.crypto.Cipher.checkCryptoPerm(Cipher.java:1060)
at javax.crypto.Cipher.init(Cipher.java:1536)
at javax.crypto.Cipher.init(Cipher.java:1470)
... 8 more

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Java のバージョンを変えてみる

既存 Kotlin プロジェクトで Java バージョンを 8 → 17 に変更した所、警告が続出したのでプロジェクト自体を作り直す。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

コーディング

ソースコードを前のものと同じなのでコピー&ペーストする。

src/main/kotlin/Main.kt
import com.box.sdk.BoxConfig
import com.box.sdk.BoxDeveloperEditionAPIConnection
import com.box.sdk.BoxFolder
import java.io.FileReader

fun main(args: Array<String>) {
    val fileName = System.getenv("BOX_CONFIG_FILE")
    val reader = FileReader(fileName)
    val boxConfig = BoxConfig.readFrom(reader)
    val api = BoxDeveloperEditionAPIConnection.getAppEnterpriseConnection(boxConfig)
    val rootFolder = BoxFolder.getRootFolder(api)

    for (info in rootFolder) {
        println("%s".format(info.name))
    }
}
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

エラーメッセージは表示されるが動いてはいるようだ

src/main/kotlin/Main.kt
import com.box.sdk.BoxConfig
import com.box.sdk.BoxDeveloperEditionAPIConnection
import com.box.sdk.BoxFolder
import java.io.FileReader

fun main(args: Array<String>) {
    val fileName = System.getenv("BOX_CONFIG_FILE")
    val reader = FileReader(fileName)
    val boxConfig = BoxConfig.readFrom(reader)
    val api = BoxDeveloperEditionAPIConnection.getAppEnterpriseConnection(boxConfig)
    val rootFolder = BoxFolder.getRootFolder(api)

    for (info in rootFolder) {
        println("%s".format(info.name))
    }

    println("Exit") // この行を追加しました
}

実行するとエラーメッセージの後に Exit と表示される。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

依存関係の追加

GitHub ドキュメントの見よう見まねで依存関係を追加してみる。

build.gradle.kts(抜粋)
dependencies {
    testImplementation(kotlin("test"))
    implementation("com.box:box-java-sdk:4.1.0")
    implementation("org.slf4j:jul-to-slf4j:1.7.32")
    implementation("org.slf4j:slf4j-api:1.7.32")
    implementation("ch.qos.logback:logback-core:1.2.7")
    implementation("ch.qos.logback:logback-classic:1.2.7")
}
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

この状態で実行

たくさんのログが表示されるようになった。

実行結果
11:12:44.259 [main] DEBUG org.jose4j.jwa.AlgorithmFactoryFactory - Initializing jose4j (running with Java 17.0.6 from Homebrew at /usr/local/Cellar/openjdk@17/17.0.6/libexec/openjdk.jdk/Contents/Home with [SUN version 17, SunRsaSign version 17, SunEC version 17, SunJSSE version 17, SunJCE version 17, SunJGSS version 17, SunSASL version 17, XMLDSig version 17, SunPCSC version 17, JdkLDAP version 17, JdkSASL version 17, Apple version 17, SunPKCS11 version 17, BC version 1.57] security providers installed)...
11:12:44.264 [main] DEBUG org.jose4j.jwa.AlgorithmFactory->JsonWebSignatureAlgorithm - org.jose4j.jws.UnsecuredNoneAlgorithm(none|null) registered for alg algorithm none
11:12:44.265 [main] DEBUG org.jose4j.jwa.AlgorithmFactory->JsonWebSignatureAlgorithm - org.jose4j.jws.HmacUsingShaAlgorithm$HmacSha256(HS256|HmacSHA256) registered for alg algorithm HS256
11:12:44.266 [main] DEBUG org.jose4j.jwa.AlgorithmFactory->JsonWebSignatureAlgorithm - org.jose4j.jws.HmacUsingShaAlgorithm$HmacSha384(HS384|HmacSHA384) registered for alg algorithm HS384
11:12:44.266 [main] DEBUG org.jose4j.jwa.AlgorithmFactory->JsonWebSignatureAlgorithm - org.jose4j.jws.HmacUsingShaAlgorithm$HmacSha512(HS512|HmacSHA512) registered for alg algorithm HS512
11:12:44.268 [main] DEBUG org.jose4j.jwa.AlgorithmFactory->JsonWebSignatureAlgorithm - org.jose4j.jws.EdDsaAlgorithm(EdDSA|EdDSA) registered for alg algorithm EdDSA
11:12:44.269 [main] DEBUG org.jose4j.jwa.AlgorithmFactory->JsonWebSignatureAlgorithm - org.jose4j.jws.EcdsaUsingShaAlgorithm$EcdsaP256UsingSha256(ES256|SHA256withECDSA) registered for alg algorithm ES256
11:12:44.269 [main] DEBUG org.jose4j.jwa.AlgorithmFactory->JsonWebSignatureAlgorithm - org.jose4j.jws.EcdsaUsingShaAlgorithm$EcdsaP384UsingSha384(ES384|SHA384withECDSA) registered for alg algorithm ES384
11:12:44.270 [main] DEBUG org.jose4j.jwa.AlgorithmFactory->JsonWebSignatureAlgorithm - org.jose4j.jws.EcdsaUsingShaAlgorithm$EcdsaP521UsingSha512(ES512|SHA512withECDSA) registered for alg algorithm ES512
11:12:44.285 [main] DEBUG org.jose4j.jws.EcdsaUsingShaAlgorithm$EcdsaSECP256K1UsingSha256 - ES256K is not available due to org.jose4j.lang.JoseException: Problem creating signature.; caused by: java.security.SignatureException: Curve not supported: java.security.spec.ECParameterSpec@793be5ca
11:12:44.286 [main] DEBUG org.jose4j.jwa.AlgorithmFactory->JsonWebSignatureAlgorithm - ES256K is unavailable so will not be registered for alg algorithms.
11:12:44.286 [main] DEBUG org.jose4j.jwa.AlgorithmFactory->JsonWebSignatureAlgorithm - org.jose4j.jws.RsaUsingShaAlgorithm$RsaSha256(RS256|SHA256withRSA) registered for alg algorithm RS256
11:12:44.286 [main] DEBUG org.jose4j.jwa.AlgorithmFactory->JsonWebSignatureAlgorithm - org.jose4j.jws.RsaUsingShaAlgorithm$RsaSha384(RS384|SHA384withRSA) registered for alg algorithm RS384
11:12:44.286 [main] DEBUG org.jose4j.jwa.AlgorithmFactory->JsonWebSignatureAlgorithm - org.jose4j.jws.RsaUsingShaAlgorithm$RsaSha512(RS512|SHA512withRSA) registered for alg algorithm RS512
11:12:44.289 [main] DEBUG org.jose4j.jwa.AlgorithmFactory->JsonWebSignatureAlgorithm - org.jose4j.jws.RsaUsingShaAlgorithm$RsaPssSha256(PS256|RSASSA-PSS) registered for alg algorithm PS256
11:12:44.291 [main] DEBUG org.jose4j.jwa.AlgorithmFactory->JsonWebSignatureAlgorithm - org.jose4j.jws.RsaUsingShaAlgorithm$RsaPssSha384(PS384|RSASSA-PSS) registered for alg algorithm PS384
11:12:44.294 [main] DEBUG org.jose4j.jwa.AlgorithmFactory->JsonWebSignatureAlgorithm - org.jose4j.jws.RsaUsingShaAlgorithm$RsaPssSha512(PS512|RSASSA-PSS) registered for alg algorithm PS512
11:12:44.294 [main] DEBUG org.jose4j.jwa.AlgorithmFactoryFactory - JWS signature algorithms: [none, HS256, HS384, HS512, EdDSA, ES256, ES384, ES512, RS256, RS384, RS512, PS256, PS384, PS512]
11:12:44.298 [main] DEBUG org.jose4j.jwa.AlgorithmFactory->KeyManagementAlgorithm - org.jose4j.jwe.RsaKeyManagementAlgorithm$Rsa1_5(RSA1_5|RSA/ECB/PKCS1Padding) registered for alg algorithm RSA1_5
11:12:44.298 [main] DEBUG org.jose4j.jwa.AlgorithmFactory->KeyManagementAlgorithm - org.jose4j.jwe.RsaKeyManagementAlgorithm$RsaOaep(RSA-OAEP|RSA/ECB/OAEPWithSHA-1AndMGF1Padding) registered for alg algorithm RSA-OAEP
11:12:44.302 [main] DEBUG org.jose4j.jwa.AlgorithmFactory->KeyManagementAlgorithm - org.jose4j.jwe.RsaKeyManagementAlgorithm$RsaOaep256(RSA-OAEP-256|RSA/ECB/OAEPWithSHA-256AndMGF1Padding) registered for alg algorithm RSA-OAEP-256
11:12:44.303 [main] DEBUG org.jose4j.jwa.AlgorithmFactory->KeyManagementAlgorithm - org.jose4j.jwe.DirectKeyManagementAlgorithm(dir|null) registered for alg algorithm dir
11:12:44.305 [main] DEBUG org.jose4j.jwa.AlgorithmFactory->KeyManagementAlgorithm - org.jose4j.jwe.AesKeyWrapManagementAlgorithm$Aes128(A128KW|AESWrap) registered for alg algorithm A128KW
11:12:44.305 [main] DEBUG org.jose4j.jwa.AlgorithmFactory->KeyManagementAlgorithm - org.jose4j.jwe.AesKeyWrapManagementAlgorithm$Aes192(A192KW|AESWrap) registered for alg algorithm A192KW
11:12:44.306 [main] DEBUG org.jose4j.jwa.AlgorithmFactory->KeyManagementAlgorithm - org.jose4j.jwe.AesKeyWrapManagementAlgorithm$Aes256(A256KW|AESWrap) registered for alg algorithm A256KW
11:12:44.312 [main] DEBUG org.jose4j.jwa.AlgorithmFactory->KeyManagementAlgorithm - org.jose4j.jwe.EcdhKeyAgreementAlgorithm(ECDH-ES|ECDH) registered for alg algorithm ECDH-ES
11:12:44.319 [main] DEBUG org.jose4j.jwa.AlgorithmFactory->KeyManagementAlgorithm - org.jose4j.jwe.EcdhKeyAgreementWithAesKeyWrapAlgorithm$EcdhKeyAgreementWithAes128KeyWrapAlgorithm(ECDH-ES+A128KW|N/A) registered for alg algorithm ECDH-ES+A128KW
11:12:44.325 [main] DEBUG org.jose4j.jwa.AlgorithmFactory->KeyManagementAlgorithm - org.jose4j.jwe.EcdhKeyAgreementWithAesKeyWrapAlgorithm$EcdhKeyAgreementWithAes192KeyWrapAlgorithm(ECDH-ES+A192KW|N/A) registered for alg algorithm ECDH-ES+A192KW
11:12:44.331 [main] DEBUG org.jose4j.jwa.AlgorithmFactory->KeyManagementAlgorithm - org.jose4j.jwe.EcdhKeyAgreementWithAesKeyWrapAlgorithm$EcdhKeyAgreementWithAes256KeyWrapAlgorithm(ECDH-ES+A256KW|N/A) registered for alg algorithm ECDH-ES+A256KW
11:12:44.336 [main] DEBUG org.jose4j.jwa.AlgorithmFactory->KeyManagementAlgorithm - org.jose4j.jwe.Pbes2HmacShaWithAesKeyWrapAlgorithm$HmacSha256Aes128(PBES2-HS256+A128KW|n/a) registered for alg algorithm PBES2-HS256+A128KW
11:12:44.337 [main] DEBUG org.jose4j.jwa.AlgorithmFactory->KeyManagementAlgorithm - org.jose4j.jwe.Pbes2HmacShaWithAesKeyWrapAlgorithm$HmacSha384Aes192(PBES2-HS384+A192KW|n/a) registered for alg algorithm PBES2-HS384+A192KW
11:12:44.337 [main] DEBUG org.jose4j.jwa.AlgorithmFactory->KeyManagementAlgorithm - org.jose4j.jwe.Pbes2HmacShaWithAesKeyWrapAlgorithm$HmacSha512Aes256(PBES2-HS512+A256KW|n/a) registered for alg algorithm PBES2-HS512+A256KW
11:12:44.341 [main] DEBUG org.jose4j.jwa.AlgorithmFactory->KeyManagementAlgorithm - org.jose4j.jwe.AesGcmKeyEncryptionAlgorithm$Aes128Gcm(A128GCMKW|AES/GCM/NoPadding) registered for alg algorithm A128GCMKW
11:12:44.341 [main] DEBUG org.jose4j.jwa.AlgorithmFactory->KeyManagementAlgorithm - org.jose4j.jwe.AesGcmKeyEncryptionAlgorithm$Aes192Gcm(A192GCMKW|AES/GCM/NoPadding) registered for alg algorithm A192GCMKW
11:12:44.342 [main] DEBUG org.jose4j.jwa.AlgorithmFactory->KeyManagementAlgorithm - org.jose4j.jwe.AesGcmKeyEncryptionAlgorithm$Aes256Gcm(A256GCMKW|AES/GCM/NoPadding) registered for alg algorithm A256GCMKW
11:12:44.342 [main] DEBUG org.jose4j.jwa.AlgorithmFactoryFactory - JWE key management algorithms: [RSA1_5, RSA-OAEP, RSA-OAEP-256, dir, A128KW, A192KW, A256KW, ECDH-ES, ECDH-ES+A128KW, ECDH-ES+A192KW, ECDH-ES+A256KW, PBES2-HS256+A128KW, PBES2-HS384+A192KW, PBES2-HS512+A256KW, A128GCMKW, A192GCMKW, A256GCMKW]
11:12:44.343 [main] DEBUG org.jose4j.jwa.AlgorithmFactory->ContentEncryptionAlgorithm - org.jose4j.jwe.AesCbcHmacSha2ContentEncryptionAlgorithm$Aes128CbcHmacSha256(A128CBC-HS256|AES/CBC/PKCS5Padding) registered for enc algorithm A128CBC-HS256
11:12:44.343 [main] DEBUG org.jose4j.jwa.AlgorithmFactory->ContentEncryptionAlgorithm - org.jose4j.jwe.AesCbcHmacSha2ContentEncryptionAlgorithm$Aes192CbcHmacSha384(A192CBC-HS384|AES/CBC/PKCS5Padding) registered for enc algorithm A192CBC-HS384
11:12:44.344 [main] DEBUG org.jose4j.jwa.AlgorithmFactory->ContentEncryptionAlgorithm - org.jose4j.jwe.AesCbcHmacSha2ContentEncryptionAlgorithm$Aes256CbcHmacSha512(A256CBC-HS512|AES/CBC/PKCS5Padding) registered for enc algorithm A256CBC-HS512
11:12:44.345 [main] DEBUG org.jose4j.jwa.AlgorithmFactory->ContentEncryptionAlgorithm - org.jose4j.jwe.AesGcmContentEncryptionAlgorithm$Aes128Gcm(A128GCM|AES/GCM/NoPadding) registered for enc algorithm A128GCM
11:12:44.346 [main] DEBUG org.jose4j.jwa.AlgorithmFactory->ContentEncryptionAlgorithm - org.jose4j.jwe.AesGcmContentEncryptionAlgorithm$Aes192Gcm(A192GCM|AES/GCM/NoPadding) registered for enc algorithm A192GCM
11:12:44.346 [main] DEBUG org.jose4j.jwa.AlgorithmFactory->ContentEncryptionAlgorithm - org.jose4j.jwe.AesGcmContentEncryptionAlgorithm$Aes256Gcm(A256GCM|AES/GCM/NoPadding) registered for enc algorithm A256GCM
11:12:44.346 [main] DEBUG org.jose4j.jwa.AlgorithmFactoryFactory - JWE content encryption algorithms: [A128CBC-HS256, A192CBC-HS384, A256CBC-HS512, A128GCM, A192GCM, A256GCM]
11:12:44.347 [main] DEBUG org.jose4j.jwa.AlgorithmFactory->CompressionAlgorithm - org.jose4j.zip.DeflateRFC1951CompressionAlgorithm@d9345cd registered for zip algorithm DEF
11:12:44.347 [main] DEBUG org.jose4j.jwa.AlgorithmFactoryFactory - JWE compression algorithms: [DEF]
11:12:44.347 [main] DEBUG org.jose4j.jwa.AlgorithmFactoryFactory - Initialized jose4j in 85ms
Exit
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

ユーザーの代理アクセス

どうやら動いている様子なのでユーザーの代理アクセスを試してみる。

src/main/kotlin/Main.kt
import com.box.sdk.BoxConfig
import com.box.sdk.BoxDeveloperEditionAPIConnection
import com.box.sdk.BoxFolder
import java.io.FileReader

fun main(args: Array<String>) {
    val fileName = System.getenv("BOX_CONFIG_FILE")
    val userID = System.getenv("BOX_USER_ID")

    val reader = FileReader(fileName)
    val boxConfig = BoxConfig.readFrom(reader)
    val api = BoxDeveloperEditionAPIConnection.getAppEnterpriseConnection(boxConfig)
    api.asUser(userID)

    val rootFolder = BoxFolder.getRootFolder(api)

    for (info in rootFolder) {
        println("%s".format(info.name))
    }

    println("Exit")
}

この時点ではカスタムアプリの権限を適切に設定していないので期待通り失敗する。

Exception in thread "main" com.box.sdk.BoxAPIResponseException: The API returned an error code [403 | nsaffzhdcvsz2dkq.053c8d43eef5fb06b5e0cc1ee1700ddba] access_denied_insufficient_permissions - Access denied - insufficient permission

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

まずは as-user ヘッダーを有効にしてみる

再承認を忘れないようにする。

Exception in thread "main" com.box.sdk.BoxAPIResponseException: The API returned an error code [403 | e8xv8ihdcvyzesc1.08f162971b1dc1773618e2f21351b1ed4] access_denied_insufficient_permissions - Access denied - insufficient permission

この時点で実行しても結果は変わらなかった。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

アプリアクセスレベルの変更


開発者コンソール


管理コンソール

エラーメッセージが変化した。

Exception in thread "main" com.box.sdk.BoxAPIResponseException: The API returned an error code [403 | .0884fb7f5eb8ab3aef87584aa570f2349]

ここまではクライアント資格情報許可と同様だ。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

アプリアクセスレベルの変更には注意

変更すると連動してアプリケーションスコープの設定が勝手に上書きされる。


変更前


変更後

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

as-user の許可にはユーザー管理の権限が必須

as-user のチェックボックスにチェックを入れるとユーザー管理のチェックボックスにもチェックが入る。


as-user チェック前


as-user チェック後

ユーザー管理のチェックボックスからチェックを外すと as-user のチェックボックスからもチェックが外れる。


ユーザー管理からチェックを外した後

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

変更が反映されるまでに遅延がある

開発者コンソールでカスタムアプリの設定を変更した後に管理コンソールで承認するまでの時間が短すぎると反映がされていない。

例えば下記の画像ではアプリケーションアクセスがすべてのユーザーになっているが、本来はこのアプリの App User のみが正しい。

しばらく待つかカスタムアプリの詳細を表示したりすると反映される。

設定変更が反映されていることをしっかり確認してから再承認する必要がある。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

この状態で実行する

下記の例外が発生する。

Exception in thread "main" com.box.sdk.BoxAPIResponseException: The API returned an error code [403 | sskifbhdd4mcmnsu.0a9c00049ce22e46e05d2a6cde9b94540] access_denied_insufficient_permissions - Access denied - insufficient permission

やはりアプリケーションアクセルレベルがアプリアクセスのみではダメなようだ。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

アプリアクセスレベルを変更する

アプリ + Enterprise アクセスにする。

as-user ヘッダーの許可はしないでおく。

この状態で変更を保存してから管理コンソールでしっかり確認してから再承認する。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

この状態で実行する

期待通り例外が発生した。

Exception in thread "main" com.box.sdk.BoxAPIResponseException: The API returned an error code [403 | .0a61d1efed33a1969d50dad23a6250e5c]

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

as-user を許可する

as-user にチェックを入れる、必然的にユーザーを管理するにもチェックが入る。

この状態で変更を保存してしっかり確認してから再承認する。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

この状態で実行する

サービスアカウントではなくユーザー(僕)のルートフォルダーの一覧が表示された。

実行結果
Box Reports
It works!
Exit
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

もしかしてクライアント資格情報許可でもできる?

同じ条件で試してみたところできてしまった。

実行結果
start
[204837656150] Box Reports
[204677117397] It works!
end

前にやった時は設定が間違っていただけでクライアント資格情報許可でもユーザーの代理アクセスができることがわかった。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

今日はここまで

ユーザーの代理アクセスを検証できて今日はキリの良いところで終わることができた。

Java 17 ではできたが Java 8 ではできていないのでせっかくなので次回は再挑戦してみたい。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

まとめたらクローズする

今回のスクラップ作成を通じて学んだことをまとめたらこのスクラップをクローズしよう。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

まとめ

  • ユーザーの代理アクセスにはアプリアクセスレベルをアプリ + Enterprise アクセスに設定する必要がある。
  • アプリ + Enterprise アクセスは再承認モーダルでは「すべてユーザー」と表示される。
  • JWT 認証では公開キーを登録するか公開/秘密キーペアを生成する必要がある。
  • キーペアを生成した場合は JSON 形式の構成ファイルがダウンロードされる。
  • クライアント資格情報許可と同様、JWT 認証でも管理コンソールから承認が必要となる。
  • Java 8 では構成ファイル読み込み時に PKCS 秘密鍵がパースできなくて例外が発生する場合がある。
  • Java 17 では例外は発生しなかった。
  • 構成ファイル読み込み時に SLF4J の警告が表示されるがプログラム自体は最後まで動く。
  • SLF4J の警告はログ関係の依存関係 4 件を追加することで表示されなくなる。
  • ユーザー代理アクセスには as-header を有効にする必要がある。
  • アプリアクセスレベルを変更すると連動して他の設定が上書きされるので注意する。
  • as-header を有効にするとユーザー管理も自動的に有効になる。
  • ユーザー管理を無効にすると as-header も自動的に無効になる。
  • 管理コンソールで承認する時に権限設定が反映されていないことがあるので注意する。
  • 承認したカスタムアプリの詳細ページなどを表示することで権限設定が反映されることがある。
  • ユーザーの代理アクセスは設定が正しければクライアント資格情報許可でも行える。
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

おわりに

ユーザー代理アクセス

ユーザーの代理アクセスが可能な点を検証できて良かった。

ユーザーの代理アクセスを使えばできることの幅が広がるので様々な要件に対応ができそう。

その一方でユーザーの代理アクセスはとても強力な権限なので適切に使う必要があると考えている。

代わりにコラボレーション(フォルダー共有)を使うこともできるので、要件に応じて適切に選択できるようになりたい。

ユーザー → サービスアカウントのコラボレーションは検証できたけど、逆方向のサービスアカウント → ユーザーのコラボレーションはできるのかな?

これは面白そうなので近日検証してみよう。

JWT 認証

Box 側が保持する情報が公開鍵だけなのでクライアント資格情報許可に比べて原理上は安全だが、結局はこちら側で秘密鍵を安全に管理しなければならないので、クライアントシークレットの場合とあまり変わらない印象を受ける。

誤解を恐れずに言えば共通鍵暗号と公開鍵暗号のどっちが安全かという議論に近い感じがする。

JWT とクライアント資格情報許可でできることはあまり変わらないみたいなので適切に鍵を管理できるのであればクライアント資格情報許可でも良い気がする。

JWT 認証は Java 8 だと Oracle でユーザー登録して JAR をダウンロードし、JAVA_HOME/lib/security に追加するという作業が必要になる。

開発環境ではこの作業は 1 回やれば良いので少し面倒なくらいで大したことはないが、運用環境では難しいケースもあるかも知れない。

まだまだ勉強不足で JWT のメリットをよく理解できてないだけかも知れないが、現時点では要件的に許されるのであればクライアント資格情報許可を選ぶことになりそう。

カスタムアプリの権限設定と承認

罠が多すぎるのでもう少し良い UI にして欲しいが Box の UI はかなり良い方だと思うので気を付けるしか無さそうだ。

このスクラップは2023/04/28にクローズされました