基於Spring Security Role過濾Jackson JSON輸出內容
在本文中,我們將展示如何根據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(","))); } } }
這樣,我們的應用程式的每個響應都將通過這個路由,它將根據我們定義的角色對映找到合適的返回結果。請注意,此方法要求我們在處理具有多個角色的使用者時要小心 。