1. 概述
本教程將介紹 Spring Security Framework 中 permitAll() 和 anonymous() 方法,以及 Spring Security 表達式如何定義這些安全訪問方式。Spring Security Framework 旨在防止漏洞攻擊,併為 Web 應用程序提供身份驗證和授權功能。通過利用 Spring Security Framework,Web 應用程序可以控制對服務器資源的訪問,例如 HTML 表單、CSS 文件、JS 文件、Web Service 端點等。它還幫助啓用基於角色的訪問控制 (RBAC),從而控制對服務器資源的訪問。
在 Web 應用程序中,始終存在一些用户可以訪問的部分,無需身份驗證。然而,也有一些部分,用户身份驗證並不重要。有趣的是,有時經過身份驗證的用户也無法訪問某些服務器資源。
我們將討論這些情況,並瞭解 permitAll() 和 anonymous() 方法 如何使用 Spring Security 表達式定義這些安全訪問方式。
2. 安全需求
在繼續之前,我們先設想一個電子商務網站,並考慮以下需求:
- 匿名用户和已認證用户均可查看網站上的產品
- 為匿名用户和已認證用户請求提供審計日誌
- 匿名用户可以訪問註冊表單,而已認證用户則不能
- 只有已認證用户才能查看其購物車
3. 控制器和 WebSecurity 配置
首先,讓我們定義我們的控制器類,該類包含電子商務網站的端點:
@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();
}
}基本上,我們已經定義了以下內容:
- 一個 AuditInterceptor 過濾器,在 AnonymousAuthenticationFilter 後,用於記錄匿名和已認證用户發出的請求。
- 用户必須強制認證才能訪問路徑 /private。
- 所有用户都可以訪問路徑 /public/showProducts。
- 只有匿名用户才能訪問路徑 /public/registerUser。
我們還配置了一個用户 spring,該用户將在文章中用於調用 EcommerceController 中定義的 Web 服務端點。
4. 在 HttpSecurity 中使用 permitAll() 方法
在類 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. 拒絕向已認證用户提供註冊用户屏幕訪問權限
在 <em >EcommerceWebSecurityConfig</em > 類中,使用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 對所有用户開放,而無需區分其身份驗證狀態。