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

GoogleCredentials.refreshIfExpired每次返回新FCM v1访问令牌问题咨询

FCM v1访问令牌刷新问题解答

问题描述

我正尝试通过以下代码获取FCM v1的访问令牌:

static String SCOPES = "https://www.googleapis.com/auth/firebase.messaging";
private static String getAccessToken() throws IOException {
  GoogleCredentials googleCredentials = GoogleCredentials
          .getApplicationDefault()
          .createScoped(Arrays.asList(SCOPES));
  // googleCredentials.refresh();
  googleCredentials.refreshIfExpired();   // Doesn't work either
  return googleCredentials.getAccessToken().getTokenValue();
}

成功获取到令牌并可向FCM推送消息,但问题是每次调用refreshIfExpired()时,即使之前的令牌尚未过期,也会返回新令牌。这是预期行为吗?我是否需要自行检查令牌过期?我猜测获取新令牌存在速率限制,且尝试使用共用的GoogleCredentials对象后仍存在该问题。

另外,刷新令牌存储在哪里?令牌过期检查是在客户端还是服务器端进行?

我查阅了文档但未找到清晰说明,需要明确的解释以及仅在令牌过期时才刷新的实现方法。


解答

1. refreshIfExpired()的行为是否符合预期?

这不是预期行为。问题根源在于你每次调用getAccessToken()时都重新创建了GoogleCredentials实例——新实例没有缓存之前的有效令牌,所以refreshIfExpired()会默认判定没有可用令牌,直接发起刷新请求获取新令牌。

Google的Credentials类本身支持缓存有效令牌,但前提是你复用同一个GoogleCredentials实例,而非每次调用都重新初始化。

2. 是否需要自行检查令牌过期?

不需要完全自行实现,但必须确保复用同一个GoogleCredentials实例。该类内部会自动管理令牌的过期检查与刷新:

  • 调用getAccessToken()时,它会先本地检查当前令牌是否有效(未过期),有效则直接返回;已过期或不存在时,才会自动触发刷新请求。
  • 若显式调用refreshIfExpired(),在复用实例的前提下,它只会在令牌过期时才执行刷新操作。

3. 速率限制问题

Google确实对令牌刷新请求有速率限制,频繁刷新可能触发限流。复用GoogleCredentials实例是避免该问题的核心,因为它会自动复用未过期的令牌,减少不必要的刷新请求。

4. 刷新令牌的存储与过期检查机制

  • 刷新令牌存储:GoogleCredentials实例会将刷新令牌和访问令牌缓存在内存中(默认不持久化到磁盘)。若需持久化,可手动实现序列化存储逻辑,或使用官方提供的FileCredentialsStore工具类(注意安全存储)。
  • 过期检查:过期检查在客户端(你的代码)完成。GoogleCredentials会根据令牌返回的expires_in字段计算过期时间,调用相关方法时先做本地校验,仅当令牌已过期时才会向Google服务器发起刷新请求。

5. 仅在令牌过期时刷新的实现方法

核心是通过单例模式复用同一个GoogleCredentials实例,避免每次初始化新实例:

import com.google.auth.oauth2.GoogleCredentials;
import java.io.IOException;
import java.util.Arrays;

public class FcmTokenProvider {
    private static final String SCOPES = "https://www.googleapis.com/auth/firebase.messaging";
    private static GoogleCredentials cachedCredentials;

    private static void initCredentials() throws IOException {
        if (cachedCredentials == null) {
            synchronized (FcmTokenProvider.class) {
                if (cachedCredentials == null) {
                    cachedCredentials = GoogleCredentials
                            .getApplicationDefault()
                            .createScoped(Arrays.asList(SCOPES));
                }
            }
        }
    }

    public static String getAccessToken() throws IOException {
        initCredentials();
        // 无需显式调用refreshIfExpired(),getAccessToken()会自动处理过期检查与刷新
        return cachedCredentials.getAccessToken().getTokenValue();
    }
}

该实现的特点:

  • cachedCredentials为静态单例,仅初始化一次
  • 调用getAccessToken()时,内部自动完成令牌有效性校验,仅过期时触发刷新
  • 同步锁确保多线程环境下的线程安全

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 07:33:11