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
相关产品推荐
相关产品推荐

