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. 註解
Jackson 庫支持通過標記我們希望包含在 JSON 表示中的字段(使用 註解)來定義多個序列化/反序列化上下文。此註解具有一個 必需的 參數,該參數類型用於區分上下文。
在我們的類中標記字段時使用 註解,我們應該記住,默認情況下,序列化上下文包括所有未明確標記為屬於視圖的屬性。為了覆蓋此行為,我們可以禁用 映射器功能。
首先,讓我們 定義一個 參數作為內部類的 類:
class View {
public static class User {}
public static class Admin extends User {}
}
接下來,我們在我們的類中添加 註解,使 僅對管理員角色可見:
@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 角色聯繫起來,我們需要定義控制器建議,該建議應用於應用程序中的所有控制器方法。
目前,我們唯一需要做的就是覆蓋 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 輸出。