1. 概述
在本教程中,我們將學習 Spring Security Framework 中 permitAll() 和 anonymous() 方法。 Spring Security Framework 能夠幫助防止漏洞攻擊,併為 Web 應用程序啓用身份驗證和授權。 通過利用它,Web 應用程序可以控制對服務器資源的訪問,例如 HTML 表單、CSS 文件、JS 文件、Web Service 端點等。 它還能夠幫助啓用基於角色的訪問控制 (RBAC) 以訪問服務器資源。
在 Web 應用程序中,始終存在用户只能訪問的某些部分。 然而,也有一些部分,用户身份驗證並不重要。 令人驚訝的是,還存在經過身份驗證的用户無法訪問某些服務器資源的場景。
我們將簡要討論所有這些情況,並瞭解 permitAll() 和 anonymous() 方法如何通過 Spring Security 表達式定義這些類型的安全訪問。
2. 安全需求
在繼續之前,讓我們設想一個電子商務網站,並滿足以下要求:- 匿名用户和已認證用户均可查看網站上的產品
- 為匿名用户和已認證用户請求提供審計日誌
- 匿名用户可以訪問用户註冊表單,而已認證用户則不能
- 只有已認證用户才能查看他們的購物車
首先,讓我們定義我們的控制器類,該類包含電子商務網站的端點:
@RestController
public class EcommerceController {
@GetMapping("/private/showCart")
public @ResponseBody String showCart() {
return "Show Cart";
}
@GetMapping("/public/showProducts")
public @ResponseBody String listProducts() {
return "List Products";
}
@GetMapping("/public/registerUser")
public @ResponseBody String registerUser() {
return "Register User";
}
}
此前,我們討論過網站的安全要求。下面在 EcommerceWebSecruityConfig 類中實現這些安全要求:
@Configuration
@EnableWebSecurity
public class EcommerceWebSecurityConfig {
@Bean
public InMemoryUserDetailsManager userDetailsService(PasswordEncoder passwordEncoder) {
UserDetails user = User.withUsername("spring")
.password(passwordEncoder.encode("secret"))
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http.addFilterAfter(new AuditInterceptor(), AnonymousAuthenticationFilter.class)
.authorizeHttpRequests(request -> request.requestMatchers(new AntPathRequestMatcher("/private/**"))
.authenticated())
.httpBasic(Customizer.withDefaults())
.authorizeHttpRequests(request -> request.requestMatchers(new AntPathRequestMatcher("/public/showProducts"))
.permitAll())
.authorizeHttpRequests(request -> request.requestMatchers(new AntPathRequestMatcher("/public/registerUser"))
.anonymous())
.build();
}
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
基本上,我們定義了以下內容:
- 一個在 AnonymousAuthenticationFilter 之後定義的 AuditInterceptor 過濾器,用於記錄匿名和已認證用户發出的請求
- 必須對訪問路徑 /private 的用户進行身份驗證
- 所有用户都可以訪問路徑 /public/showProducts
- 只有匿名用户才能訪問路徑 /public/registerUser
我們還配置了一個名為 spring 的用户,該用户將在文章中用於調用 EcommerceController 中定義的 Web 服務端點。
4. 方法 permitAll() 在 HttpSecurity 中
基本上,在類 EcommerceWebSecurityConfig 中,我們使用了 permitAll() 來開放端點 /public/showProducts 對所有用户而言。
現在,讓我們看看它是否有效:
@WithMockUser(username = "spring", password = "secret")
@Test
public void givenAuthenticatedUser_whenAccessToProductLinePage_thenAllowAccess() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/public/showProducts"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().string("List Products"));
}
@WithAnonymousUser
@Test
public void givenAnonymousUser_whenAccessToProductLinePage_thenAllowAccess() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/public/showProducts"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().string("List Products"));
}
正如預期的那樣,匿名用户和已認證用户都可以訪問該頁面。
此外,在 Spring Security 6 中,permitAll() 有助於高效地保護靜態資源,如 JS 和 CSS 文件。 此外,我們應該始終優先使用 permitAll() 而不是忽略 靜態資源在 Spring Security 過濾器鏈中。 因為過濾器鏈無法為忽略的靜態資源設置安全標頭。
5. 方法 anonymous() 在 HttpSecurity 中
在開始實施電子商務網站的要求之前,理解表達式 anonymous() 的概念至關重要。
遵循 Spring Security 原則,我們需要為所有用户定義權限和限制。這同樣適用於匿名用户。因此,他們與 ROLE_ANONYMOUS 相關聯。
5.1. 實現 AuditInterceptor
Spring Security 填充了匿名用户的 Authentication 對象在 AnonymousAuthenticationFilter 中。這在通過攔截器對電子商務網站中匿名用户和註冊用户執行的操作進行審計時很有幫助。
以下是 AuditInterceptor 的概要,我們在類 EcommerceWebSecurityConfig 中之前已配置它:
public class AuditInterceptor extends OncePerRequestFilter {
private final Logger logger = LoggerFactory.getLogger(AuditInterceptor.class);
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication instanceof AnonymousAuthenticationToken) {
logger.info("Audit anonymous user");
}
if (authentication instanceof UsernamePasswordAuthenticationToken) {
logger.info("Audit registered user");
}
filterChain.doFilter(request, response);
}
}
即使對於匿名用户,Authentication 對象也不是 null。這導致了 AuditInterceptor 的穩健實現。它具有用於審計匿名用户和已認證用户的分流流程。
5.2. 拒絕向已認證用户提供註冊用户屏幕的訪問權限
在類 EcommerceWebSecurityConfig 中,使用表達式 anonymous(), 我們確保只有匿名用户才能訪問端點 public/registerUser。而已認證用户則無法訪問它。
讓我們看看它是否實現了預期的結果:
@WithAnonymousUser
@Test
public void givenAnonymousUser_whenAccessToUserRegisterPage_thenAllowAccess() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/public/registerUser"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().string("Register User"));
}
因此,匿名用户可以訪問用户註冊頁面。
同樣,它是否能夠拒絕向已認證用户提供訪問權限?讓我們找出答案:
@WithMockUser(username = "spring", password = "secret")
@Test
public void givenAuthenticatedUser_whenAccessToUserRegisterPage_thenDenyAccess() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/public/registerUser"))
.andExpect(MockMvcResultMatchers.status().isForbidden());
}
上述方法成功地拒絕了已認證用户對用户註冊頁面的訪問權限。
與 permitAll() 方法不同,anonymous() 也可以用於為不需要身份驗證的靜態資源提供服務。
6. 結論
在本教程中,通過示例,我們演示了 permitAll() 和 anonymous() 方法之間的區別。
anonymous() 用於我們擁有應該僅對匿名用户可訪問的公共內容的情況。 相比之下,permitAll() 用於允許所有用户訪問特定 URL,而無需區分其身份驗證狀態。