1. Обзор
В этом кратком руководстве мы покажем, как фильтровать выходные данные сериализации JSON в зависимости от роли пользователя, определенной в Spring Security.
2. Зачем нам фильтровать?
Давайте рассмотрим простой, но распространенный вариант использования, когда у нас есть веб-приложение, которое обслуживает пользователей с разными ролями. Например, пусть эти роли будут User
и Admin
.
Для начала давайте определим требование, чтобы администраторы
имели полный доступ к внутреннему состоянию объектов, предоставляемых через общедоступный REST API. Наоборот, Пользователи
должны видеть только предопределенный набор свойств объектов.
Мы будем использовать платформу Spring Security для предотвращения несанкционированного доступа к ресурсам веб-приложений.
Давайте определим объект, который мы будем возвращать в качестве полезной нагрузки ответа REST в нашем API:
class Item {
private int id;
private String name;
private String ownerName;
// getters
}
Конечно, мы могли бы определить отдельный класс объектов передачи данных для каждой роли, присутствующей в нашем приложении. Однако такой подход привнесет в нашу кодовую базу бесполезные дублирования или сложные иерархии классов.
С другой стороны, мы можем использовать функцию просмотра JSON библиотеки Джексона . Как мы увидим в следующем разделе, настроить представление JSON так же просто, как добавить аннотацию к полю.
3. @JsonView
Аннотация
Библиотека Джексона поддерживает определение нескольких контекстов сериализации/десериализации , помечая поля, которые мы хотим включить в представление JSON, аннотацией @JsonView
. Эта аннотация имеет обязательный параметр типа класса ,
который используется для разделения контекстов.
Отмечая поля в нашем классе с помощью @JsonView
, мы должны помнить, что по умолчанию контекст сериализации включает все свойства, которые явно не помечены как часть представления. Чтобы переопределить это поведение, мы можем отключить функцию сопоставления DEFAULT_VIEW_INCLUSION
.
Во-первых, давайте определим класс View
с некоторыми внутренними классами, которые мы будем использовать в качестве аргумента для аннотации @JsonView
:
class View {
public static class User {}
public static class Admin extends User {}
}
Затем мы добавляем аннотации @JsonView
в наш класс, делая ownerName
доступным только для роли администратора:
@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<Role, Class> MAPPING = new HashMap<>();
static {
MAPPING.put(Role.ADMIN, Admin.class);
MAPPING.put(Role.USER, User.class);
}
//...
}
Наконец, мы подошли к центральной точке нашей интеграции. Чтобы связать представления JSON и роли Spring Security, нам нужно определить рекомендации контроллера , которые применяются ко всем методам контроллера в нашем приложении.
И пока нам нужно только переопределить метод beforeBodyWriteInternal класса
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<? extends GrantedAuthority> authorities
= SecurityContextHolder.getContext().getAuthentication().getAuthorities();
List<Class> 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 Security.
Весь соответствующий код можно найти на Github .