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

基于Node与Google OAuth2的API认证方案及令牌刷新咨询

Google OAuth2 API认证:受保护路由授权与令牌刷新问题

概述

我的API需要代表用户访问Google服务,已设置access_type: 'offline'以获取refresh token,使用googleapis库中的oAuth2Client实现认证流程。

现有认证流程

  1. 客户端请求授权URL
    API端生成授权URL的函数:

    getAuthUrl() {
      try {
        const scopes = [
          'https://www.googleapis.com/auth/userinfo.email',
          'https://www.googleapis.com/auth/userinfo.profile',
          'openid',
          'https://www.googleapis.com/auth/youtube.readonly',
        ];
    
        const authUrl = oAuth2Client.generateAuthUrl({
          access_type: 'offline',
          scope: scopes,
          include_granted_scopes: true,
          prompt: 'consent',
        });
    
        return authUrl;
      } catch (e) {
        throw new AuthServiceError((e as Error)?.message);
      }
    }
    
  2. 客户端换取令牌
    客户端获取authorizationCode后,调用API换取令牌:

    async getTokens(authorizationCode: string) {
      try {
        const { tokens } = await oAuth2Client.getToken(authorizationCode);
        oAuth2Client.setCredentials(tokens);
    
        const { credentials } = oAuth2Client;
        const idToken = credentials.id_token;
        const refreshToken = credentials.refresh_token;
    
        if (!idToken) {
          throw new Error(ErrorMessage.TOKENS_MISSING_ID_TOKEN);
        }
        if (!refreshToken) {
          throw new Error(ErrorMessage.TOKENS_MISSING_REFRESH_TOKEN);
        }
    
        return {
          idToken,
          refreshToken,
        };
      } catch (e) {
        throw new AuthServiceError((e as Error)?.message);
      }
    },
    
  3. API端处理用户创建与令牌返回
    API控制器通过令牌获取用户信息,创建/查找用户后返回Google的idToken:

    async googleTokens(req, res, next) {
      try {
        const { authorizationCode } = req.body;
    
        const { idToken, refreshToken } =
          await googleAuthService.getTokens(authorizationCode);
    
        const { userId } = await googleAuthService.getUserInfo(refreshToken);
    
        // 在数据库中查找或创建该userId对应的用户,返回用户数据
        const userData = await db.user.findOrCreate(userId);
    
          res.send({
            data: {
              user: {
                ...userData,
                idToken,
              },
            },
          });
      } catch (e) {
        next(e);
      }
    },
    
  4. 客户端访问受保护路由
    客户端将Google的idToken放在请求头Authorization: Bearer <idToken>中,访问受保护内容。

  5. 服务端验证令牌
    服务端中间件使用google.oauth.OAuth2的verifyIdToken方法验证请求头中的idToken有效性,通过则允许访问。

问题

  1. 步骤2中换取的Google idToken会在1小时后过期,推荐的刷新方式是什么?应该在服务端还是客户端处理?
  2. 当前策略是否能正确实现基于Google OAuth2的API认证?如果不能,结合以下需求如何调整:
    • 代表用户离线访问Google服务
    • 在数据库中创建用户账户,并授权API请求访问受保护内容

解决方案与调整建议

1. 令牌刷新方案:服务端处理,禁止客户端接触refresh token

  • 核心原则:Google的refresh token是长期有效且高风险的凭证,必须仅在服务端存储,绝对不能暴露给客户端。
  • 具体流程:
    1. 首次获取refresh token后,立即将其与用户记录绑定,存入数据库(比如在用户表新增google_refresh_token字段)。
    2. 当客户端的idToken过期时,客户端向服务端发起专门的刷新请求(如POST /refresh-google-token),请求中携带当前的用户标识(或服务端自行签发的会话凭证)。
    3. 服务端验证请求合法性后,从数据库取出对应用户的refresh token,调用oAuth2Client.refreshAccessToken()获取新的idToken和accessToken。
    4. 服务端将新的idToken返回给客户端,客户端更新本地存储即可。

2. 现有认证策略的问题与调整方案

当前策略存在两个核心问题:一是直接返回Google idToken给客户端用于接口授权,缺乏自主会话管理;二是未持久化存储refresh token,无法支持离线访问Google服务。结合需求调整如下:

(1)重构授权凭证:使用服务端自定义JWT替代Google idToken

  • 服务端在完成用户创建后,自行签发一个自定义JWT(而非返回Google的idToken)给客户端。自定义JWT包含用户ID等必要信息,用服务端私钥签名,设置较短的过期时间(如15分钟)。
  • 客户端访问受保护路由时,携带该自定义JWT,服务端中间件验证JWT的签名与有效性即可,无需再调用Google的验证接口,提升性能。

(2)持久化存储Google refresh token,支持离线访问

  • 在步骤3的googleTokens控制器中,将refresh token存入用户记录:
    // 替换原findOrCreate逻辑,新增/更新用户的Google refresh token
    const [user, created] = await db.user.findOrCreate({
      where: { userId },
      defaults: { googleRefreshToken: refreshToken }
    });
    if (!created) {
      await db.user.update({ googleRefreshToken: refreshToken }, { where: { userId } });
    }
    
  • 当需要代表用户调用Google API时,从数据库取出refresh token,初始化oAuth2Client并刷新accessToken:
    async callGoogleApi(userId) {
      const user = await db.user.findByPk(userId);
      const oAuthClient = new google.auth.OAuth2(CLIENT_ID, CLIENT_SECRET, REDIRECT_URI);
      oAuthClient.setCredentials({ refresh_token: user.googleRefreshToken });
      
      // 自动刷新过期的accessToken
      const { credentials } = await oAuthClient.refreshAccessToken();
      oAuthClient.setCredentials(credentials);
      
      // 调用Google API,比如YouTube
      const youtube = google.youtube({ version: 'v3', auth: oAuthClient });
      const response = await youtube.channels.list({ mine: true, part: 'snippet' });
      return response.data;
    }
    

(3)优化令牌刷新流程

  • 客户端的自定义JWT过期后,调用服务端的POST /refresh-session接口,携带过期的JWT。
  • 服务端验证JWT的合法性(即使过期,只要签名有效即可),重新签发新的自定义JWT返回给客户端,维持会话有效性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 20:50:17