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

实现用户退出Google服务时同步退出Express应用

实现Google退出时同步退出你的Express应用

一、先配置Google Cloud控制台的退出重定向

  1. 登录Google Cloud控制台,找到你的OAuth 2.0客户端ID配置项。
  2. 在「已授权的重定向URI」下方,添加你的应用退出回调地址,比如 https://你的域名.com/auth/google/logout/callback。
  3. 保存配置。

二、后端添加两个退出相关端点

一个用来触发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服务中退出时,你的应用无法被动接收通知,因此需要前端定期主动检查:

  1. 引入Google OAuth客户端库:
<script src="https://accounts.google.com/gsi/client" async defer></script>
  1. 初始化客户端并定时检查登录状态:
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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 16:24:56