0

Google クラウド エンドポイント (バージョン 1、Android Studio を使用) を使用して、Android アプリケーションのモバイル バックエンドを構築しました。メール/パスワードでユーザーを認証したいので、Firebase Authentication を使用しています。Firebase Authentication SDK を使用すると、クライアント側 (Android) で各ユーザーのトークンを取得でき、firebase admin SDK を使用すると、バックエンドでトークンの有効性を確認できます。クラウド エンドポイントで独自のカスタム認証システムを提供できることを理解しています ( Google Cloud エンドポイントとユーザー認証を参照)。カスタム認証システム内で firebase admin SDK を呼び出して、ユーザーが提示したトークンを検証する予定です。

私の問題は、Google クラウド エンドポイントを使用してバックエンドを構築しているため、トークンを検証する前に必要な firebase 管理オブジェクトの初期化を行うコードを挿入する場所がわからないことです。通常のアプリ エンジン環境では、HTTPServlet の init() メソッドでこの初期化を行います ( https://github.com/GoogleCloudPlatform/firebase-appengine-backend/blob/master/src/main/java/comを参照)。 /google/cloud/solutions/flexenv/backend/MessageProcessorServlet.java)、しかしクラウド エンドポイントは、「SystemServiceServlet」を HTTPServlet として自動的に提供することで、これを隠します。SystemServiceServlet のサブクラス化と init() メソッドのオーバーライドを試みましたが、明らかに、Android クライアント ライブラリの作成には SystemServiceServlet を使用する必要があるため (「SystemServiceServlet」という名前にする必要があるため)、アプリ エンジンへのエンドポイントのデプロイは失敗します。 」)。

クラウド エンドポイントが提供する各 API メソッド (たとえば、API の挿入メソッド) で firebase 管理アプリの初期化を行うことができますが、これは非常に非効率的なようです。Google クラウド エンドポイントを使用して構築されたバックエンドで Firebase admin SDK を使用するにはどうすればよいですか?

お時間をいただきありがとうございました

4

2 に答える 2

5

Cloud Endpoints 内で Firebase 管理コードを初期化するための適切な場所が見つからなかったため、https: //firebase.google.com/docs/auth/admin/ に従って Firebase トークンを検証する独自のサーバー側 Java コードを作成しました。 verify-id-tokens#verify_id_tokens_using_a_third-party_jwt_library

これは、ユーザーの Firebase トークンを検証し、Firebase ユーザー uid を取得するために使用できるヘルパー クラスです (このコードは、https: //bitbucket.org/b_c/jose4j/wiki/Home の jose.4.j ライブラリを使用して JWT を実行します)。操作):

public class TokenManager {
    private final static String PROJECT_ID = "your_firebase_project_id";
    private final static String AUDIENCE = PROJECT_ID;
    private final static String ISSUER = "https://securetoken.google.com/" + PROJECT_ID;
    private final static String KEYS_URL = "https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com";

    /**
     * Parses and verifies a FirebaseUser ID token (JWT) and returns the associated user's uid
     *
     * @param token the firebase user's token
     * @return the firebase user UID
     * @throws UnauthorizedException if the token is invalid.
     */
    public static String verfiyToken(String token) throws UnauthorizedException{
        JwtConsumer firstPassJwtConsumer = new JwtConsumerBuilder()
                .setSkipAllValidators()
                .setDisableRequireSignature()
                .setSkipSignatureVerification()
                .build();

        //The first JwtConsumer is basically just used to parse the JWT into a JwtContext object.
        JwtContext jwtContext;
        try {
            jwtContext = firstPassJwtConsumer.process(token);
        } catch (InvalidJwtException e) {
            throw new UnauthorizedException(e.getMessage());
        }

        // get the key id from the header of the JWT
        List<JsonWebStructure> list = jwtContext.getJoseObjects();
        String kid = list.get(0).getKeyIdHeaderValue();
        String keyAsString;
        try {
            keyAsString = getPublicKey(kid);
        } catch (IOException e) {
            throw new UnauthorizedException(e.getMessage());
        }

        // decode the key into proper format
        InputStream certIs = new ByteArrayInputStream(Charset.forName("UTF-8").encode(keyAsString).array());

        CertificateFactory certificateFactory;
        try {
            certificateFactory = CertificateFactory.getInstance("X.509");
        } catch (CertificateException e) {
            throw new UnauthorizedException(e.getMessage());
        }

        X509Certificate cert;
        try {
            cert = (X509Certificate) certificateFactory.generateCertificate(certIs);
        } catch (CertificateException e) {
            throw new UnauthorizedException(e.getMessage());
        }
        PublicKey key = cert.getPublicKey();

        // now that we have the public key, we can verify the JWT
        JwtConsumer jwtConsumer = new JwtConsumerBuilder()
                .setRequireExpirationTime() // the JWT must have an expiration time
                .setMaxFutureValidityInMinutes(300) // but the  expiration time can't be too crazy
                .setAllowedClockSkewInSeconds(30) // allow some leeway in validating time based claims to account for clock skew
                .setRequireSubject() // the JWT must have a subject claim
                .setExpectedIssuer(ISSUER) // whom the JWT needs to have been issued by
                .setExpectedAudience(AUDIENCE) // to whom the JWT is intended for
                .setVerificationKey(key) // verify the signature with the public key
                .build(); // create the JwtConsumer instance

        JwtClaims jwtClaims;
        try {
            //  Validate the JWT and process it to the Claims
            jwtClaims = jwtConsumer.processToClaims(token);
        } catch (InvalidJwtException e)  {
            throw new UnauthorizedException(e.getMessage());
        }

        String userUid;

        try {
            userUid = jwtClaims.getSubject();
        } catch(MalformedClaimException e) {
            throw new UnauthorizedException(e.getMessage());
        }
        return userUid;
    }


    /**
     * Grab the certificate corresponding to the keyid specified in the JWT
     *
     * @param kid key id corresponding to one of the public keys listed at public keys listed at
     *            https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com
     * @return the certificate
     * @throws IOException if the process fails
     */
    private static String getPublicKey(String kid) throws IOException {
        URL url = new URL(KEYS_URL);
        HttpURLConnection request = (HttpURLConnection) url.openConnection();
        request.connect();

        JsonParser jp = new JsonParser(); //from gson
        JsonElement root = jp.parse(new InputStreamReader((InputStream) request.getContent()));
        JsonObject rootobj = root.getAsJsonObject();
        String publicKey = rootobj.get(kid).getAsString();

        return publicKey;
    }
}
于 2016-11-28T20:18:57.617 に答える
0

@ Dan7620 は別の代替方法を提案しましたが、問題は解決しません。これは、Cloud Endpoints モジュールで適切に構成および初期化された Firebase Admin SDK を使用する簡単なソリューションです。ここで手順を要約します。

  1. アプリの WEB-INF フォルダーにserviceAccountKey.jsonを配置します。
  2. これを appengineweb.xml に挿入します。

    <resource-files>
        <include path="/**.json" />
    </resource-files>
    
  3. 以下のような場所にクラスを定義します。強制的に別の init() メソッドを用意します。

    public class FirebaseService {
    
    public static void init() {
        try {
            FileInputStream serviceAccount = new FileInputStream(new File("WEB-INF/serviceAccountKey.json"));
            FirebaseOptions options = new FirebaseOptions.Builder()
                .setCredential(FirebaseCredentials.fromCertificate(serviceAccount))
                .setDatabaseUrl("https://[YOUR_APP_NAME].firebaseio.com/")
                .build();
            FirebaseApp.initializeApp(options);
            System.out.print("In Firebase Init module...!!");
        } catch(FileNotFoundException ignored) {}
    }
    
  4. ユーザーが定義した任意のエンドポイントの任意の静的コードで、このメソッドを呼び出します。 {} 例えば:

    static {
        ObjectifyService.register(FCMTokenMap.class);
        FirebaseService.init();
    }
    
    1. データベース、FCM などを含む他の Firebase メソッドをどこからでも呼び出すことができます..!
于 2017-03-23T03:23:08.110 に答える