Рубрики
Без рубрики

Фильтрация вывода Jackson JSON На основе роли безопасности Spring

Узнайте, как фильтровать вывод JSON в веб-приложении на основе роли безопасности Spring.

Автор оригинала: baeldung.

1. Обзор

В этом кратком руководстве мы покажем, как фильтровать выходные данные сериализации JSON в зависимости от роли пользователя, определенной в Spring Security.

2. Зачем Нам Нужно Фильтровать?

Давайте рассмотрим простой, но распространенный случай использования, когда у нас есть веб-приложение, которое обслуживает пользователей с разными ролями. Например, пусть эти роли будут User и Admin .

Для начала давайте определим требование, чтобы Администраторы имели полный доступ к внутреннему состоянию объектов , доступных через общедоступный REST API. Напротив, Пользователи должны видеть только заранее определенный набор свойств объектов.

Мы будем использовать структуру безопасности Spring для предотвращения несанкционированного доступа к ресурсам веб-приложений.

Давайте определим объект, который мы вернем в качестве полезной нагрузки ответа REST в нашем API:

class Item {
    private int id;
    private String name;
    private String ownerName;

    // getters
}

Конечно, мы могли бы определить отдельный класс объектов передачи данных для каждой роли, присутствующей в нашем приложении. Однако такой подход приведет к появлению бесполезных дубликатов или сложных иерархий классов в нашей кодовой базе.

С другой стороны, мы можем использовать функцию представления JSON библиотеки Джексона |. Как мы увидим в следующем разделе, это делает настройку представления JSON такой же простой, как добавление аннотации в поле.

3. Аннотация @JsonView

Библиотека Джексона поддерживает определение нескольких контекстов сериализации/десериализации , помечая поля, которые мы хотим включить в представление JSON, аннотацией @JsonView . Эта аннотация имеет обязательный параметр класса типа , который используется для различения контекстов.

При маркировке полей в нашем классе с помощью @JsonView мы должны иметь в виду , что по умолчанию контекст сериализации включает все свойства, которые явно не помечены как часть представления. Чтобы переопределить это поведение, мы можем отключить функцию DEFAULT_VIEW_INCLUSION mapper.

Во-первых, давайте определим класс View с некоторыми внутренними классами, которые мы будем использовать в качестве аргумента для @JsonView аннотации :

class View {
    public static class User {}
    public static class Admin extends User {}
}

Затем мы добавляем @JsonView аннотации к нашему классу, делая имя владельца доступным только для роли администратора:

@JsonView(View.User.class)
private int id;
@JsonView(View.User.class)
private String name;
@JsonView(View.Admin.class)
private String ownerName;

4. Как интегрировать Аннотацию @JsonView С Spring Security

Теперь давайте добавим перечисление, содержащее все роли и их имена. После этого давайте представим сопоставление между представлениями JSON и ролями безопасности:

enum Role {
    ROLE_USER,
    ROLE_ADMIN
}

class View {

    public static final Map MAPPING = new HashMap<>();

    static {
        MAPPING.put(Role.ADMIN, Admin.class);
        MAPPING.put(Role.USER, User.class);
    }

    //...
}

Наконец, мы подошли к центральной точке нашей интеграции. Чтобы связать представления JSON и роли безопасности Spring, нам нужно определить рекомендации контроллера , которые применяются ко всем методам контроллера в нашем приложении.

И до сих пор единственное, что нам нужно сделать, это переопределить перед тем, как написать внутренний метод AbstractMappingJacksonResponseBodyAdvice класса:

@RestControllerAdvice
class SecurityJsonViewControllerAdvice extends AbstractMappingJacksonResponseBodyAdvice {

    @Override
    protected void beforeBodyWriteInternal(
      MappingJacksonValue bodyContainer,
      MediaType contentType,
      MethodParameter returnType,
      ServerHttpRequest request,
      ServerHttpResponse response) {
        if (SecurityContextHolder.getContext().getAuthentication() != null
          && SecurityContextHolder.getContext().getAuthentication().getAuthorities() != null) {
            Collection authorities
              = SecurityContextHolder.getContext().getAuthentication().getAuthorities();
            List jsonViews = authorities.stream()
              .map(GrantedAuthority::getAuthority)
              .map(AppConfig.Role::valueOf)
              .map(View.MAPPING::get)
              .collect(Collectors.toList());
            if (jsonViews.size() == 1) {
                bodyContainer.setSerializationView(jsonViews.get(0));
                return;
            }
            throw new IllegalArgumentException("Ambiguous @JsonView declaration for roles "
              + authorities.stream()
              .map(GrantedAuthority::getAuthority).collect(Collectors.joining(",")));
        }
    }
}

Таким образом, каждый ответ от нашего приложения будет проходить через этот совет , и он найдет соответствующее представление представления в соответствии с определенным нами отображением ролей. Обратите внимание, что этот подход требует от нас быть осторожными при работе с пользователями, имеющими несколько ролей .

5. Заключение

В этом кратком руководстве мы узнали, как фильтровать вывод JSON в веб-приложении на основе роли безопасности Spring.

Весь соответствующий код можно найти на Github .