1. 概述
在本文中,我們將學習如何使用 @PreFilter 和 @PostFilter 註解來在 Spring 應用程序中安全操作。
當與已認證的主體信息一起使用時,@PreFilter 和 @PostFilter 允許我們使用 Spring 表達式語言定義細粒度的安全規則。
2. 介紹 和
簡單來説, 和 註解用於根據我們自定義的安全規則過濾對象列表。
定義了方法返回列表的過濾規則,通過將該規則應用於列表中的每個元素。
如果評估值是 true,則該項目將保留在列表中。否則,該項目將被刪除。
運作方式非常相似,但是過濾應用於作為方法輸入參數傳遞的列表。
這兩個註解可以應用於方法或類型(類和接口)。在本文中,我們將只在方法中使用它們。
這些註解默認情況下處於禁用狀態——我們需要使用 註解和 (此註解默認啓用) 來啓用它們,在我們的安全配置中:
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class WebSecurityConfig {
// ...
}
3. 編寫安全規則
為了編寫這些兩個註解中的安全規則,我們將使用 Spring-EL 表達式; 還可以使用內置對象 filterObject以引用正在測試的列表元素。
Spring Security 提供了許多其他內置對象,可以創建非常具體和精確的規則。
例如,我們可以使用 @PreFilter來檢查 assignee屬性是否等於當前已認證用户的 name:
@PostFilter("filterObject.assignee == authentication.name")
List<Task> findAll() {
...
}
由於我們希望方法執行並首先獲取所有任務,然後將每個任務從列表中傳遞到我們的過濾器規則中,因此我們使用了 @PostFilter註解。
因此,如果已認證的用户是 michael,則 findAll方法返回的任務列表將僅包含分配給 michael 的任務,即使數據庫中存在分配給 jim 和 pam 的任務。
現在讓我們使規則變得更有趣。 假設如果用户是經理,則他們可以看到所有任務,而不考慮他們被分配給誰:
@PostFilter("hasRole('MANAGER') or filterObject.assignee == authentication.name")
List<Task> findAll() {
// ...
}
我們使用了內置方法 hasRole來檢查已認證的用户是否具有 MANAGER 角色。 如果 hasRole 返回 true,則任務將保留在最終列表中。 因此,如果用户是經理,則規則將為列表中的每個項目返回 true。 因此,最終列表將包含所有項目。
現在讓我們使用 @PreFilter過濾傳遞給 save方法的列表:
@PreFilter("hasRole('MANAGER') or filterObject.assignee == authentication.name")
Iterable<Task> save(Iterable<Task> entities) {
// ...
}
安全規則與我們在 @PostFilter示例中使用的規則相同。 主要區別在於列表項將在方法執行之前進行過濾,從而允許我們從列表中刪除某些項,防止它們被保存到數據庫中。
因此,jim,如果他不是經理,可能會嘗試保存一個任務列表,其中一些任務分配給 pam。 但是,只會包含分配給 jim 的任務,其他任務將被忽略。
4. 大列表性能
@PostFilter 非常酷且易於使用,但在處理非常大的列表時可能會變得低效,因為檢索操作將檢索所有數據並在之後應用過濾器。
例如,假設我們有數千個任務數據庫中,並且我們想檢索目前分配給 pam 的五個任務。如果使用 @PostFilter,數據庫操作將首先檢索所有任務,然後遍歷所有任務以過濾掉未分配給 pam 的任務。