在本文中,我們將展示如何根據Spring Security中定義的用戶角色過濾JSON序列化輸出。
爲什麼我們需要過濾?
讓我們考慮一個簡單但常見的用例,我們有一個Web應用程序,爲不同角色的用戶提供服務。例如,這些角色爲User和Admin。
首先,讓我們定義一個要求,即Admin可以完全訪問通過公共REST API公開的對象的內部狀態。相反,User用戶應該只看到一組預定義的對象屬性。
我們將使用Spring Security框架來防止對Web應用程序資源的未授權訪問。
讓我們定義一個對象,我們將在API中作爲REST響應返回數據:
class Item {
private int id;
private String name;
private String ownerName;
// getters
}
當然,我們可以爲應用程序中的每個角色定義一個單獨的數據傳輸對象類。但是,這種方法會爲我們的代碼庫引入無用的重複或複雜的類層次結構。
另一方面,我們可以使用Jackson庫的JSON View功能。正如我們將在下一節中看到的那樣,它使得自定義JSON表示就像在字段上添加註釋一樣簡單。
@JsonView註釋
Jackson庫支持通過使用@JsonView註解標記我們想要包含在JSON表示中的字段來定義多個序列化/反序列化上下文。此註解具有Class類型的必需參數,用於區分上下文。
使用@JsonView在我們的類中標記字段時,我們應該記住,默認情況下,序列化上下文包括未明確標記爲視圖一部分的所有屬性。爲了覆蓋此行爲,我們可以禁用DEFAULT_VIEW_INCLUSION映射器功能。
首先,讓我們定義一個帶有一些內部類的View類,我們將它們用作@JsonView註解的參數:
class View {
public static class User {}
public static class Admin extends User {}
}
接下來,我們將@JsonView註解添加到我們的類中,使ownerName只能訪問admin角色:
@JsonView(View.User.class)
private int id;
@JsonView(View.User.class)
private String name;
@JsonView(View.Admin.class)
private String ownerName;
如何將@JsonView註解與Spring Security 集成
現在,讓我們添加一個包含所有角色及其名稱的枚舉。之後,讓我們介紹JSONView和安全角色之間的映射:
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);
}
//...
}
最後,我們來到了整合的中心點。爲了綁定JSONView和Spring Security角色,我們需要定義適用於我們應用程序中所有控制器方法的控制器。
到目前爲止,我們唯一需要做的就是覆蓋AbstractMappingJacksonResponseBodyAdvice類的 beforeBodyWriteInternal方法:
@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(",")));
}
}
}
這樣,我們的應用程序的每個響應都將通過這個路由,它將根據我們定義的角色映射找到合適的返回結果。請注意,此方法要求我們在處理具有多個角色的用戶時要小心。