基於Spring Security Role過濾Jackson JSON輸出內容

在本文中,咱們將展現如何根據Spring Security中定義的用戶角色過濾JSON序列化輸出。java

爲何咱們須要過濾?


讓咱們考慮一個簡單但常見的用例,咱們有一個Web應用程序,爲不一樣角色的用戶提供服務。例如,這些角色爲User和Admin。json

首先,讓咱們定義一個要求,即Admin能夠徹底訪問經過公共REST API公開的對象的內部狀態。相反,User用戶應該只看到一組預約義的對象屬性安全

咱們將使用Spring Security框架來防止對Web應用程序資源的未受權訪問。app

讓咱們定義一個對象,咱們將在API中做爲REST響應返回數據:框架

class Item {
    private int id;
    private String name;
    private String ownerName;
 
    // getters
}

固然,咱們能夠爲應用程序中的每一個角色定義一個單獨的數據傳輸對象類。可是,這種方法會爲咱們的代碼庫引入無用的重複或複雜的類層次結構。ide

另外一方面,咱們可使用Jackson庫的JSON View功能。正如咱們將在下一節中看到的那樣,它使得自定義JSON表示就像在字段上添加註釋同樣簡單。code

@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(",")));
        }
    }
}

這樣,咱們的應用程序的每一個響應都將經過這個路由,它將根據咱們定義的角色映射找到合適的返回結果。請注意,此方法要求咱們在處理具備多個角色的用戶時要當心

相關文章
相關標籤/搜索