Filtrando a saída JSON de Jackson com base na função de segurança da primavera
1. Visão geral
Neste tutorial rápido, mostraremos como filtrar a saída de serialização JSON, dependendo da função de usuário definida no Spring Security.
2. Por que precisamos filtrar?
Vamos considerar um caso de uso simples, porém comum, em que temos um aplicativo Web que atende usuários com funções diferentes. Por exemplo, sejam essas funçõesUsereAdmin.
Para começar, vamos definir um requisito queAdmins have full access to the internal state of objects exposto por meio de uma API REST pública. Pelo contrário,Users should see only a predefined set of objects’ properties.
UsaremosSpring Security framework para evitar o acesso não autorizado a recursos de aplicativos da web.
Vamos definir um objeto que retornaremos como uma carga útil de resposta REST em nossa API:
class Item {
private int id;
private String name;
private String ownerName;
// getters
}
Claro, poderíamos ter definido uma classe de objeto de transferência de dados separada para cada função presente em nosso aplicativo. No entanto, essa abordagem apresentará duplicações inúteis ou hierarquias sofisticadas de classe à nossa base de código.
Por outro lado, podemos empregar o uso deJackson library’s JSON views feature. Como veremos na próxima seção, ele criacustomizing JSON representation as easy as adding an annotation em um campo.
3. @JsonView Anotação
A biblioteca Jackson suporta a definição demultiple serialization/deserialization contexts marcando os campos que queremos incluir na representação JSON com a anotação@JsonView. Esta anotação tem umrequired parameter of a Class type que é usado para diferenciar os contextos.
Ao marcar campos em nossa classe com@JsonView, devemos ter em mente que, por padrão, o contexto de serialização inclui todas as propriedades que não estão explicitamente marcadas como parte de uma visualização. Para substituir esse comportamento, podemos desativar o recurso de mapeadorDEFAULT_VIEW_INCLUSION.
Em primeiro lugar, vamosdefine a View class with some inner classes that we’ll be using as an argument for the @JsonView annotation:
class View {
public static class User {}
public static class Admin extends User {}
}
Em seguida, adicionamos anotações de@JsonView à nossa classe, tornandoownerName acessível apenas para a função administrativa:
@JsonView(View.User.class)
private int id;
@JsonView(View.User.class)
private String name;
@JsonView(View.Admin.class)
private String ownerName;
4. Como integrar a anotação@JsonView com Spring Security
Agora, vamos adicionar uma enumeração contendo todas as funções e seus nomes. Depois disso, vamos apresentar um mapeamento entre visualizações JSON e funções de segurança:
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);
}
//...
}
Finalmente, chegamos ao ponto central da nossa integração. Para vincular visualizações JSON e funções Spring Security, precisamosdefine controller advice que se aplica a todos os métodos do controlador em nosso aplicativo.
E até agora, a única coisa que precisamos fazer é a classeoverride the beforeBodyWriteInternal method of the 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 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(",")));
}
}
}
Dessa forma,every response from our application will go through this advice, e ele encontrará a representação de visualização apropriada de acordo com o mapeamento de função que definimos. Observe que essa abordagem requerbe careful when dealing with users that have multiple roles.
5. Conclusão
Neste breve tutorial, aprendemos como filtrar a saída JSON em um aplicativo da web com base em uma função Spring Security.
Todos os códigos relacionados podem ser encontradosover on Github.