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

Java调用Gmail API报400 invalid_grant 刷新令牌频繁失效问题

Google OAuth refresh_token 闲置数天即失效返回invalid_grant问题修复

问题复现表现

  • 基于Google OAuth Playground生成的refresh_token,在Java业务代码中用于兑换新access_token,功能上线初期正常,间隔数天未运行后调用接口直接报错
  • 接口返回错误响应:
{
"error": "invalid_grant",
"error_description": "Token has been expired or revoked."
}
  • 重新在OAuth Playground生成新的refresh_token替换credentials.json内的旧值后,功能立刻恢复,但闲置数天后会再次出现完全相同的错误

根因说明

  1. 核心配置问题:你当前GCP项目的OAuth同意屏幕处于「Testing(测试)」状态,该状态下Google会强制所有签发的refresh_token有效期为7天,到期自动失效,和代码逻辑无关——这也是你每次换完token能用几天、到点就坏的核心原因。OAuth Playground默认生成的临时凭证本身也是7天有效期,和测试状态的规则一致。
  2. 代码存在两处不规范问题:
    • 调用的token端点是已废弃的老地址https://accounts.google.com/o/oauth2/token,和官方当前推荐的https://oauth2.googleapis.com/token存在校验逻辑差异,偶发会触发凭证校验异常
    • 请求参数中多余传入了project_id字段,该字段不属于token接口的合法入参,同时缺少表单请求必须的Content-Type头,错误场景下直接读取输入流会拿不到完整错误信息

修复步骤

  • 第一步:进入GCP控制台的OAuth同意屏幕页面,将应用发布状态从「Testing」切换为「In production(生产)」。如果使用的scope不属于Google标注的敏感/受限类,不需要提交审核,切换操作即时生效。
  • 第二步:状态切换完成后,重新生成一次refresh_token写入配置,该状态下签发的refresh_token为长期有效,不会出现7天自动过期的问题,除非用户手动撤销授权、或者你重置了对应client的secret。
  • 第三步:修正Java代码中的不规范逻辑,修正后参考代码如下:
private String getAccessToken() {
    try {
        credentials.put("grant_type", "refresh_token");
        credentials.put("client_id", credential.get("client_id"));
        credentials.put("client_secret", credential.get("client_secret"));
        credentials.put("refresh_token", credential.get("refresh_token"));
        // 移除无效的project_id参数,token接口不需要该字段

        StringBuilder postData = new StringBuilder();
        for (Map.Entry<String, Object> param : credentials.entrySet()) {
            if (postData.length() != 0) {
                postData.append('&');
            }
            postData.append(URLEncoder.encode(param.getKey(), "UTF-8"));
            postData.append('=');
            postData.append(URLEncoder.encode(String.valueOf(param.getValue()), "UTF-8"));
        }
        byte[] postDataBytes = postData.toString().getBytes("UTF-8");
        // 替换为官方最新的token端点
        URL url = new URL("https://oauth2.googleapis.com/token");
        HttpURLConnection con = (HttpURLConnection) url.openConnection();
        con.setDoOutput(true);
        con.setUseCaches(false);
        con.setRequestMethod("POST");
        // 补全表单请求头
        con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
        con.getOutputStream().write(postDataBytes);

        // 兼容错误场景的响应读取,避免4xx时直接抛异常拿不到错误信息
        int responseCode = con.getResponseCode();
        InputStream respStream = responseCode >= 400 ? con.getErrorStream() : con.getInputStream();
        BufferedReader reader = new BufferedReader(new InputStreamReader(respStream, "UTF-8"));
        StringBuffer buffer = new StringBuffer();
        for (String line = reader.readLine(); line != null; line = reader.readLine()) {
            buffer.append(line);
        }
        JSONObject json = new JSONObject(buffer.toString());
        return json.getString("access_token");
    } catch (Exception ex) {
        log.error("Error on generating access token:"+ExceptionUtils.getFullStackTrace(ex));
    }
    return null;
}

额外注意事项

  • 同一个Google账号对同一个OAuth client_id,最多只能留存50个有效refresh_token,超过上限后最早生成的token会被Google静默作废,不要反复生成测试token覆盖配置避免正常token被挤掉。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 21:36:16