实现用户退出Google服务时同步退出Express应用
实现Google退出时同步退出你的Express应用
一、先配置Google Cloud控制台的退出重定向
- 登录Google Cloud控制台,找到你的OAuth 2.0客户端ID配置项。
- 在「已授权的重定向URI」下方,添加你的应用退出回调地址,比如
https://你的域名.com/auth/google/logout/callback。 - 保存配置。
二、后端添加两个退出相关端点
一个用来触发Google退出流程,另一个处理退出后的回调并清理自己应用的登录状态:
// 触发Google退出的端点 app.get('/auth/google/logout', (req, res) => { // 先清除自己应用的JWT cookie res.clearCookie('token', { httpOnly: true }); // 跳转到Google官方退出页面,指定回调地址 const googleLogoutUrl = `https://accounts.google.com/logout?continue=https://你的域名.com/auth/google/logout/callback`; res.redirect(googleLogoutUrl); }); // 处理Google退出后的回调 app.get('/auth/google/logout/callback', (req, res) => { // 如有需要,可在此清理数据库中存储的用户关联数据(如refresh token) // 最后跳回应用首页 res.redirect(origin); });
三、前端主动监听Google登录状态(核心)
用户直接在Gmail等Google服务中退出时,你的应用无法被动接收通知,因此需要前端定期主动检查:
- 引入Google OAuth客户端库:
<script src="https://accounts.google.com/gsi/client" async defer></script>
- 初始化客户端并定时检查登录状态:
window.onload = function() { google.accounts.id.initialize({ client_id: '你的Google客户端ID', callback: handleCredentialResponse }); // 每30秒检查一次登录状态 setInterval(checkGoogleLoginStatus, 30000); }; function checkGoogleLoginStatus() { google.accounts.id.revoke('', (response) => { // 返回错误说明用户已退出Google账号 if (response.error) { // 触发自身应用的退出流程 window.location.href = '/auth/google/logout'; } }); }
也可以通过调用Google Userinfo接口验证(需前端存储Google的access token):
async function checkGoogleLoginStatus() { try { const accessToken = localStorage.getItem('google_access_token'); const response = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', { headers: { 'Authorization': `Bearer ${accessToken}` } }); if (!response.ok) { // 用户已退出Google,触发应用退出 window.location.href = '/auth/google/logout'; } } catch (err) { // 请求失败默认视为已退出 window.location.href = '/auth/google/logout'; } }
四、后端验证增强(可选)
如果将Google的refresh token存储到数据库,可在每次请求验证JWT时,额外检查refresh token的有效性:
首先修改原回调代码,保存refresh token到数据库:
app.get('/auth/google/callback', async (req, res) => { const code = req.query.code as string; const { tokens } = await authClient.getToken(code); authClient.setCredentials(tokens); const { data } = await google.oauth2('v2').userinfo.get({ auth: authClient }); let user = await prisma.user.findUnique({ where: { googleId: data.id! } }); if (!user) { user = await prisma.user.create({ data: { googleId: data.id!, displayName: data.name!, googleRefreshToken: tokens.refresh_token // 保存refresh token }, }); } else { // 用户已存在时更新refresh token await prisma.user.update({ where: { id: user.id }, data: { googleRefreshToken: tokens.refresh_token } }); } const token = jwt.sign(user, secret); res.cookie('token', token, { httpOnly: true, maxAge: 24 * 60 * 60 * 1000 }); res.redirect(origin); });
然后添加中间件验证:
// 验证JWT并检查Google refresh token的中间件 app.use(async (req, res, next) => { const token = req.cookies.token; if (!token) { return next(); } try { const decoded = jwt.verify(token, secret) as any; const user = await prisma.user.findUnique({ where: { id: decoded.id } }); if (!user || !user.googleRefreshToken) { res.clearCookie('token'); return res.redirect('/login'); } // 尝试用refresh token获取新access token,失败则说明已失效 const authClient = new google.auth.OAuth2(CLIENT_ID, CLIENT_SECRET, REDIRECT_URI); authClient.setCredentials({ refresh_token: user.googleRefreshToken }); await authClient.getAccessToken(); req.user = user; next(); } catch (err) { res.clearCookie('token'); res.redirect('/login'); } });
内容的提问来源于stack exchange,提问作者harsh
相关产品推荐
相关产品推荐

