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

使用FCM REST API通过Cloudflare Workers发通知:永久Token获取咨询

如何获取可永久复用的令牌用于Cloudflare Worker发送FCM通知?

我正在开发一个用于向Android应用发送FCM通知的Cloudflare Worker,目前已实现通知发送功能,但每次都需要手动完成登录流程。原因是从https://accounts.google.com/o/oauth2/v2/auth获取的OAuth2授权码(code)只能一次性换取access_token,无法重复使用;而随access_token一同获取的refresh_token,一年后也会过期失效。请问有没有可重复使用的永久令牌来实现无手动登录的通知发送?

当前代码如下:

export default {
  async fetch(request, env, ctx) {
    const { searchParams } = new URL(request.url)
    let auth_token = searchParams.get('code')
    const req1 = await fetch(`https://oauth2.googleapis.com/token`
      + `?code=${auth_token}`
      + `&redirect_uri=${REDIRECT_URI}`
      + `&client_id=${CLIENT_ID}`
      + `&client_secret=${env.CLIENT_SECRET}`
      + `&grant_type=authorization_code`
      + `&scope=${SCOPE}`,
      { method: "POST" }
    );
    const reqJson = await req1.json()
    const respBody = {
      "message": {
        "token": APP_TOKEN,
        "notification": {
          "body": "This is an FCM notification message!",
          "title": "FCM Message"
        }
      }
    }
    const respHeaders = new Headers({
      "Authorization": "Bearer " + reqJson.access_token,
      "Content-Type": "application/json"
    })
    const notificationResp = fetch(
      `https://fcm.googleapis.com/v1/projects/${PROJECT_NAME}/messages:send`,
      {
        method: "POST",
        body: JSON.stringify(respBody),
        headers: respHeaders
      }
    )

    return new Response(JSON.stringify(reqJson) + "\n" + JSON.stringify((await notificationResp).json()))
  },
};

const APP_TOKEN = "[REDACTED]"
const PROJECT_NAME = "[REDACTED]"
const CLIENT_ID = '[REDACTED]'
const SCOPE = '[REDACTED]'
const REDIRECT_URI = '[REDACTED]'

解决方案:用服务账号密钥替代OAuth2授权码

Google FCM支持使用服务账号密钥生成长期有效的访问令牌,无需用户手动登录,也不存在refresh_token过期问题,适合Worker这类无人值守的场景。

操作步骤

  1. 创建服务账号密钥

    • 登录Google Cloud控制台,找到你的FCM项目
    • 进入「IAM与管理」→「服务账号」,创建或选择现有服务账号
    • 给账号添加「Firebase Cloud Messaging Admin」角色,确保有发送通知权限
    • 进入服务账号详情→「密钥」→「添加密钥」→「创建新密钥」,选JSON格式下载密钥文件
  2. 在Worker中自动生成令牌
    服务账号通过JWT签名向Google令牌服务请求access_token,令牌有效期1小时,到期自动重新生成,全程无需人工干预。

修改后的Worker代码

export default {
  async fetch(request, env, ctx) {
    // 从Cloudflare环境变量读取服务账号JSON密钥
    const serviceAccount = JSON.parse(env.SERVICE_ACCOUNT_KEY);
    
    // 构造JWT头部与载荷
    const jwtHeader = btoa(JSON.stringify({ alg: "RS256", typ: "JWT" }));
    const now = Math.floor(Date.now() / 1000);
    const jwtPayload = btoa(JSON.stringify({
      iss: serviceAccount.client_email,
      scope: "https://www.googleapis.com/auth/firebase.messaging",
      aud: "https://oauth2.googleapis.com/token",
      exp: now + 3600, // 有效期1小时
      iat: now
    }));
    
    // 用服务账号私钥签名JWT
    const encoder = new TextEncoder();
    const data = encoder.encode(`${jwtHeader}.${jwtPayload}`);
    const privateKey = await crypto.subtle.importKey(
      "pkcs8",
      Uint8Array.from(atob(serviceAccount.private_key.replace(/\\n/g, "\n")), c => c.charCodeAt(0)),
      { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
      false,
      ["sign"]
    );
    const signatureBuffer = await crypto.subtle.sign("RSASSA-PKCS1-v1_5", privateKey, data);
    const signature = btoa(String.fromCharCode(...new Uint8Array(signatureBuffer)))
      .replace(/\+/g, "-")
      .replace(/\//g, "_")
      .replace(/=+$/, "");
    const jwt = `${jwtHeader}.${jwtPayload}.${signature}`;
    
    // 请求获取access_token
    const tokenResp = await fetch("https://oauth2.googleapis.com/token", {
      method: "POST",
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
      body: new URLSearchParams({
        grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
        assertion: jwt
      })
    });
    const tokenJson = await tokenResp.json();
    
    // 发送FCM通知
    const respBody = {
      "message": {
        "token": env.APP_TOKEN,
        "notification": {
          "body": "This is an FCM notification message!",
          "title": "FCM Message"
        }
      }
    };
    const notificationResp = await fetch(
      `https://fcm.googleapis.com/v1/projects/${env.PROJECT_NAME}/messages:send`,
      {
        method: "POST",
        body: JSON.stringify(respBody),
        headers: {
          "Authorization": `Bearer ${tokenJson.access_token}`,
          "Content-Type": "application/json"
        }
      }
    );
    const notificationResult = await notificationResp.json();
    
    return new Response(JSON.stringify({ token: tokenJson, notification: notificationResult }), {
      headers: { "Content-Type": "application/json" }
    });
  },
};

配置注意事项

  • 把下载的服务账号JSON内容存入Worker环境变量SERVICE_ACCOUNT_KEY
  • 将APP_TOKEN、PROJECT_NAME也存入环境变量,避免硬编码
  • Cloudflare Worker默认支持Web Crypto API,无需额外配置权限

方案优势

  • 服务账号私钥不会主动过期(除非你手动删除或轮换)
  • 自动生成有效期1小时的access_token,到期自动续期,无需人工操作
  • 完全符合Google OAuth2服务端认证规范,安全性有保障

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 18:22:33