首页 文章

Cloud Endpoints Firebase Admin SDK:通过FirebaseUser令牌对用户进行身份验证

提问于
浏览
0

我使用谷歌 Cloud endpoints (版本1,使用android studio)为我的Android应用程序构建了一个移动后端 . 我希望通过电子邮件/密码验证我的用户,因此我使用Firebase身份验证来执行此操作 . Firebase身份验证sdk允许我在客户端(在android中)获取每个用户的令牌,而firebase管理员sdk允许我检查后端令牌的有效性 . 我知道在 Cloud endpoints 我可以提供自己的自定义身份验证器(请参阅:Google Cloud Endpoints and user's authentication),我计划在我的自定义身份验证器中调用firebase admin sdk来验证用户的呈现令牌 .

我的问题是,因为我使用谷歌 Cloud endpoints 来构建我的后端,我不知道在我可以验证任何令牌之前,在哪里插入代码来执行firebase管理对象的初始化 . 在常规应用程序引擎环境中,您将在HTTPServlet的init()方法中执行此初始化(请参阅https://github.com/GoogleCloudPlatform/firebase-appengine-backend/blob/master/src/main/java/com/google/cloud/solutions/flexenv/backend/MessageProcessorServlet.java),但是 Cloud endpoints 通过自动提供"SystemServiceServlet"作为HTTPServlet来隐藏此信息 . 我已经尝试了继承SystemServiceServlet并重写init()方法,但是然后将 endpoints 部署到app引擎失败了,因为显然,创建android客户端库要求必须使用SystemServiceServlet(并且必须将其命名为"SystemServiceServlet") .

我可以在 Cloud endpoints 提供的每个api方法中对firebase管理应用程序进行初始化(例如,在我的api的insert方法中),但这看起来效率极低 . 如何在使用谷歌 Cloud endpoints 构建的后端中使用Firebase管理员sdk?

非常感谢你花时间陪伴

2 回答

  • 5

    由于我无法在Cloud Endpoints中找到初始化Firebase管理代码的合适位置,因此我编写了自己的服务器端Java代码,以根据https://firebase.google.com/docs/auth/admin/verify-id-tokens#verify_id_tokens_using_a_third-party_jwt_library验证Firebase令牌

    这是一个帮助程序类,您可以使用它来验证用户的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;
        }
    }
    
  • 0

    @ Dan7620,提出了另一种替代方法,但它并没有解决问题 . 这是一个简单的解决方案,它使用Firebase Admin SDK,在Cloud Endpoints模块中正确配置和初始化 . 我将总结这里的步骤:

    • 将serviceAccountKey.json放入应用程序的WEB-INF文件夹中 .

    • 在appengineweb.xml中插入:

    <resource-files>
        <include path="/**.json" />
    </resource-files>
    
    • 在下面的某个地方定义一个类 . 强制使用单独的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) {}
    }
    
    • 在您定义的任何 endpoints 中的任何 static{} 代码中调用此方法 . 例如:
    static {
        ObjectifyService.register(FCMTokenMap.class);
        FirebaseService.init();
    }
    
    • 您可以从任何地方调用涉及数据库,FCM等的其他Firebase方法 .

相关问题