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

如何为Google Cloud Tasks的HTTP POST请求添加安全防护?

解决Google Cloud Tasks触发HTTP函数的安全验证问题

我在应用中用Google Cloud Tasks做事件调度,到点触发任务后会更新UI状态,但发现一个严重的安全问题:只要有人知道HTTP请求的格式,就能随便调用我的云函数schedulerCallback,进而删除Firestore里的events文档——完全没有任何验证机制。

原创建任务代码

const task = {
   name: `${taskName}/myTask-${snapshot.id}-${expireSeconds}`,
   httpRequest: {
     httpMethod: 'POST',
     url: `https://${location}-${project}.cloudfunctions.net/schedulerCallback?eventId=${snapshot.id}`,
     body: Buffer.from(JSON.stringify(payload)).toString('base64'),
     headers: { 'Content-Type': 'application/json' }
  },
   scheduleTime: {
     seconds: expirationAtSeconds
  }
};

await tasksClient.createTask({ parent: queuePath, task });

原HTTP触发器代码

export const schedulerCallback = functions.https.onRequest(async (req, res) => {
   const eventId = req.query.eventId as string;
   try {
      await admin.firestore().collection("events").doc(eventId).delete();
   } catch(e) {
      console.log(e);
      res.status(500).send(e);
   }
});

找了很久都没找到针对性的方案,Stack Overflow上的相关问题也不沾边,最后研究出了几个可行的解决办法,分享给大家:


推荐方案:用OIDC令牌验证请求身份

这是Google官方推荐的方式,让Cloud Tasks在发起请求时携带OIDC ID令牌,云函数验证令牌的合法性,确保请求确实来自Cloud Tasks。

步骤1:修改任务创建代码,添加OIDC令牌配置

创建任务时,在httpRequest里加上oidcToken字段,指定用来签名令牌的服务账号(需要有调用云函数的权限):

const task = {
   name: `${taskName}/myTask-${snapshot.id}-${expireSeconds}`,
   httpRequest: {
     httpMethod: 'POST',
     url: `https://${location}-${project}.cloudfunctions.net/schedulerCallback?eventId=${snapshot.id}`,
     body: Buffer.from(JSON.stringify(payload)).toString('base64'),
     headers: { 'Content-Type': 'application/json' },
     // 添加OIDC令牌配置
     oidcToken: {
       serviceAccountEmail: 'your-service-account@your-project.iam.gserviceaccount.com',
       audience: `https://${location}-${project}.cloudfunctions.net/schedulerCallback`
     }
  },
   scheduleTime: {
     seconds: expirationAtSeconds
  }
};

await tasksClient.createTask({ parent: queuePath, task });

注意:audience必须和云函数的URL完全一致,不能带查询参数。

步骤2:在云函数中验证OIDC令牌

在schedulerCallback函数里,先提取请求头里的Authorization令牌,然后验证其合法性:

import * as admin from 'firebase-admin';
import * as functions from 'firebase-functions';
import { OAuth2Client } from 'google-auth-library';

const authClient = new OAuth2Client();

export const schedulerCallback = functions.https.onRequest(async (req, res) => {
  // 1. 提取Authorization令牌
  const authHeader = req.headers.authorization;
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).send('Unauthorized: Missing or invalid token');
  }
  const idToken = authHeader.split(' ')[1];

  try {
    // 2. 验证令牌
    const ticket = await authClient.verifyIdToken({
      idToken,
      audience: `https://${process.env.LOCATION}-${process.env.PROJECT_ID}.cloudfunctions.net/schedulerCallback`
    });
    const payload = ticket.getPayload();
    // 3. 验证令牌的签发者是否是指定的服务账号
    if (!payload || !payload.email_verified || !payload.email?.endsWith('@your-project.iam.gserviceaccount.com')) {
      return res.status(403).send('Forbidden: Invalid service account');
    }

    // 4. 执行原有业务逻辑
    const eventId = req.query.eventId as string;
    await admin.firestore().collection("events").doc(eventId).delete();
    res.status(200).send('Event deleted successfully');
  } catch (e) {
    console.error('Token verification failed:', e);
    res.status(403).send('Forbidden: Invalid token');
  }
});

备选方案:自定义签名验证

如果不想用OIDC,也可以在创建任务时给payload添加一个签名,函数里验证签名是否正确:

步骤1:创建任务时添加签名

import * as crypto from 'crypto';

// 密钥存在环境变量中,绝对不能硬编码
const SIGNING_SECRET = process.env.SIGNING_SECRET;

// 生成签名,用payload、eventId、过期时间作为签名依据
const signature = crypto
  .createHmac('sha256', SIGNING_SECRET)
  .update(JSON.stringify(payload) + snapshot.id + expireSeconds)
  .digest('hex');

const task = {
   name: `${taskName}/myTask-${snapshot.id}-${expireSeconds}`,
   httpRequest: {
     httpMethod: 'POST',
     url: `https://${location}-${project}.cloudfunctions.net/schedulerCallback?eventId=${snapshot.id}&signature=${signature}`,
     body: Buffer.from(JSON.stringify(payload)).toString('base64'),
     headers: { 'Content-Type': 'application/json' }
  },
   scheduleTime: {
     seconds: expirationAtSeconds
  }
};

await tasksClient.createTask({ parent: queuePath, task });

步骤2:函数里验证签名

import * as crypto from 'crypto';

const SIGNING_SECRET = process.env.SIGNING_SECRET;

export const schedulerCallback = functions.https.onRequest(async (req, res) => {
  const eventId = req.query.eventId as string;
  const receivedSignature = req.query.signature as string;
  const payload = JSON.parse(Buffer.from(req.body, 'base64').toString());

  // 重新计算签名,确保和创建时的参数完全一致
  const expectedSignature = crypto
    .createHmac('sha256', SIGNING_SECRET)
    .update(JSON.stringify(payload) + eventId + expireSeconds)
    .digest('hex');

  if (receivedSignature !== expectedSignature) {
    return res.status(403).send('Forbidden: Invalid signature');
  }

  // 执行业务逻辑
  try {
    await admin.firestore().collection("events").doc(eventId).delete();
    res.status(200).send('Event deleted successfully');
  } catch(e) {
    console.log(e);
    res.status(500).send(e);
  }
});

注意事项

  • OIDC方案是最安全且维护成本最低的,推荐优先使用。
  • 自定义签名的密钥一定要存在环境变量里,绝对不能硬编码到代码中。
  • 确保服务账号有足够的权限:创建任务的账号需要cloudtasks.tasks.create权限,OIDC里的服务账号需要cloudfunctions.invoker权限。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 00:28:09