通过简单的 HttpCall (Postman) 获取 Firebase Bearer 令牌

Getting Firebase Bearer token by simple HttpCall (Postman)

我目前面临以下情况。

通过 google API 端点通过 HttpCall 发送 Firebase 消息:

https://fcm.googleapis.com/v1/projects/projectName/messages:send

这里我们必须使用 OAuth2.0 和有效的 Bearer Token,就像这个问题中讨论的那样:

What Bearer token should I be using for Firebase Cloud Messaging testing?

完成这些步骤后,我能够通过 google API.

发送 Firebase 消息

现在我想通过 HttpCall 获取 Bearer Token,而无需使用 Playground https://developers.google.com/oauthplayground

我找不到任何关于如何通过简单的 HttpCall“交换令牌的授权代码”的文档。我无法实现任何代码,因为我想在“云流”中发送 Firebase 消息,因此无法加载任何外部 DLL(如 Firebase Admin Dll,它将实现此功能)。

感谢任何帮助

您可以使用您的 firebase 服务帐户从 OAuth 访问令牌中获取有效的不记名令牌。使用您的 Service Account credentials from your Firebase console. If it is at all possible within your environment, I suggest using the OAuth 2 options than you can find here: https://firebase.google.com/docs/database/rest/auth#authenticate_with_an_access_token

否则,您将不得不创建凭证,该凭证将提供一个访问令牌,该令牌将是一个有效的不记名令牌。

请注意,这仅适用于以下语言:

  • node.js
  • python
  • java

https://firebase.google.com/docs/cloud-messaging/auth-server#use-credentials-to-mint-access-tokens

下面的代码是安装在 API collection 上的 Postman Pre-Request Script,其中包含您正在测试的路由。其目的是将静态凭据(如 email-password 组合或服务帐户密钥)转换为访问令牌以用于 API 调用。

模拟用户

要代表用户使用它进行测试,您需要在请求中添加 X-Auth-Token-Type: user header(由下面的脚本使用和删除)并且您需要设置following environment variables:

Name Value
firebase_apiKey The Firebase API Key for a web application
firebase_test_user An email for an account used for testing
firebase_test_password A password for an account used for testing

模拟服务帐户(谨慎使用!)

要代表服务帐户使用它进行测试,您需要在请求中添加 X-Auth-Token-Type: admin header(由下面的脚本使用和删除)并且您需要设置上 following environment variables:

Name Value
firebase_privateKey The value of private_key in a Service Account Key
Important: For security do not set the "initial value" for this variable!
firebase_scope (optional) A space-delimited list of scopes to authenticate for.
Note: If omitted, the default Admin SDK scopes are used

Pre-Request 脚本

const { Header, Response, HeaderList } = require('postman-collection');

/**
 * Information about the current Firebase user
 * @typedef {Object} UserInfo
 * @property {String} accessToken - The Firebase ID token for this user
 * @property {String | undefined} displayName - Display name of the user, if available
 * @property {Number} expiresAt - When this token expires as a unix timestamp
 * @property {String | undefined} email - Email associated with the user, if available
 * @property {String} refreshToken - Refresh token for this user's ID token
 * @property {String} uid - User ID for this user
 */

/**
 * Loads a third-party JavaScript module from a CDN (e.g. unpkg, jsDelivr)
 * @param {[String, String, String]} moduleTuple - Array containing the module's ID, its source URL and an optional SHA256 signature
 * @param {Object | (err: any, exports: any) => any} exportsRefOrCallback - Object reference to use as `exports` for the module or a result handler callback
 * @param {(err: any, exports: any) => any} callback - result handler callback
 */
function loadModule(moduleTuple, exportsRefOrCallback, callback = undefined) {
    const exports = arguments.length == 2 ? {} : exportsRefOrCallback;
    callback = arguments.length == 2 ? exportsRefOrCallback : callback;
    const [id, src, signature] = moduleTuple;
   
    if (pm.environment.has("jslibcache_" + id)) {
        const script = pm.environment.get("jslibcache_" + id);

        if (signature && signature === CryptoJS.SHA256(script).toString()) {
            console.log("Using cached copy of " + src);
            try {
              eval(script);
              return callback(null, exports);
            } catch {}
        }
    }

    pm.sendRequest(src, (err, response) => {
        try {
            if (err || response.code !== 200) {
                pm.expect.fail('Could not load external library');
            }

            const script = response.text();
            signature && pm.expect(CryptoJS.SHA256(script).toString(), 'External library (' + id + ') has a bad SHA256 signature').to.equal(signature);
            pm.environment.set("jslibcache_" + id, script);
            eval(script);

            callback(null, exports);
        } catch (err) {
            callback(err, null);
        }
    });
}

/**
 * Signs in a test user using an email and password combination
 * 
 * @param {String} email email of the account to sign in with
 * @param {String} password email of the account to sign in with
 * @param {(error: any, response: Response) => any} callback request result handler
 */
function signInWithEmailAndPassword(email, password, callback) {
    pm.sendRequest({
        url: "https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=" + encodeURIComponent(pm.environment.get("firebase_apiKey")),
        body: JSON.stringify({ email, password, "returnSecureToken": true }),
        headers: new HeaderList({}, [new Header("application/json", "Content-Type")]),
        method: "POST"
    }, callback);
}

/**
 * Builds an Admin SDK compatible JWT using a Service Account key
 * 
 * Required Environment Variables:
 *  - `firebase_privateKey` - the private key from inside a service account key JSON file
 * 
 * Environment Variables:
 *  - `firebase_scope` - scopes used for the access token, space delimited
 * 
 * @param {Boolean | (error: any, idToken: String) => any} callbackOrForceRefresh token result handler or `true` to force using a fresh user token
 * @param {(error: any, idToken: String) => any} [callback] token result handler
 */
