Cloud Endpoints + Firebase Admin SDK: аутентификация пользователей с помощью их токенов FirebaseUser

Я создал мобильную серверную часть для своего приложения Android с использованием конечных точек Google Cloud (версия 1, с использованием студии Android). Я хочу аутентифицировать своих пользователей по электронной почте / паролю, поэтому я использую для этого аутентификацию Firebase. SDK Firebase Authentication позволяет мне получить токен каждого пользователя на стороне клиента (в Android), а SDK администратора Firebase позволяет мне проверять действительность токена на бэкэнде. Я понимаю, что в облачных конечных точках я могу предоставить свой собственный пользовательский аутентификатор (см. Google Cloud Endpoints и аутентификация пользователя), и я планирую вызвать sdk администратора firebase в моем настраиваемом аутентификаторе, чтобы проверить представленный пользователем токен.

Моя проблема в том, что, поскольку я использую конечные точки облака Google для создания своего бэкэнда, я не знаю, куда вставить код для инициализации объекта администратора firebase, который требуется, прежде чем я смогу проверить какие-либо токены. В обычной среде движка приложений вы должны выполнить эту инициализацию в методе init () HTTPServlet (см. 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 ").

Я мог бы выполнить инициализацию приложения администратора firebase в каждом из методов api, которые предоставляют конечные точки облака (например, в методе вставки моего api), но это кажется крайне неэффективным. Как мне использовать SDK администратора Firebase в серверной части, созданной с использованием конечных точек Google Cloud?

Большое спасибо за ваше время


comment
Если все, что вам нужно сделать, это создать собственный токен, используйте эту библиотеку для создания пользовательского токена - ›github.com/jwtk / jjwt. Ее легко использовать, и вы не столкнетесь с проблемой инициализации с помощью этой библиотеки.   -  person Rahul Sainani    schedule 28.11.2016


Ответы (2)


Поскольку мне не удалось найти подходящее место для инициализации кода администратора Firebase в Cloud Endpoints, я написал свой собственный серверный Java-код для проверки токена Firebase согласно https://firebase.google.com/docs/auth/admin/verify-id-tokens#verify_id_tokens_using_a_wird_party-party

Вот вспомогательный класс, который вы можете использовать для проверки токена Firebase пользователя и получения его uid пользователя Firebase (в этом коде используется библиотека jose.4.j из https://bitbucket.org/b_c/jose4j/wiki/Home, чтобы выполнять манипуляции с 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/[email protected]";

    /**
     * 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/[email protected]
     * @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;
    }
}
person Dan7620    schedule 28.11.2016
comment
Это прекрасно сработало в моем случае, пытаясь проверить токен из Firebase синхронно, а не асинхронно, как силы APK администратора Firebase. Спасибо за прекрасный пример! - person DoctorD; 12.04.2017

@ Dan7620 предложил другой альтернативный способ, но он не решает проблемы. Вот простое решение, использующее Firebase Admin SDK, правильно настроенное и инициализированное в модуле Cloud Endpoints. Я резюмирую шаги здесь:

  1. Поместите свой serviceAccountKey.json в папку WEB-INF ваших приложений.
  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. You can invoke other Firebase methods which involve Database, FCM, etc from anywhere..!
person Mohammed Mukhtar    schedule 23.03.2017