You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

非Firebase托管NextJS应用调用云函数遇CORS及安全问题

问题描述

我有一个部署在非Firebase托管服务上的NextJS应用,同时使用Firebase Cloud Functions可调用函数。函数代码如下:

functions/index.js:

// index.js
const functions = require('firebase-functions')
const admin = require('firebase-admin')
admin.initializeApp()
const db = admin.firestore()

const function1 = require('./src/function1')

exports.function1 = functions
    .region('us-east1')
    .https.onCall((data, context) => {
      return function1.handler(data, context, db)
    })

functions/src/function1.js:

exports.handler = (data, context, db) => {
  // 业务代码
}

该函数通过context和db操作Firestore增删记录,本地模拟正常,但线上部署后调用出现CORS错误。找到的解决方案都是开放未授权访问,虽能解决CORS但存在安全隐患,希望得到以下两种方案之一:

  1. 允许未授权调用,但通过context参数校验用户权限,未授权则终止执行;
  2. 仅允许特定URI(如https://www.mywebsite.com)调用云函数。

补充信息

  • 使用Firebase Auth;
  • CORS错误信息:

Access to fetch at 'https://us-east1-(projectid).cloudfunctions.net/function1' from origin 'https://(projectid).vercel.app' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

  • 客户端调用代码:
import { functions } from './FirebaseConfig'
// ^ 上述代码导入内容:
// export const firebase = initializeApp(firebaseConfig)
// export const functions = getFunctions(firebase, 'us-east1')

const function1 = httpsCallable(functions, 'function1')

// 通过按钮点击事件调用
<button             
  onClick={() => {
    function1({
      // 传入数据
    })
      .then(result => {
        // 处理结果
      })
      .catch(error => {
        // 处理错误
      })
  }}
>
  调用函数
</button>

解决方案

方案1:允许未授权调用,但校验用户权限

Firebase可调用函数的context.auth会自动携带已登录用户的认证信息,只要用官方httpsCallable调用,CORS会被自动处理。你只需要在函数中添加权限校验逻辑:

修改function1.js的handler:

exports.handler = (data, context, db) => {
  // 校验用户是否已登录
  if (!context.auth) {
    throw new functions.https.HttpsError('unauthenticated', '用户未登录,无法调用该函数');
  }

  // 可选:校验用户是否有特定权限(比如从Firestore读取用户角色)
  const userId = context.auth.uid;
  return db.collection('users').doc(userId).get()
    .then(doc => {
      if (!doc.exists || !doc.data().isAllowed) {
        throw new functions.https.HttpsError('permission-denied', '无权限调用该函数');
      }
      // 执行你的业务逻辑
      return { success: true };
    })
    .catch(error => {
      throw new functions.https.HttpsError('internal', error.message);
    });
}

这样即使开放了函数的未授权访问,未登录或无权限的用户会被直接拦截,同时不会出现跨域问题。

方案2:仅允许特定域名调用云函数

如果想限制只有特定域名能调用函数,需要改用https.onRequest(因为https.onCall的CORS由Firebase自动处理,无法自定义),同时手动处理CORS和认证:

修改index.js:

const functions = require('firebase-functions')
const admin = require('firebase-admin')
const cors = require('cors')({ origin: 'https://www.mywebsite.com' }); // 指定允许的域名
admin.initializeApp()
const db = admin.firestore()

const function1 = require('./src/function1')

exports.function1 = functions
    .region('us-east1')
    .https.onRequest((req, res) => {
      cors(req, res, () => {
        // 校验用户认证
        const idToken = req.headers.authorization?.split('Bearer ')[1];
        if (!idToken) {
          return res.status(401).send('未授权');
        }
        admin.auth().verifyIdToken(idToken)
          .then(decodedToken => {
            const userId = decodedToken.uid;
            // 传递userId和db到handler
            return function1.handler(req.body, userId, db)
              .then(result => res.status(200).send(result))
              .catch(error => res.status(400).send(error.message));
          })
          .catch(error => res.status(401).send('无效的认证令牌'));
      });
    });

对应的客户端调用需要修改为手动携带认证令牌:

import { getAuth, getIdToken } from "firebase/auth";

const callFunction1 = async (data) => {
  const auth = getAuth();
  const token = await getIdToken(auth.currentUser);
  const response = await fetch('https://us-east1-(projectid).cloudfunctions.net/function1', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(data)
  });
  return response.json();
}

// 按钮调用
<button onClick={() => {
  callFunction1({/* 数据 */})
    .then(result => {/* 处理结果 */})
    .catch(error => {/* 处理错误 */})
}}>调用函数</button>

这种方式严格限制了调用域名,同时保留了用户认证校验。


后续疑问解答
  1. 能否通过auth.uid校验用户是否登录?用户能否伪造他人UID?
    可以通过context.auth.uid校验登录状态。Firebase可调用函数的context.auth是由Firebase Auth服务验证令牌后生成的,无法被伪造——客户端传递的ID令牌会被自动验证,只有合法令牌才能获取到auth信息,不用担心UID被伪造。Admin SDK绕过Firestore安全规则是正常的,只要你正确校验了用户身份,就能确保只有授权用户能执行操作。

  2. 浏览器GET方法访问函数返回400错误?
    Firebase可调用函数(https.onCall)只接受POST请求,并且要求请求体是特定格式的JSON。用GET访问触发400错误是正常的,不符合onCall的请求规范。如果需要支持GET,需要改用https.onRequest实现。

内容的提问来源于stack exchange,提问作者aabdulahad

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 10:05:31