我使用谷歌云端点(版本1,使用android studio)为我的Android应用程序构建了一个移动后端。我希望通过电子邮件/密码验证我的用户,因此我使用Firebase身份验证来执行此操作。 Firebase身份验证sdk允许我在客户端(在android中)获取每个用户的令牌,而firebase管理员sdk允许我检查后端令牌的有效性。据我所知,在云端点我可以提供自己的自定义身份验证器(请参阅:Google Cloud Endpoints and user's authentication),我打算在我的自定义身份验证器中调用firebase admin sdk来验证用户提供的令牌。
我的问题是,由于我使用谷歌云端点来构建我的后端,我不知道在我可以验证任何令牌之前,在哪里插入代码来执行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()方法,但是然后将端点部署到app引擎失败了,因为显然,创建android客户端库要求必须使用SystemServiceServlet(并且必须将其命名为&# 34; SystemServiceServlet"。)
我可以在云端点提供的每个api方法中初始化firebase管理应用程序(例如,在我的api的insert方法中),但这看起来效率极低。如何在使用谷歌云端点构建的后端中使用Firebase管理sdk?
非常感谢你的时间
答案 0 :(得分: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;
}
}
答案 1 :(得分:0)
@ Dan7620,提出了另一种替代方法,但它并没有解决问题。这是一个简单的解决方案,它使用Firebase Admin SDK,在Cloud Endpoints模块中正确配置和初始化。我将总结这里的步骤:
将其插入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) {}
}
在您定义的任何端点中的任何 static {} 代码中调用此方法。例如:
static {
ObjectifyService.register(FCMTokenMap.class);
FirebaseService.init();
}