1. Overview
1.概述
In this quick tutorial, we’ll show how to filter JSON serialization output depending on a user role defined in Spring Security.
在这个快速教程中,我们将展示如何根据Spring Security中定义的用户角色来过滤JSON序列化输出。
2. Why Do We Need To Filter?
2.我们为什么需要过滤?
Let’s consider a simple yet common use case where we have a web application that serves users with different roles. For example, let these roles be User and Admin.
让我们考虑一个简单而常见的用例,我们有一个Web应用程序,为具有不同角色的用户服务。例如,让这些角色成为User和Admin。
To begin with, let’s define a requirement that Admins have full access to the internal state of objects exposed via a public REST API. On the contrary, Users should see only a predefined set of objects’ properties.
首先,让我们定义一个需求:Admins可以完全访问通过公共REST API暴露的对象的内部状态。相反,用户应该只看到一组预定义的对象属性。
We’ll use the Spring Security framework to prevent unauthorized access to web application resources.
我们将使用Spring安全框架来防止对Web应用程序资源的未授权访问。
Let’s define an object that we’ll return as a REST response payload in our API:
让我们定义一个对象,我们将在我们的API中作为REST响应的有效载荷返回。
class Item {
private int id;
private String name;
private String ownerName;
// getters
}
Of course, we could’ve defined a separate data transfer object class for each role present in our application. However, this approach will introduce useless duplications or sophisticated class hierarchies to our codebase.
当然,我们可以为我们应用程序中的每个角色定义一个单独的数据传输对象类。然而,这种方法会给我们的代码库带来无用的重复或复杂的类层次结构。
On the other hand, we can employ the use of the Jackson library’s JSON views feature. As we’ll see in the next section, it makes customizing JSON representation as easy as adding an annotation on a field.
另一方面,我们可以使用Jackson库的JSON视图功能。正如我们在下一节中所看到的,它使定制JSON表示法变得像在一个字段上添加注释一样容易。
3. @JsonView Annotation
3.@JsonView注释
The Jackson library supports defining multiple serialization/deserialization contexts by marking fields we want to include in JSON representation with the @JsonView annotation. This annotation has a required parameter of a Class type that is used to tell contexts apart.
Jackson库支持定义多个序列化/反序列化上下文,方法是用@JsonView注解来标记我们希望包含在JSON表示中的字段。该注解有一个Class类型的必要参数,用于区分上下文。
When marking fields in our class with @JsonView, we should keep in mind that, by default, the serialization context includes all the properties that are not explicitly marked as being part of a view. In order to override this behavior, we can disable the DEFAULT_VIEW_INCLUSION mapper feature.
当用@JsonView标记我们类中的字段时,我们应该记住,默认情况下,序列化上下文包括所有没有明确标记为视图一部分的属性。为了覆盖这一行为,我们可以禁用DEFAULT_VIEW_INCLUSION映射器功能。
Firstly, let’s define a View class with some inner classes that we’ll be using as an argument for the @JsonView annotation:
首先,让我们定义一个View类,其中有一些内层类,我们将把它们作为@JsonView注解的参数。
class View {
public static class User {}
public static class Admin extends User {}
}
Next, we add @JsonView annotations to our class, making ownerName accessible to the admin role only:
接下来,我们给我们的类添加@JsonView注解,使ownerName仅能被管理员角色访问。
@JsonView(View.User.class)
private int id;
@JsonView(View.User.class)
private String name;
@JsonView(View.Admin.class)
private String ownerName;
4. How to Integrate @JsonView Annotation With Spring Security
4.如何将@JsonView注解与Spring Security结合起来
Now, let’s add an enumeration containing all the roles and their names. After that, let’s introduce a mapping between JSON views and security roles:
现在,让我们添加一个包含所有角色和他们名字的枚举。之后,让我们在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);
}
//...
}
Finally, we’ve come to the central point of our integration. In order to tie up JSON views and Spring Security roles, we need to define controller advice that applies to all the controller methods in our application.
最后,我们来到了整合的中心点。为了将JSON视图和Spring Security角色联系起来,我们需要定义控制器建议,适用于我们应用程序中的所有控制器方法。
And so far, the only thing we need to do is to override the beforeBodyWriteInternal method of the AbstractMappingJacksonResponseBodyAdvice class:
而到目前为止,我们唯一需要做的就是覆盖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(",")));
}
}
}
This way, every response from our application will go through this advice, and it’ll find the appropriate view representation according to the role mapping we have defined. Note that this approach requires us to be careful when dealing with users that have multiple roles.
这样,我们应用程序的每一个响应都会经过这个建议,它会根据我们定义的角色映射找到合适的视图表示。请注意,这种方法要求我们在处理有多个角色的用户时要小心谨慎。
5. Conclusion
5.总结
In this brief tutorial, we’ve learned how to filter JSON output in a web application based on a Spring Security role.
在这个简短的教程中,我们已经学会了如何根据Spring Security的角色来过滤Web应用程序中的JSON输出。
All the related code can be found over on Github.
所有相关的代码都可以在Github上找到over。