【问题标题】:Spring 5, OAuth2 with Google refresh access token after 401 returnSpring 5,401 返回后带有 Google 刷新访问令牌的 OAuth2
【发布时间】:2021-07-05 07:14:49
【问题描述】:

我有简单的配置应用程序

spring.security.oauth2.client.registration.google.clientId=xyz
spring.security.oauth2.client.registration.google.clientSecret=xyz
spring.security.oauth2.client.registration.google.scope=email,profile,openid,https://www.googleapis.com/auth/calendar,https://www.googleapis.com/auth/spreadsheets

我使用 http://localhost:8080/oauth2/authorization/google 登录并正常保存到谷歌日历。

获取访问令牌如下所示:

private String getAccessToken() {
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    String accessToken = null;
    if (authentication.getClass().isAssignableFrom(OAuth2AuthenticationToken.class)) {
      OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication;
      String clientRegistrationId = oauthToken.getAuthorizedClientRegistrationId();
      OAuth2AuthorizedClient client = authorizedClientService.loadAuthorizedClient(
        clientRegistrationId, oauthToken.getName());
      accessToken = client.getAccessToken().getTokenValue();
    }
    return accessToken;
  }

OAuth2AuthorizedClient 包含刷新令牌。

但是 1 小时后访问令牌过期,我不知道如何刷新它。无需重新登录。

https://developers.google.com/calendar/v3/errors#401_invalid_credentials

使用长期刷新令牌获取新的访问令牌。

我找到了这个Spring Google OAuth2 With Refresh Token,但对我不起作用。

在 401 之后也有重新登录消息,但它不是用户友好的。

你能帮帮我吗?提前致谢。

【问题讨论】:

  • 我对 Java 帮不上什么忙,但是如果您查看此示例 developers.google.com/analytics/devguides/reporting/core/v4/…,请查看它如何使用 FileDataStoreFactory 如果您请求离线访问,您将获得一个刷新令牌,GoogleAuthorizationCodeFlow 可以使用它来加载需要时提供新的访问权限
  • 您是否尝试过遵循日历 API quickstart
  • Iamblichus,是的,我做到了,我解决了我的问题,如下所示。要使用 OAuth2 Google 进行身份验证,我使用 Spring 内置机制。它是接收访问令牌和刷新令牌,然后我使用它们通过 Google lib 连接到 Google 日历。

标签: java spring-security google-oauth google-calendar-api spring-oauth2


【解决方案1】:

感谢 DaImTo 的提示。

我更改了我的凭据用法

    Credential credential = new GoogleCredential().setAccessToken(tokenUtils.getAccessToken());

 private Calendar getClient() throws GeneralSecurityException, IOException {
    HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();

    Credential credential = new GoogleCredential.Builder()
      .setJsonFactory(JSON_FACTORY)
      .setTransport(httpTransport)
      .setClientSecrets(clientId, clientSecret)
      .build()
      .setAccessToken(tokenUtils.getAccessToken())
      .setRefreshToken(tokenUtils.getRefreshToken());

    return new Calendar.Builder(httpTransport, JSON_FACTORY, credential)
      .setApplicationName("appname").build();
  }

它有效!

但只有在设置了prompt 的值select_accountconsent 时,应用才会收到刷新令牌。 带值none 或不带prompt 刷新令牌始终为空(access_type=offline)。

这也不友好,因为应用程序用户每次都必须选择谷歌帐户......

你知道一些解决方法吗?

编辑:

public class TokenUtils {

  private final OAuth2AuthorizedClientService authorizedClientService;

  public TokenUtils(OAuth2AuthorizedClientService authorizedClientService) {
    this.authorizedClientService = authorizedClientService;
  }

  public String getAccessToken() {
    OAuth2AuthorizedClient client = getClient();
    return client.getAccessToken().getTokenValue();
  }

  public String getRefreshToken() {
    OAuth2AuthorizedClient client = getClient();
    return client.getRefreshToken().getTokenValue();
  }

  public OAuth2AuthorizedClient getClient() {
    OAuth2AuthenticationToken oauthToken = getOAuthToken();
    return authorizedClientService.loadAuthorizedClient(
      oauthToken.getAuthorizedClientRegistrationId(), oauthToken.getName());
  }

  private OAuth2AuthenticationToken getOAuthToken() {
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    if (authentication.getClass().isAssignableFrom(OAuth2AuthenticationToken.class)) {
      return (OAuth2AuthenticationToken) authentication;
    }
    throw new SecurityException("Authentication is not OAuth2AuthenticationToken");
  }
}

编辑 2:

http
      .oauth2Login()
      .successHandler(new RedirectToPrevAuthSuccessHandler())
      .userInfoEndpoint()
      .oidcUserService(addRolesOidcUserService);
      .oidcUserService(addRolesOidcUserService)
      .and()
      .authorizationEndpoint()
      .authorizationRequestResolver(new AddParamsRequestResolver(
        this.clientRegistrationRepository))
    ;

public class AddParamsRequestResolver implements OAuth2AuthorizationRequestResolver {

  private final OAuth2AuthorizationRequestResolver defaultAuthorizationRequestResolver;

  public AddParamsRequestResolver(ClientRegistrationRepository clientRegistrationRepository) {
    this.defaultAuthorizationRequestResolver =
      new DefaultOAuth2AuthorizationRequestResolver(
        clientRegistrationRepository,
        OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI);
  }

  @Override
  public OAuth2AuthorizationRequest resolve(HttpServletRequest request) {
    OAuth2AuthorizationRequest authorizationRequest =
      this.defaultAuthorizationRequestResolver.resolve(request);

    if (authorizationRequest != null) {
      return addParams(authorizationRequest);
    }
    return null;
  }

  @Override
  public OAuth2AuthorizationRequest resolve(HttpServletRequest request, String clientRegistrationId) {
    OAuth2AuthorizationRequest authorizationRequest =
      this.defaultAuthorizationRequestResolver.resolve(
        request, clientRegistrationId);

    if (authorizationRequest != null) {
      return addParams(authorizationRequest);
    }
    return null;
  }

  private OAuth2AuthorizationRequest addParams(
    OAuth2AuthorizationRequest authorizationRequest) {

    Map<String, Object> additionalParameters =
      new LinkedHashMap<>(authorizationRequest.getAdditionalParameters());
    additionalParameters.put("access_type", "offline");
    additionalParameters.put("prompt", "consent");
//    additionalParameters.put("prompt", "select_account");
//    additionalParameters.put("prompt", "login");

    return OAuth2AuthorizationRequest.from(authorizationRequest)
      .additionalParameters(additionalParameters)
      .build();
  }

【讨论】:

  • 可能是因为 localhost...stackoverflow.com/a/14929345/13729723
  • 什么是 tokenUtils?
  • @JoaquínL.Robles 我在帖子中添加了 tokenUtils。起来。
  • @Dalmto 我们如何在最新版本的谷歌库中做同样的事情 - GoogleCredential 已被弃用。
  • 感谢@wojek,我添加了客户端引用,但 client.getRefreshToken() 始终为空,缺少什么?
猜你喜欢
  • 2020-02-21
  • 2017-04-14
  • 2021-11-21
  • 2021-12-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-21
  • 2015-05-17
相关资源
最近更新 更多