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

2024-02-12

我目前面临以下情况。

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

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

在这里,我们必须将 OAuth2.0 与有效的 Bearer Token 一起使用,如本问题中讨论的:

我应该使用什么承载令牌进行 Firebase 云消息传递测试? https://stackoverflow.com/questions/50399170/what-bearer-token-should-i-be-using-for-firebase-cloud-messaging-testing

遵循这些步骤后,我能够通过 google API 发送 Firebase 消息。

现在我想通过 HttpCall 获取不记名令牌,而无需使用 Playground 执行手动步骤https://developers.google.com/oauthplayground https://developers.google.com/oauthplayground.

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

任何帮助表示赞赏


下面的代码是一个邮递员预请求脚本 https://learning.postman.com/docs/writing-scripts/pre-request-scripts/安装在您的 API 集合上,其中包含您正在测试的路由。其目的是将静态凭据(例如电子邮件密码组合或服务帐户密钥)转换为用于 API 调用的访问令牌。

模拟用户

要使用它代表用户进行测试,您可以添加X-Auth-Token-Type: user请求上的标头(由下面的脚本使用和删除),您需要设置以下环境变量 https://learning.postman.com/docs/sending-requests/managing-environments/#editing-environment-variables:

Name Value
firebase_apiKey The Firebase API Key for a web application https://console.firebase.google.com/project/_/settings/general
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请求上的标头(由下面的脚本使用和删除),您需要设置以下环境变量 https://learning.postman.com/docs/sending-requests/managing-environments/#editing-environment-variables:

Name Value
firebase_privateKey The value of private_key in a Service Account Key https://console.firebase.google.com/project/_/settings/serviceaccounts/adminsdk
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 https://github.com/firebase/firebase-admin-node/blob/2feece31422c62ba9f57751221c8e459abf931c1/src/credential/credential-internal.ts#L111-L117 are used

预请求脚本

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/[email protected] /cdn-cgi/l/email-protection/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"));
        });
        break;
    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
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

通过简单的 HttpCall (Postman) 获取 Firebase Bearer 令牌 的相关文章

随机推荐