1. 引言
在本教程中,我們將重點介紹 Spring Security 表達式及其在實踐中的應用。 Spring Security 表達式提供了一種聲明式定義授權規則的方式。
它們允許在指定誰可以訪問特定 URL 或執行某些方法時具有靈活性。 這些表達式對於在我們的應用程序的各個級別處理安全至關重要,從 Web 請求授權到方法級別安全。
在查看更復雜的實現,例如 ACL,之前,重要的是要對安全表達式有紮實的理解,因為它們在正確使用時可以非常靈活和強大。
2. Maven 依賴
為了在我們的項目中使用 Spring Security,我們在 pom.xml 中包含了以下依賴:
<dependencies>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>6.1.5</version>
</dependency>
</dependencies><p><em>spring-security-web</em> 依賴項是 Spring Security 的關鍵組成部分,而 spring-core 和 spring-context 則對於構建一個功能完整的 Web 應用程序是必需的。</p>
3. 配置
Spring Security 可以通過基於 Java 的配置進行配置,這提供了更好的靈活性,並且是大多數現代 Spring 應用中推薦的方法。 下面是一個示例:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@ComponentScan("com.baeldung.security")
public class SecurityJavaConfig {
...
}@EnableGlobalMethodSecurity 註解允許使用@PreAuthorize 和 @PostAuthorize 註解,從而實現方法級別的安全控制。通過這些註解,我們可以應用在方法執行之前或之後的安全邏輯。
4. Web 安全表達式
讓我們深入瞭解一些常見的 Spring Security 表達式:
4.1. hasRole(), hasAnyRole()
這些表達式用於指定訪問特定 URL 或方法的所需角色:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.antMatchers("/auth/admin/*").hasRole("ADMIN")
.antMatchers("/auth/*").hasAnyRole("ADMIN", "USER")
.build();
}
上述示例確保只有擁有ADMIN角色的用户才能訪問以/auth/admin/開頭的URL,而ADMIN角色和USER角色均可訪問以/auth/開頭的URL。
4.2. hasAuthority(), hasAnyAuthority()</h3
Roles 和 authorities 都是用於訪問控制的,但它們語義略有不同。 Authorities 不需要 ROLE_ 前綴,Spring 會自動為其添加該前綴。 建議使用 authorities 代替 roles,因為它提供了更大的靈活性。
以下是一個定義具有特定 authorities 的用户的快速示例:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/auth/admin/*").hasAuthority("ADMIN")
.requestMatchers("/auth/*").hasAnyAuthority("ADMIN", "USER"
)
.build();
}4.3. permitAll(), denyAll()
這些表達式允許我們明確地允許或拒絕訪問特定的 URL。 讓我們來看一個例子:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.requestMatchers("/private/**").denyAll()
)
.build();
}此配置使用 permitAll() 允許任何人訪問以 /public/ 開頭的 URL,從而使它們公開可用,而 denyAll() 則完全阻止對以 /private/ 開頭 URL 的訪問,從而有效地禁用這些資源。
4.4.isAnonymous()、isRememberMe()、isAuthenticated()、isFullyAuthenticated()
這些表達式控制基於用户身份驗證狀態的訪問權限:
- isAnonymous():允許未身份驗證的用户訪問。
- isAuthenticated():限制對已身份驗證的用户訪問。
- isFullyAuthenticated():要求用户在執行敏感操作時重新身份驗證。
- isRememberMe():允許通過“記住我”功能身份驗證的用户訪問。
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/login").anonymous()
.requestMatchers("/dashboard").authenticated()
.requestMatchers("/settings").fullyAuthenticated()
)
.build();
}在本示例中,我們使用 anonymous() 以確保僅未身份驗證的用户可以訪問 /login,authenticated() 要求用户已登錄才能訪問 /dashboard,而 fullyAuthenticated() 則強制要求對敏感的 /settings 訪問進行全新登錄,從而增強安全性。
4.5. 主要身份驗證 (principal, authentication)
Spring Security 通過 Principal 和 Authentication 對象提供當前用户身份驗證的詳細信息。這些對象包含有關已認證用户的信息,例如用户名和角色。
例如,當用户向受保護的端點發出請求時,我們可以使用 Principal 對象檢索其詳細信息:
@GetMapping("/profile")
public String profile(Principal principal) {
return "Hello, " + principal.getName();
}在本示例中,profile() 方法從 Principal 對象中提取已登錄用户的姓名,並返回一個問候消息。
或者,如果我們需要關於身份驗證的更多信息,例如角色或身份驗證類型,我們可以使用 Authentication 對象。
@GetMapping("/user-details")
public String userDetails(Authentication authentication) {
return "User: " + authentication.getName() + ", Roles: " + authentication.getAuthorities();
}4.6. hasPermission() API 文檔</h3
hasPermission()表達式是 Spring Security 的 ACL 系統的一部分,允許對領域對象進行精細化的權限檢查。它與僅檢查角色(例如hasRole('ADMIN'))不同,而是驗證特定資源上的權限。
例如,我們可以強制只有具有isEditor權限的用户才能批准文章:
@PreAuthorize("hasPermission(#article, 'isEditor')")
public void acceptArticle(Article article) {
...
}為了使用 <em >hasPermission</em >,我們必須配置一個自定義的 <em >PermissionEvaluator</em >>。這個評估器定義瞭如何針對不同的對象檢查權限:
@Override
protected MethodSecurityExpressionHandler expressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler =
new DefaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(new CustomInterfaceImplementation());
return expressionHandler;
}5. 基於方法的安全控制,使用 @PreAuthorize 和 @PostAuthorize
Spring Security 也允許我們在方法級別應用安全表達式。通過使用 @PreAuthorize 和 @PostAuthorize,我們可以執行方法執行之前或之後的安全檢查。這在基於角色或其他條件來保護服務時尤其有用。
以下示例確保只有擁有 ADMIN 角色的用户才能執行 deleteUser() 方法:
@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(User user) {
// Method logic
}
相反,@PostAuthorize() 確保方法執行後,用户只能查看自己的用户對象。
@PostAuthorize("returnObject.owner == authentication.name")
public User getUser(Long id) {
// Fetch the user and return
}6. 路徑變量和 @PreFilter / @PostFilter
Spring Security 也支持使用 @PreFilter 和 @PostFilter 註解在方法中過濾集合。這在我們需要在方法執行之前或之後,根據已登錄用户的權限或其他條件進行過濾數據時非常有用。
@PreFilter 註解確保僅將集合中的某些元素傳遞給方法,在執行之前過濾掉未授權的元素:
@PreFilter("filterObject.owner == authentication.name")
public void updatePosts(List<Post> posts) {
// Update posts for the user
}表達式 filterObject.owner == authentication.name 確保僅處理屬於已登錄用户 (authentication.name) 的帖子。
@PostFilter 註解確保在執行後僅返回允許的元素給調用者。這在檢索數據時,限制訪問僅授權項目時非常有用。 讓我們來看一個例子:
@PostFilter("filterObject.owner == authentication.name")
public List<Post> getPosts() {
// Get all the posts and ret
}
@PostFilter 註解會過濾掉不屬於當前已認證用户的所有帖子。
7. 結論
在本文中,我們探討了 Spring Security 表達式及其在保護 Web 應用程序中的應用。 通過利用諸如 hasRole()、hasAuthority()、permitAll() 等表達式,我們可以對應用程序的安全性進行精細調整。 此外,使用 @PreAuthorize、@PostAuthorize、@PreFilter 和 @PostFilter 允許我們對方法級別的安全性以及數據過濾進行更精細的控制。