При генерации JWT в Spring Security, как можно сделать полезную нагрузку токена доступа отличной от полезной нагрузки токена обновления?

#spring-security #jwt #spring-security-oauth2

#spring-security #jwt #spring-security-oauth2

Вопрос:

Хотя у нас есть решение для этого, мы хотим знать, есть ли решение получше, потому что наше уродливое и кажется очень неправильным. Итак, кто-нибудь знает о лучшем решении с использованием Spring Security?

Проблема

Мы поддерживаем приложение, которое управляет учетными записями пользователей. При входе в систему мы создаем два JWT — токен доступа и токен обновления. Мы хотели бы, чтобы полезная нагрузка токена доступа содержала другой набор полей, чем токен обновления. Например, если полезная нагрузка токена доступа равна:

 {
  "a" : "foo",
  "b" : "bar",
}
  

мы хотели бы, чтобы полезная нагрузка токена обновления была:

 {
  "a" : "foo",
  "x" : "baz",
}
  

Что мы сделали

Единственное решение, которое мы могли придумать, — это реализовать пользовательский усилитель токена, который расширяет JwtAccessTokenConverter и переопределяет enhance метод таким образом, что содержимое метода суперкласса немного модифицируется для управления содержимым каждой полезной нагрузки. Вот код:

 public class CustomJwtAccessTokenConverter extends JwtAccessTokenConverter {

  @Override
  public OAuth2AccessToken enhance(
      OAuth2AccessToken accessToken,
      OAuth2Authentication authentication
  ) {
    DefaultOAuth2AccessToken result = new DefaultOAuth2AccessToken(accessToken);
    Map<String, Object> info = new LinkedHashMap<>(accessToken.getAdditionalInformation());


    // <custom-code>
    info.remove("x");
    // </custom-code>


    String tokenId = result.getValue();
    if (!info.containsKey(TOKEN_ID)) {
      info.put(TOKEN_ID, tokenId);
    } else {
      tokenId = (String) info.get(TOKEN_ID);
    }
    result.setAdditionalInformation(info);
    result.setValue(encode(result, authentication));
    OAuth2RefreshToken refreshToken = result.getRefreshToken();
    if (refreshToken != null) {
      DefaultOAuth2AccessToken encodedRefreshToken = new DefaultOAuth2AccessToken(accessToken);
      encodedRefreshToken.setValue(refreshToken.getValue());
      // Refresh tokens do not expire unless explicitly of the right type
      encodedRefreshToken.setExpiration(null);
      try {
        Map<String, Object> claims = jsonParser
            .parseMap(JwtHelper.decode(refreshToken.getValue()).getClaims());
        if (claims.containsKey(TOKEN_ID)) {
          encodedRefreshToken.setValue(claims.get(TOKEN_ID).toString());
        }
      } catch (IllegalArgumentException e) {
      }
      Map<String, Object> refreshTokenInfo = new LinkedHashMap<>(
          accessToken.getAdditionalInformation());


      // <custom-code>
      refreshTokenInfo.remove("b");
      // </custom-code>


      refreshTokenInfo.put(TOKEN_ID, encodedRefreshToken.getValue());
      refreshTokenInfo.put(ACCESS_TOKEN_ID, tokenId);
      encodedRefreshToken.setAdditionalInformation(refreshTokenInfo);
      DefaultOAuth2RefreshToken token = new DefaultOAuth2RefreshToken(
          encode(encodedRefreshToken, authentication));
      if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
        Date expiration = ((ExpiringOAuth2RefreshToken) refreshToken).getExpiration();
        encodedRefreshToken.setExpiration(expiration);
        token = new DefaultExpiringOAuth2RefreshToken(encode(encodedRefreshToken, authentication), expiration);
      }
      result.setRefreshToken(token);
    }
    return resu<
  }

}
  

Контекст для разных полезных нагрузок

У нас есть мобильное приложение, которому необходимо передать свои токены доступа и обновления в веб-представление. Приложение, запущенное в веб-представлении, не имеет резервного сервера (приложение React Javascript, обслуживаемое с CDN). Наши токены доступа и обновления содержат вложенные токены, выпущенные из отдельной системы единого входа, в которой мы фактически являемся расширенным прокси. В результате токены, которые мы выпускаем, довольно большие — я сгенерировал токен доступа на 2000 символов в моем собственном тестировании. Чтобы передать токены в веб-представление, мы передаем их через параметр запроса. Поскольку некоторые браузеры имеют ограничения по URL, мы хотим, чтобы токен доступа единого входа присутствовал только в нашем токене доступа, а токен обновления единого входа присутствовал только в нашем токене обновления, поскольку это уменьшит раздувание URL-адреса — токены доступа единого входа являются полноценными JWT, в то время как их токены обновления являются простыми идентификаторами GUID.

Мы знаем, что передача JWT через параметр запроса не является лучшей практикой, и прочитали RFC. У нас жесткие сроки, и это было признано самым быстрым возможным решением. Прямо сейчас мы разрабатываем микросервис обмена токенами, чтобы обойти это в будущем.

Комментарии:

1. Чтобы включить разные полезные нагрузки в access и refresh токен с этой библиотекой, вы следовали подходящему подходу, то есть создали свой пользовательский JwtAccessTokenConverter . Возможно, этот пример поможет вам сделать это более чистым способом: github.com/doctore/Spring5Microservices/blob/master /…