使用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这类无人值守的场景。
操作步骤
创建服务账号密钥
- 登录Google Cloud控制台,找到你的FCM项目
- 进入「IAM与管理」→「服务账号」,创建或选择现有服务账号
- 给账号添加「Firebase Cloud Messaging Admin」角色,确保有发送通知权限
- 进入服务账号详情→「密钥」→「添加密钥」→「创建新密钥」,选JSON格式下载密钥文件
在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
相关产品推荐
相关产品推荐

