调用refreshToken()遇401 Unauthorized,如何更新Google AccessToken?
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,提问作者Александр Хворостенко

