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

调用refreshToken()遇401 Unauthorized,如何更新Google AccessToken?

问题:Google OAuth2刷新AccessToken时出现401 Unauthorized错误

com.google.api.client.auth.oauth2.TokenResponseException: 401
Unauthorized POST https://oauth2.googleapis.com/token

通过Spring Security获取AccessToken和RefreshToken后,应用在AccessToken过期前可正常运行,但过期后调用refreshToken()或直接使用GoogleRefreshTokenRequest都会触发上述401错误。删除credential.refreshToken()代码后,AccessToken有效期内(1小时)可正常调用Google API,过期后仍报错,求正确的AccessToken更新方式。


相关代码

Credential创建代码

public void initCredential() {
    try {
      Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
      if (ObjectUtils.isEmpty(properties)) {
        properties = createAdsProperties(managerProvider);
      }
      authorizedClient = authorizedClientService.loadAuthorizedClient(
          ID_CLIENT_PROVIDER,
          authentication.getName());
      credential = new GoogleCredential.Builder()
          .setClientSecrets(properties.getProperty(CLIENT_ID),
              properties.getProperty(CLIENT_SECRET))
          .setJsonFactory(JSON_FACTORY)
          .setTransport(GoogleNetHttpTransport.newTrustedTransport())
          .build()
          .setAccessToken(authorizedClient.getAccessToken().getTokenValue())
          .setRefreshToken(authorizedClient.getRefreshToken().getTokenValue());

//      TokenResponse response =
//          new GoogleRefreshTokenRequest(GoogleNetHttpTransport.newTrustedTransport(),
//              JSON_FACTORY,
//              authorizedClient.getRefreshToken().getTokenValue(),
//              properties.getProperty(CLIENT_ID),
//              properties.getProperty(CLIENT_SECRET)).execute(); //TokenResponseException 401

        credential.refreshToken(); //TokenResponseException 401

    } catch (IOException | GeneralSecurityException e) {
      String errorMessage = e.getMessage();
      log.error(errorMessage);
      show(errorMessage);
      throw new CustomException(errorMessage, e);
    }
  }

注释的GoogleRefreshTokenRequest方式同样会触发401错误。

Spring Security配置

spring.security.oauth2.client.registration.google.client-id=xxxx
spring.security.oauth2.client.registration.google.client-secret=xxxx
spring.security.oauth2.client.registration.google.redirect-uri=http://localhost:8080/login/oauth2/code/google
spring.security.oauth2.client.registration.google.scope=https://www.googleapis.com/auth/userinfo.email,\
  openid,\
  https://www.googleapis.com/auth/userinfo.profile
spring.security.oauth2.client.provider.google.token-uri=https://oauth2.googleapis.com/token
spring.security.oauth2.client.provider.google.authorization-uri=https://accounts.google.com/o/oauth2/auth?prompt=consent&access_type=offline
spring.security.oauth2.client.provider.google.user-info-uri=https://www.googleapis.com/oauth2/v3/userinfo

DriveService初始化代码示例

driveService = new Drive.Builder(
          GoogleNetHttpTransport.newTrustedTransport(),
          GoogleConnectionService.JSON_FACTORY, googleConnectionService.getCredential())
          .setApplicationName(GoogleConnectionService.APPLICATION_NAME)
          .build();

 driveService.channels().stop(channel).execute();

解决方案

1. 补全API权限Scope

当前配置的Scope仅包含用户信息相关权限,但你用到了Drive API,必须添加对应权限(比如https://www.googleapis.com/auth/drive或更细分的Drive权限),否则即使RefreshToken有效,也无法刷新出能访问Drive的AccessToken。

修改Spring Security配置的scope字段:

spring.security.oauth2.client.registration.google.scope=https://www.googleapis.com/auth/userinfo.email,\
  openid,\
  https://www.googleapis.com/auth/userinfo.profile,\
  https://www.googleapis.com/auth/drive

2. 利用Spring Security的自动刷新机制

不要手动调用refreshToken(),Spring Security的OAuth2AuthorizedClient支持自动刷新AccessToken,直接从授权服务获取最新的客户端实例即可:

public void initCredential() {
    try {
      Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
      if (ObjectUtils.isEmpty(properties)) {
        properties = createAdsProperties(managerProvider);
      }
      // 直接获取最新的授权客户端,Spring Security会自动刷新过期token
      authorizedClient = authorizedClientService.loadAuthorizedClient(
          ID_CLIENT_PROVIDER,
          authentication.getName());
      
      credential = new GoogleCredential.Builder()
          .setClientSecrets(properties.getProperty(CLIENT_ID),
              properties.getProperty(CLIENT_SECRET))
          .setJsonFactory(JSON_FACTORY)
          .setTransport(GoogleNetHttpTransport.newTrustedTransport())
          .build()
          .setAccessToken(authorizedClient.getAccessToken().getTokenValue())
          .setRefreshToken(authorizedClient.getRefreshToken().getTokenValue());

    } catch (IOException | GeneralSecurityException e) {
      String errorMessage = e.getMessage();
      log.error(errorMessage);
      show(errorMessage);
      throw new CustomException(errorMessage, e);
    }
  }

3. 开启GoogleCredential的自动刷新能力

GoogleCredential本身支持在调用API时自动检查并刷新Token,只需确保构建时配置完整,无需手动调用refreshToken():

credential = new GoogleCredential.Builder()
    .setClientSecrets(properties.getProperty(CLIENT_ID), properties.getProperty(CLIENT_SECRET))
    .setJsonFactory(JSON_FACTORY)
    .setTransport(GoogleNetHttpTransport.newTrustedTransport())
    .build()
    .setAccessToken(authorizedClient.getAccessToken().getTokenValue())
    .setRefreshToken(authorizedClient.getRefreshToken().getTokenValue())
    .setTokenServerEncodedUrl("https://oauth2.googleapis.com/token");
// 后续调用Drive API时,Credential会自动处理token过期刷新

4. 验证RefreshToken有效性

  • 确认用户首次授权时没有跳过离线访问授权(配置里的access_type=offline已正确设置,但需确保用户授权流程正常)。
  • 检查用户Google账号是否已撤销该应用的权限,若已撤销,需让用户重新授权。

内容的提问来源于stack exchange,提问作者Александр Хворостенко

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 06:00:17