function getAdminToken(callbackOrForceRefresh, callback) {
    let forceRefresh = Boolean(callbackOrForceRefresh);
    if (arguments.length === 1) {
        callback = callbackOrForceRefresh;
        forceRefresh = callbackOrForceRefresh = false;
    }

    loadModule(
        ["jsrsasign", "https://unpkg.com/jsrsasign@10.3.0/lib/jsrsasign.js", "39b7a00e9eed7d20b2e60fff0775697ff43160e02e5276868ae8780295598fd3"],
        (loadErr, { KJUR }) => {
            if (loadErr) return callback(loadErr, null);
            
            const exp = pm.environment.get("currentAdmin.exp");
            const nowSecs = Math.floor(Date.now() / 1000);

            if (exp && exp > nowSecs && forceRefresh === false) {
                return callback(null, pm.environment.get("currentAdmin.jwt"));
            }

            try {
                if (!pm.environment.has('firebase_privateKey')) {
                    pm.expect.fail('Missing required environment variable "firebase_privateKey".');
                }

                // use specified scopes, or fallback to Admin SDK defaults
                const scope = pm.environment.get('firebase_scope') || 'https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/firebase.database https://www.googleapis.com/auth/firebase.messaging https://www.googleapis.com/auth/identitytoolkit https://www.googleapis.com/auth/userinfo.email';
                const privateKey = String(pm.environment.get('firebase_privateKey')).replace("\n", "\n");

                const header = {"alg" : "RS256", "typ" : "JWT"};
                
                const claimSet =
                {
                    "iss": "https://securetoken.google.com/" + pm.environment.get("firebase_projectId"),
                    "scope": scope,
                    "aud":"https://accounts.google.com/o/oauth2/auth",
                    "exp": nowSecs + 3600, // now + 1 hour
                    "iat": nowSecs
                }

                const jwt = KJUR.jws.JWS.sign(null, header, claimSet, privateKey);
                
                // comment these lines out to disable caching
                pm.environment.set("currentAdmin.jwt", jwt);
                pm.environment.set("currentAdmin.exp", claimSet.exp);

                callback(null, jwt);
            } catch (err) {
                callback(err, null);
            }
        }
    );
}

/**
 * Builds a User ID Token using an email-password combo
 * 
 * Required Environment Variables:
 *  - `firebase_apiKey` - the Firebase API key for a web application
 *  - `firebase_test_user` - an email for a test user
 *  - `firebase_test_password` - the password for the test user
 * 
 * @param {Boolean | (error: any, idToken: String) => any} callbackOrForceRefresh token result handler or `true` to force using a fresh user token
 * @param {(error: any, idToken: String) => any} [callback] token result handler
 */
function getIdToken(callbackOrForceRefresh, callback) {
    let forceRefresh = Boolean(callbackOrForceRefresh);
    if (arguments.length === 1) {
        callback = callbackOrForceRefresh;
        forceRefresh = callbackOrForceRefresh = false;
    }

    if (pm.environment.has("currentUser") && forceRefresh === false) {
        /** @type UserInfo */
        const currentUser = JSON.parse(pm.environment.has("currentUser"));
        if (currentUser.expiresAt > Date.now()) { // has token expired?
            return callback(null, currentUser.accessToken);
        }
    }

    try {
        if (!pm.environment.has('firebase_apiKey')) {
            pm.expect.fail('Missing required environment variable "firebase_apiKey".');
        }
        if (!pm.environment.has('firebase_test_user')) {
            pm.expect.fail('Missing required environment variable "firebase_test_user".');
        }
        if (!pm.environment.has('firebase_test_password')) {
            pm.expect.fail('Missing required environment variable "firebase_test_password".');
        }
    } catch (err) {
        return callback(err, null);
    }

    signInWithEmailAndPassword(pm.environment.get("firebase_test_user"), pm.environment.get("firebase_test_password"), (err, response) => {
        if (err || response.code !== 200) {
            pm.expect.fail('Could not sign in user: ' + response.json().error.message);
        }

        /** @type String */
        let accessToken;

        try {
            const { idToken, refreshToken, email, displayName, localId: uid, expiresIn } = response.json();
            accessToken = idToken;
            const expiresAt = Date.now() + Number(expiresIn);

            // comment these lines out to disable caching
            pm.environment.set("currentUser", JSON.stringify({ accessToken, refreshToken, email, displayName, uid, expiresAt }));
            // pm.environment.set("currentUser.accessToken", accessToken);
            // pm.environment.set("currentUser.refreshToken", refreshToken);
            // pm.environment.set("currentUser.email", email);
            // pm.environment.set("currentUser.displayName", displayName);
            // pm.environment.set("currentUser.uid", uid);
            // pm.environment.set("currentUser.expiresAt", expiresAt);

        } catch (err) {
            return callback(err, null);
        }

        callback(null, accessToken);
    });
}

const tokenTypeHeader = pm.request.headers.one("X-Auth-Token-Type");
pm.request.removeHeader("X-Auth-Token-Type");

switch (tokenTypeHeader && tokenTypeHeader.value.toLowerCase()) {
    case "admin":
        getAdminToken(false, (err, token) => { 
            if (err || !token) pm.expect.fail("failed to get admin SDK token for request: " + err.message);
            pm.request.addHeader(new Header("Bearer " + token, "Authorization"));
        });
    case "user":
        getIdToken(false, (err, idToken) => {
            if (err || !idToken) pm.expect.fail("failed to get user ID token for request: " + err.message);
            pm.request.addHeader(new Header("Bearer " + idToken, "Authorization"));
        });
        break;
    default:
        break; // no auth, do nothing
}