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

Java Servlet调用Google People API遇Token过期/撤销错误求助

问题分析与解决

核心原因

  1. Token存储路径问题:Main类使用的相对路径(如./tokens)在Tomcat容器中会指向Tomcat的bin目录,而非Web项目目录,导致Servlet无法读取有效Token,或刷新Token时无写入权限。
  2. Credential对象线程不安全:Google的Credential实例不能在多线程的Servlet环境中共享复用,直接全局存储会引发Token状态混乱。
  3. 容器权限限制:Tomcat运行用户可能没有读写Token存储目录的权限,导致无法保存刷新后的Token。

具体解决步骤

1. 修正Token存储路径

放弃相对路径,通过ServletContext获取Web项目的绝对路径来存储Token:

// 在Servlet方法内获取项目WEB-INF下的token存储目录
String tokenDir = getServletContext().getRealPath("/WEB-INF/tokens");
File dir = new File(tokenDir);
if (!dir.exists()) {
    dir.mkdirs(); // 确保目录存在并有权限
}
String tokenFilePath = new File(dir, "contacts-token.json").getAbsolutePath();

// 加载客户端密钥(将client_secret.json放在WEB-INF目录下)
GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, 
    new InputStreamReader(getServletContext().getResourceAsStream("/WEB-INF/client_secret.json")));

// 构建授权流程时使用绝对路径
Credential credential = new AuthorizationCodeInstalledApp(
    new AuthorizationCodeFlow.Builder(HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES)
        .setDataStoreFactory(new FileDataStoreFactory(new File(tokenFilePath)))
        .setAccessType("offline") // 必须设置以获取离线刷新Token
        .build(),
    new LocalServerReceiver()).authorize("user");

2. 保证Credential线程安全

Servlet是多线程环境,不要将Credential实例存为Servlet成员变量,每个请求独立加载或处理Token:

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // 每次请求都加载/刷新Credential
    Credential credential = loadValidCredential();
    // 后续调用Google Contacts API逻辑
}

private Credential loadValidCredential() throws IOException {
    Credential credential = // 从绝对路径加载Credential的逻辑
    if (credential.getExpiresInSeconds() <= 60) {
        // 手动刷新Token
        credential.refreshToken();
    }
    return credential;
}

3. 配置Tomcat权限

  • Windows:右键WEB-INF/tokens目录 → 属性 → 安全,给Tomcat运行用户(如NETWORK SERVICE)添加读写权限。
  • Linux:执行命令修改目录权限:
    chown tomcat:tomcat /path/to/your/webapp/WEB-INF/tokens
    chmod 755 /path/to/your/webapp/WEB-INF/tokens
    

4. 强制获取离线刷新Token

授权流程中必须设置setAccessType("offline"),确保能获取到可刷新的Token:

new AuthorizationCodeFlow.Builder(
    HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES)
    .setDataStoreFactory(new FileDataStoreFactory(new File(tokenFilePath)))
    .setAccessType("offline") // 核心配置,无此则无法刷新Token
    .setApprovalPrompt("force") // 首次授权强制获取离线Token,可选
    .build()

5. 重置无效Token

如果旧Token已过期或被撤销,直接删除WEB-INF/tokens目录下的Token文件,重新运行Servlet触发新的授权流程,获取有效Token。

联系人存入MySQL示例

获取联系人数据后,用JDBC写入数据库(建议使用连接池优化性能):

// 假设已从Google Contacts API获取到联系人列表
List<Contact> contacts = ...;

// JDBC连接配置(实际项目建议存到配置文件)
String dbUrl = "jdbc:mysql://localhost:3306/your_db?useSSL=false&serverTimezone=UTC";
String dbUser = "your_db_user";
String dbPass = "your_db_password";

try (Connection conn = DriverManager.getConnection(dbUrl, dbUser, dbPass)) {
    String sql = "INSERT INTO contacts (google_id, display_name, email) VALUES (?, ?, ?)";
    try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
        for (Contact contact : contacts) {
            pstmt.setString(1, contact.getId());
            pstmt.setString(2, contact.getDisplayName());
            // 处理多邮箱情况,此处取第一个邮箱示例
            if (!contact.getEmailAddresses().isEmpty()) {
                pstmt.setString(3, contact.getEmailAddresses().get(0).getValue());
            } else {
                pstmt.setNull(3, Types.VARCHAR);
            }
            pstmt.executeUpdate();
        }
    }
} catch (SQLException e) {
    e.printStackTrace();
}

错误排查补充

若问题仍存在,查看错误栈的具体位置:

  • 若报错在credential.refreshToken(),说明刷新Token已失效,需重新授权。
  • 若报错在读取Token文件时,打印tokenFilePath的值,确认路径是否正确、目录是否有读写权限。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 15:35:24