Reputation: 423
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:
{
"id": 1,
"baseInfo": "Some basic info"
}
{
"id": 1,
"baseInfo": "Some basic info",
"userInfo": "Info only the user should be able to see"
}
{
"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
Upvotes: 3
Views: 1935
Reputation: 423
Thanks to @second 's comment, I have been able to figure out the issue. As it turns out I made two small mistakes:
The first change is setting spring.jackson.mapper.default_view_inclusion
to true
in your application.properties
file. By doing so, Jackson will include all properties by default, removing the issue where the payload is empty
Whenever an unauthenticated user tries to access the system, we need to set the serialization view to a base user. In my post above, I have the User
View but I never used it. Setting this as a default serialization view (in the interceptor above) solves the issue and the result is the one I stated above.
I uploaded an example here. It's very simple but it shows how the different fields can be excluded. Use like this:
http://localhost:8080/test
{
"id":1,
"baseInfo":"This is the base info"
}
http://localhost:8080/test?view=user
{
"id":1,
"baseInfo":"This is the base info",
"userInfo":"This is the user info"
}
http://localhost:8080/test?view=admin
{
"id":1,
"baseInfo":"This is the base info",
"userInfo": "This is the user info",
"secretInfo":"This is the secret info"
}
Hope this helps!
Upvotes: 4