FirebaseのAPIをサービスアカウントを使って、Googleのライブラリ無しで呼び出します。
サービスアカウントを作る
https://console.cloud.google.com/iam-admin/serviceaccounts
今回はFirebase App DistributionのAPIを使用したかった為、こちらの権限を与えました。
鍵を作る
推奨のJsonで作ります。以下のようなjsonがダウンロードできます
{ "type": "service_account", "project_id": "api-0000000000000000000-000000", "private_key_id": "2222222222222222222222222222222222222222", "private_key": "-----BEGIN PRIVATE KEY-----\nHOGE\nHOGE=\n-----END PRIVATE KEY-----\n", "client_email": "test-app@api-0000000000000000000-000000.iam.gserviceaccount.com", "client_id": "111111111111111111111", "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://oauth2.googleapis.com/token", "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/test-app%40api-0000000000000000000-000000.iam.gserviceaccount.com" }
リクエストを行う
流れ
以下のような流れで作成していきます。
- jwtを作る
- jwtを使ってTokenを取得する
- Tokenを使用してリクエストを行う
今回はKotlin+JDK17で書きます。
jwtからTokenを取得する
使用ライブラリ
jwtライブラリとjsonのシリアライズライブラリだけ追加で使用します。
build.gradle.kts
plugins { kotlin("jvm") version "1.7.10" kotlin("plugin.serialization") version "1.7.10" } repositories { mavenCentral() } dependencies { implementation("com.auth0:java-jwt:3.19.2") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.0-RC") }
コード
まずはサービスアカウントの private_key
のキー部分だけを取り出して、auth0が求める型に変換します。
import
import com.auth0.jwt.JWT import com.auth0.jwt.algorithms.Algorithm import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import org.intellij.lang.annotations.Language import java.net.URI import java.net.http.HttpClient import java.net.http.HttpRequest import java.net.http.HttpResponse import java.security.KeyFactory import java.security.interfaces.RSAPrivateKey import java.security.spec.PKCS8EncodedKeySpec import java.util.*
/** * @param privateKeyString private_key の値 * @param issuer client_email の値 **/ fun get( privateKeyString: String, issuer: String, ): String { val privateKey: RSAPrivateKey = privateKeyString .replace("-----BEGIN PRIVATE KEY-----", "") .replace("-----END PRIVATE KEY-----", "") .replace("\n", "") .let { Base64.getDecoder().decode(it) } .let { PKCS8EncodedKeySpec(it) } .let { KeyFactory .getInstance("RSA") .generatePrivate(it) as RSAPrivateKey } // ... }
期限は1時間に設定します。
更にClaimで scope
を設定します。
自分が使用したいAPIは以下なので、ドキュメントにある通り、 https://www.googleapis.com/auth/cloud-platform
を設定します。
https://firebase.google.com/docs/reference/app-distribution/rest/v1/projects.apps.releases/list
// requestUrlは `token_uri` の値 val requestUrl = "https://oauth2.googleapis.com/token" val time = Calendar.getInstance() val jwt = JWT.create() .withIssuer(issuer) .withAudience(requestUrl) .withIssuedAt(time.time) .withExpiresAt(time.also { it.add(Calendar.HOUR, 1) }.time) .withClaim( "scope", "https://www.googleapis.com/auth/cloud-platform" ) .sign(Algorithm.RSA256(null, privateKey))
POSTリクエストを投げます。
grant_typeには urn:ietf:params:oauth:grant-type:jwt-bearer
, assertionには先程生成したjwtを設定します。
val httpRequestResult = HttpClient.newHttpClient() .send( HttpRequest.newBuilder() .uri(URI(requestUrl)) .header("Content-Type", "application/json") .POST( HttpRequest.BodyPublishers.ofString( run { @Language("json") val json = """ { "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer", "assertion": "$jwt" } """.trimIndent() json } ) ) .build(), HttpResponse.BodyHandlers.ofString() )
以下のような構造のレスポンスが得られます。
@Serializable private data class TokenRequestResult( @SerialName("access_token") val accessToken: String, @SerialName("expires_in") val expiresIn: Int, @SerialName("token_type") val tokenType: String, )
結果をパースします。これでTokenが得られます。
val requestResult = Json.Default.decodeFromString<TokenRequestResult>(body) println(requestResult) return requestResult.accessToken
TokenでAPIを叩く
ここまでくればAPIを叩くだけです。
Authorization
ヘッダに先程のjwtを設定します。
val requestUrl = "https://firebaseappdistribution.googleapis.com/v1/projects/000000000000/apps/1:000000000000:android:9999999999999999/releases" val result = HttpClient.newHttpClient() .send( HttpRequest.newBuilder() .uri(URI(requestUrl)) .header("Content-Type", "application/json") .header("Authorization", "Bearer $token") .GET() .build(), HttpResponse.BodyHandlers.ofString() ) println(result.body())