Java Servlet调用Google People API遇Token过期/撤销错误求助
问题分析与解决
核心原因
- Token存储路径问题:Main类使用的相对路径(如
./tokens)在Tomcat容器中会指向Tomcat的bin目录,而非Web项目目录,导致Servlet无法读取有效Token,或刷新Token时无写入权限。 - Credential对象线程不安全:Google的
Credential实例不能在多线程的Servlet环境中共享复用,直接全局存储会引发Token状态混乱。 - 容器权限限制: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
相关产品推荐
相关产品推荐

