【未解决】基于Firebase与Google API实现Web应用客户端/服务端授权访问谷歌日历
嘿,你已经用Firebase Auth搞定Google登录了,接下来要实现客户端+服务端全场景(在线+离线)读写Google日历是吧?我帮你把整个流程拆解清楚,一步步来:
一、客户端:获取带日历权限的授权凭证(含离线访问Token)
首先,Firebase默认的Google登录scope只包含基础用户信息,你得手动添加Google Calendar的权限范围,而且要确保拿到refresh token——这是支持离线访问的核心,因为access token会过期,refresh token可以用来刷新出新的access token。
修改你的登录代码:
firebase.initializeApp({apiKey: 'MY_WEB_APP_API_KEY'}); const provider = new firebase.auth.GoogleAuthProvider(); // 添加日历权限范围,按需选择最小必要的: // - 只读日历:https://www.googleapis.com/auth/calendar.readonly // - 读写日历:https://www.googleapis.com/auth/calendar // - 读写日历事件:https://www.googleapis.com/auth/calendar.events provider.addScope('https://www.googleapis.com/auth/calendar'); provider.addScope('https://www.googleapis.com/auth/calendar.events'); // 关键:强制弹出授权确认框,确保能拿到refresh token(哪怕用户之前授权过) provider.setCustomParameters({ prompt: 'select_account consent' }); // 执行登录并保存凭证 firebase.auth().signInWithPopup(provider) .then((result) => { const credential = result.credential; const accessToken = credential.accessToken; // 临时在线凭证 const refreshToken = credential.refreshToken; // 离线刷新凭证,必须存到服务端! // 把refresh token存到Firebase Firestore/Realtime Database,注意设置安全规则 return firebase.firestore().collection('users').doc(result.user.uid).set({ refreshToken: refreshToken, email: result.user.email }, { merge: true }); }) .catch((error) => { console.error('登录/授权失败:', error); });
二、客户端在线读写日历
用Google官方的API客户端库来调用Calendar API,直接用登录拿到的access token初始化:
// 加载Google API客户端库 function loadGapi() { return new Promise((resolve, reject) => { const script = document.createElement('script'); script.src = 'https://apis.google.com/js/api.js'; script.onload = () => gapi.load('client:auth2', resolve); script.onerror = reject; document.body.appendChild(script); }); } // 初始化日历客户端并调用API async function initCalendarClient(accessToken) { await loadGapi(); await gapi.client.init({ apiKey: 'YOUR_GOOGLE_API_KEY', // 你在开发者控制台创建的API密钥 clientId: 'YOUR_OAUTH_CLIENT_ID', // Web应用类型的OAuth客户端ID scope: 'https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/calendar.events' }); // 设置当前的access token gapi.client.setToken({ access_token: accessToken }); // 示例:获取用户的日历列表 try { const response = await gapi.client.calendar.calendarList.list(); console.log('用户日历列表:', response.result.items); } catch (err) { console.error('调用日历API失败:', err); } } // 登录成功后调用 firebase.auth().signInWithPopup(provider).then((result) => { initCalendarClient(result.credential.accessToken); });
三、服务端:离线场景下的日历操作
服务端要用到Firebase Admin SDK和Google API客户端库,通过存在数据库里的refresh token来获取有效的access token,实现离线读写。
1. 安装依赖
npm install firebase-admin googleapis --save
2. 服务端核心代码
const admin = require('firebase-admin'); const { google } = require('googleapis'); // 初始化Firebase Admin SDK(用服务账号密钥,不要硬编码!) admin.initializeApp({ credential: admin.credential.applicationDefault() }); // 创建OAuth2客户端(填你的OAuth客户端ID、密钥、回调地址) const oauth2Client = new google.auth.OAuth2( 'YOUR_OAUTH_CLIENT_ID', 'YOUR_OAUTH_CLIENT_SECRET', // 这个要保密,存在环境变量里! 'YOUR_REDIRECT_URI' // 和客户端OAuth设置的一致,比如http://localhost:3000 ); // 从数据库获取refresh token,刷新得到有效的access token async function getValidAccessToken(userId) { const userDoc = await admin.firestore().collection('users').doc(userId).get(); if (!userDoc.exists) throw new Error('用户不存在'); const { refreshToken, expiresAt } = userDoc.data(); oauth2Client.setCredentials({ refresh_token: refreshToken }); // 如果access token没过期,直接用;过期了就刷新 if (expiresAt && Date.now() < expiresAt) { return userDoc.data().accessToken; } const { credentials } = await oauth2Client.refreshAccessToken(); // 更新数据库里的access token和过期时间 await admin.firestore().collection('users').doc(userId).update({ accessToken: credentials.access_token, expiresAt: credentials.expiry_date }); return credentials.access_token; } // 示例:服务端创建日历事件 async function createCalendarEvent(userId, eventData) { const accessToken = await getValidAccessToken(userId); oauth2Client.setCredentials({ access_token: accessToken }); const calendar = google.calendar({ version: 'v3', auth: oauth2Client }); try { const response = await calendar.events.insert({ calendarId: 'primary', // 操作用户的主日历 resource: eventData }); return response.data; } catch (err) { console.error('创建日历事件失败:', err); throw err; } } // 比如封装成Firebase云函数供客户端调用 exports.createCalendarEvent = functions.https.onCall(async (data, context) => { if (!context.auth) { throw new functions.https.HttpsError('unauthenticated', '请先登录'); } const eventData = { summary: '测试会议', start: { dateTime: '2024-06-01T14:00:00', timeZone: 'Asia/Shanghai' }, end: { dateTime: '2024-06-01T15:00:00', timeZone: 'Asia/Shanghai' } }; const event = await createCalendarEvent(context.auth.uid, eventData); return { eventId: event.id }; });
四、必看的关键注意事项
- 权限最小化:只申请你实际需要的scope,比如只读就用
readonly,避免过度授权。 - Refresh Token安全:绝对不能在客户端暴露refresh token,必须存在服务端数据库,并且设置Firestore安全规则,确保只有用户自己或服务端能访问。
- 密钥保密:OAuth客户端密钥要存在环境变量里,绝对不能硬编码在代码中,尤其是生产环境。
- 重复授权:如果用户之前已经授权过,只有设置
prompt: 'consent'才会重新返回refresh token,所以如果需要更新权限,要触发重新授权。
内容的提问来源于stack exchange,提问作者mlb
相关产品推荐
相关产品推荐

