I am trying to selectively hide certain fields in a response, depending on the role of the user requesting it. From what I have been able to understand, JsonView
from Jackson
may be the way to go.
I need to be able to display all fields, UNLESS they have been marked with a specific access level. I have created the following structure for the access levels:
(Quick note: I have left the base user in there, but it shouldn't really matter)
public class View {
public static final Map<Role, Class> MAPPING = new HashMap<>();
static {
MAPPING.put(Role.ROLE_ADMIN, Admin.class);
MAPPING.put(Role.ROLE_USER, AuthenticatedUser.class);
}
public interface User {}
public interface AuthenticatedUser extends User {}
public interface Admin extends AuthenticatedUser {}
}
And used them as follows:
@Entity
public class SomeEntity {
@Id
@GeneratedValue
private Integer id;
private String baseInfo;
@JsonView(View.AuthenticatedUser.class)
private String userInfo;
@JsonView(View.Admin.class)
private String secretInfo;
...
}
(PS: I have stripped away all the non essential annotations, etc.)
Now, I expect that depending on the level of access, the responses are as follows:
- Non-authenticated user:
{ "id": 1, "baseInfo": "Some basic info" }
- Authenticated user:
{ "id": 1, "baseInfo": "Some basic info", "userInfo": "Info only the user should be able to see" }
- Admin user:
{ "id": 1, "baseInfo": "Some basic info", "userInfo": "Info only the user should be able to see", "secretInfo": "Info only the admin can see" }
I used code from this tutorial to integrate it into spring security and my own structure.
@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<Role> foundRoles = authorities.stream()
.map(GrantedAuthority::getAuthority)
.map(Role::get).collect(Collectors.toList());
if (foundRoles.contains(null)) {
System.err.println("User has no auth. Setting no serialization view");
return;
}
List<Class> jsonViews = foundRoles.stream().map(View.MAPPING::get)
.collect(Collectors.toList());
if (jsonViews.size() == 1) {
System.err.println("Setting " + jsonViews.get(0) + " as serialization view");
bodyContainer.setSerializationView(jsonViews.get(0));
return;
}
throw new IllegalArgumentException("Ambiguous @JsonView declaration for roles "
+ authorities.stream()
.map(GrantedAuthority::getAuthority).collect(Collectors.joining(",")));
}
System.err.println("No auth found");
}
}
(Pardon the awful code, I have been trying a bit of everything...)
At this point, I expect the result to be as the one that I stated above, but I keep on getting all fields, without any kind of filtering. No type of annotation avoids the fields from being serialized. I know I could set the default to be that all fields are excluded EXCEPT for the ones I annotate, but it would mean annotating all fields and the project is quite big.
Is my initial assumption about JsonView
wrong, or am I making a mistake somewhere?
Thanks in advance