1. 概述
在本快速教程中,我們將演示如何根據 Spring Security 中定義的用户角色,過濾 JSON 序列化輸出。
2. 我們需要過濾的原因是什麼?
讓我們考慮一個簡單的但常見的用例,即我們有一個為不同角色提供服務的 Web 應用程序。例如,這些角色可以是 用户 和 管理員。
首先,讓我們定義一個要求:管理員 擁有通過公共 REST API 提供的對象內部狀態的完整訪問權限。相反,用户 只能看到對象預定義的屬性集。
我們將使用 Spring Security 框架來防止未經授權的 Web 應用程序資源訪問。
讓我們定義一個我們將作為 REST 響應負載返回的 對象:
class Item {
private int id;
private String name;
private String ownerName;
// getters
}當然,我們也可以為應用程序中每個角色定義一個單獨的數據傳輸對象類。然而,這種方法會導致代碼庫中產生無用的重複或複雜的類層次結構。
另一方面,我們可以利用 Jackson 庫的 JSON 視圖功能。正如我們在下一部分所看到的,它使將 JSON 表示形式定製變得像添加註釋一樣簡單。
3. <em @JsonView 註解
Jackson 庫支持通過標記希望包含在 JSON 表示中的字段,使用 <em @JsonView 註解來定義多個序列化/反序列化上下文。該註解具有一個 必需的 <em Class 類型參數,用於區分上下文。
當我們在類中標記字段時使用 <em @JsonView,應注意,默認情況下,序列化上下文包括所有未明確標記為屬於視圖的屬性。為了覆蓋此行為,可以禁用 <em DEFAULT_VIEW_INCLUSION 映射器功能。
首先,讓我們 定義一個 <em View 類,其中包含一些我們將用作 <em @JsonView 註解參數的內類:
class View {
public static class User {}
public static class Admin extends User {}
}
接下來,我們為我們的類添加了 @JsonView 註解,使得 ownerName 僅對管理員角色可見:
@JsonView(View.User.class)
private final int id;
@JsonView(View.User.class)
private final String name;
@JsonView(View.Admin.class)
private final String ownerName;4. 如何將 @JsonView 註解與 Spring Security 集成
現在,讓我們添加一個枚舉,其中包含所有角色及其名稱。接下來,我們將介紹 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);
}
//...
}最後,我們終於來到了集成中的核心要點。為了將 JSON 視圖與 Spring Security 角色關聯起來,我們需要定義應用於應用程序中所有控制器方法的控制器建議,以完成 JSON 視圖與 Spring Security 角色關聯。
目前,我們唯一需要做的就是覆蓋 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(",")));
}
}
}這樣一來,每一條來自我們應用程序的響應都會經過此處的建議,並且它會根據我們已定義的角色映射查找適當的視圖表示形式。請注意,這種方法要求我們在處理具有多個角色的用户時要格外小心。
5. 結論
在本簡短教程中,我們學習瞭如何在 Web 應用程序中根據 Spring Security 角色過濾 JSON 輸出。