1. 概述
我們可能需要在 Spring Boot 應用程序的不同路徑中應用多個安全過濾器。
在本教程中,我們將探討兩種自定義安全的方法:通過使用 <a href="https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/config/annotation/web/configuration/EnableWebSecurity.html"><em @EnableWebSecurity</em></em> 和 <a href="https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/config/annotation/method/configuration/GlobalMethodSecurityConfiguration.html"><em @EnableGlobalMethodSecurity</em></em>。
為了説明這些差異,我們將使用一個包含一些管理員資源、已認證用户資源以及我們樂於供任何人下載的公共資源的簡單應用程序。
2. Spring Boot Security
Spring Boot Security 是 Spring Boot 框架中用於安全應用程序的模塊。它提供了一套用於保護應用程序的工具和配置,包括身份驗證、授權和會話管理。
Spring Boot Security 簡化了 Spring Boot 應用程序的安全配置過程,並提供了與 Spring Security 的兼容性。
核心功能:
- 身份驗證 (Authentication): 驗證用户身份,確定用户是否是他們聲稱的那個人。
- 授權 (Authorization): 確定用户是否具有訪問特定資源或執行特定操作的權限。
- 會話管理 (Session Management): 管理用户會話,包括會話創建、銷燬和管理。
- 基於角色的訪問控制 (RBAC): 允許根據用户角色分配權限。
- 基於屬性的訪問控制 (ABAC): 允許根據更復雜的條件分配權限。
Spring Boot Security 支持多種身份驗證機制,包括:
- 用户名/密碼
- OAuth 2.0
- LDAP
- 基於 JWT 的身份驗證
通過 Spring Boot Security,開發者可以輕鬆地構建安全、可靠的 Spring Boot 應用程序。
2.1. Maven 依賴
無論我們採用哪種方法,首先都需要添加 Spring Boot Starter for Security:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>2.2. Spring Boot 自動配置
憑藉 Spring Security 在類路徑上,Spring Boot Security 自動配置的 <em >WebSecurityEnablerConfiguration</em> 激活了 <em >@EnableWebSecurity</em>。
這會將 Spring 的默認安全配置應用於我們的應用程序。
默認安全激活了 HTTP 安全過濾器和安全過濾器鏈,並對我們的端點應用基本身份驗證。
3. 保護我們的端點
為了我們的第一種方法,我們首先創建一個 MySecurityConfigurer 類,並確保用 @EnableWebSecurity 註解它。
@EnableWebSecurity
public class MySecurityConfigurer {
}3.1. 快速瞭解 SecurityFilterChain Bean
首先,讓我們快速瞭解一下如何註冊 SecurityFilterChain Bean:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeRequests((requests) -> requests.anyRequest().authenticated());
http.formLogin(Customizer.withDefaults());
http.httpBasic(Customizer.withDefaults());
return http.build();
}在這裏,我們可以看到,我們接收到的任何請求都會進行身份驗證,並且我們有一個基本的表單登錄,用於提示憑據。
當我們想要使用 HttpSecurity DSL 時,我們會將其編寫為:
http.authorizeRequests(request -> request.anyRequest().authenticated())
.formLogin(Customizer.withDefaults())
.httpBasic(Customizer.withDefaults())3.2. 要求用户具有適當的角色
現在,讓我們配置安全策略,僅允許具有 ADMIN 角色的用户訪問我們的 /admin 端點。 此外,我們還將僅允許具有 USER 角色的用户訪問我們的 /protected 端點。
我們通過創建 SecurityFilterChain 豆來實現這一點:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http.authorizeHttpRequests(request -> request.requestMatchers(new AntPathRequestMatcher("/admin/**"))
.hasRole("ADMIN"))
.authorizeHttpRequests(request -> request.requestMatchers(new AntPathRequestMatcher("/protected/**"))
.hasRole("USER"))
.build();
}3.3. 允許公共資源訪問權限
我們不需要對我們的公共 /hello 資源進行身份驗證,因此我們將配置 WebSecurity 不對它們進行任何限制。
正如之前一樣,讓我們註冊一個 WebSecurityCustomizer Bean:
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring()
.requestMatchers(new AntPathRequestMatcher("/public/*"));
}4. 使用註解保護我們的端點
為了採用基於註解的安全方法,我們可以使用 <em @EnableGlobalMethodSecurity.</em >。
4.1. 要求用户擁有適當的角色,使用安全註解
現在,我們使用方法註解來配置安全性,僅允許 ADMIN 用户訪問我們的 /admin 端點,以及 USER 用户訪問我們的 /protected 端點。
讓我們啓用 JSR-250 註解,通過在我們的 EnableGlobalMethodSecurity 註解中設置 jsr250Enabled=true 來實現:
@EnableGlobalMethodSecurity(jsr250Enabled = true)
@Controller
public class AnnotationSecuredController {
@RolesAllowed("ADMIN")
@RequestMapping("/admin")
public String adminHello() {
return "Hello Admin";
}
@RolesAllowed("USER")
@RequestMapping("/protected")
public String jsr250Hello() {
return "Hello Jsr250";
}
}4.2. 強制所有公共方法具有安全措施
當我們使用註解作為安全實施的方式時,可能會忘記對方法進行註解。這會無意中造成安全漏洞。
為了防止這種情況發生,我們應該拒絕訪問所有沒有授權註解的公共方法。
4.3. 允許訪問公共資源
Spring 的默認安全策略強制對所有端點進行身份驗證,無論我們是否添加基於角色的安全策略。
儘管我們之前的示例將安全策略應用於我們的 /admin 和 /protected 端點,但我們仍然希望允許訪問基於文件資源的 /hello 端點。
雖然我們可以再次擴展 <em >WebSecurityAdapter</em>,Spring 提供了更簡單的替代方案。
在通過註解保護了我們的方法之後,我們可以添加 <em >WebSecurityCustomizer</em> 以開放 /hello/* 資源:
public class MyPublicPermitter implements WebSecurityCustomizer {
public void customize(WebSecurity webSecurity) {
webSecurity.ignoring()
.antMatchers("/hello/*");
}
}當然,以下是翻譯後的內容:
或者,我們也可以在配置類內部創建一個實現它的 Bean:
@Configuration
public class MyWebConfig {
@Bean
public WebSecurityCustomizer ignoreResources() {
return (webSecurity) -> webSecurity
.ignoring()
.antMatchers("/hello/*");
}
}當 Spring Security 初始化時,它會調用它找到的任何 WebSecurityCustomizer,包括我們自己的。
5. 測試我們的安全
現在我們已經配置了安全機制,應該檢查它是否按照預期工作。
根據我們選擇的安全方法,我們有以下一個或兩個自動測試選項。我們可以通過向我們的應用程序發送網絡請求,或者直接調用我們的控制器方法。
5.1. 通過 Web 請求進行測試
對於第一個選項,我們將創建一個帶有 <em @SpringBootTest> 的測試類以及 <em @TestRestTemplate>:
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT)
public class WebSecuritySpringBootIntegrationTest {
@Autowired
private TestRestTemplate template;
}現在,讓我們添加一個測試,以確保我們的公共資源可用:
@Test
public void givenPublicResource_whenGetViaWeb_thenOk() {
ResponseEntity<String> result = template.getForEntity("/hello/baeldung.txt", String.class);
assertEquals("Hello From Baeldung", result.getBody());
}我們還可以觀察到嘗試訪問受保護資源時發生的情況:
@Test
public void whenGetProtectedViaWeb_thenForbidden() {
ResponseEntity<String> result = template.getForEntity("/protected", String.class);
assertEquals(HttpStatus.FORBIDDEN, result.getStatusCode());
}這裏我們收到一個 禁止訪問 的響應,因為我們的匿名請求沒有所需的角色權限。
因此,我們可以使用此方法測試我們的安全應用程序,無論我們選擇哪種安全方案。
5.2. 通過自動裝配和註解進行測試
現在讓我們看看我們的第二個選項。我們設置一個 <em @SpringBootTest</em> 並自動裝配 <em AnnotationSecuredController</em>。
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT)
public class GlobalMethodSpringBootIntegrationTest {
@Autowired
private AnnotationSecuredController api;
}讓我們首先通過使用 @WithAnonymousUser 裝飾器測試我們的公開可訪問的方法:
@Test
@WithAnonymousUser
public void givenAnonymousUser_whenPublic_thenOk() {
assertThat(api.publicHello()).isEqualTo(HELLO_PUBLIC);
}
現在我們已經訪問了公共資源,接下來我們將使用 @WithMockUser 標註來訪問我們的受保護方法。
首先,讓我們使用具有“USER”角色的用户來測試我們的 JSR-250 受保護方法:
@WithMockUser(username="baeldung", roles = "USER")
@Test
public void givenUserWithRole_whenJsr250_thenOk() {
assertThat(api.jsr250Hello()).isEqualTo("Hello Jsr250");
}現在,讓我們嘗試在用户沒有正確權限的情況下訪問相同的該方法:
@WithMockUser(username="baeldung", roles = "NOT-USER")
@Test(expected = AccessDeniedException.class)
public void givenWrongRole_whenJsr250_thenAccessDenied() {
api.jsr250Hello();
}我們的請求被 Spring Security 攔截,並拋出了 AccessDeniedException。
我們只能在選擇基於註解的安全方式時使用這種方法。
6. 註釋注意事項
當我們選擇基於註釋的方法時,需要注意以下重要事項。
我們的註釋安全僅在通過公共方法進入類時才會被應用。
6.1. 間接調用方法
之前,當我們調用帶有安全註解的方法時,我們成功地應用了安全策略。現在,讓我們在同一類中創建一個公共方法,但不要添加任何安全註解。我們將讓它調用我們已註解的 jsr250Hello 方法:
@GetMapping("/indirect")
public String indirectHello() {
return jsr250Hello();
}
現在我們使用匿名訪問方式調用我們的“/indirect”端點:
@Test
@WithAnonymousUser
public void givenAnonymousUser_whenIndirectCall_thenNoSecurity() {
assertThat(api.indirectHello()).isEqualTo(HELLO_JSR_250);
}我們的測試通過,因為我們的“安全”方法在沒有觸發任何安全檢查的情況下被調用了。換句話説,同一類中內部調用不進行任何安全處理。
6.2. 通過間接方式調用不同類的註解方法
現在,讓我們看看當未保護的方法調用另一個類中註解的方法時會發生什麼。
首先,創建一個名為 DifferentClass 的類,該類包含一個註解方法 differentJsr250Hello:
@Component
public class DifferentClass {
@RolesAllowed("USER")
public String differentJsr250Hello() {
return "Hello Jsr250";
}
}現在,讓我們將 DifferentClass 自動注入到我們的控制器中,並添加一個未保護的 differentClassHello 公共方法來調用它。
@Autowired
DifferentClass differentClass;
@GetMapping("/differentclass")
public String differentClassHello() {
return differentClass.differentJsr250Hello();
}最後,讓我們測試調用並確認我們的安全措施已生效:
@Test(expected = AccessDeniedException.class)
@WithAnonymousUser
public void givenAnonymousUser_whenIndirectToDifferentClass_thenAccessDenied() {
api.differentClassHello();
}因此,我們看到,當我們調用同一類中已註釋的方法時,調用另一個方法時,我們的安全註解不會被尊重,但當我們在另一個類中調用已註釋的方法時,它們會被尊重。
6.3. 最終的注意事項
請務必確保正確配置 @EnableGlobalMethodSecurity。 如果未正確配置,即使我們使用了所有安全註解,它們也可能沒有任何作用。
例如,如果我們使用了 JSR-250 註解,但沒有指定 jsr250Enabled=true,而是指定了 prePostEnabled=true,那麼我們的 JSR-250 註解將毫無作用!
@EnableGlobalMethodSecurity(prePostEnabled = true)當然,我們也可以通過將它們都添加到我們的 @EnableGlobalMethodSecurity 註解中,聲明同時使用多種標註類型:
@EnableGlobalMethodSecurity(jsr250Enabled = true, prePostEnabled = true)7. 當我們需要更多
與 JSR-250 相比,我們還可以使用 Spring Method Security。 這包括使用更強大的 Spring Security 表達式語言 (SpEL) 用於更高級的授權場景。 通過在 EnableGlobalMethodSecurity 註解中設置 prePostEnabled=true,我們可以啓用 SpEL。
@EnableGlobalMethodSecurity(prePostEnabled = true)此外,當我們想要基於是否由用户擁有來強制執行安全策略時,可以使用 Spring Security 訪問控制列表。
我們也應該注意到,在編寫反應式應用程序時,我們使用 @EnableWebFluxSecurity 和 @EnableReactiveMethodSecurity。
8. 結論
在本教程中,我們首先探討了如何使用集中式安全規則方法來安全我們的應用程序,藉助 @EnableWebSecurity。
然後,我們在此基礎上,通過配置安全策略,將這些規則更靠近影響它們的代碼。我們通過使用 @EnableGlobalMethodSecurity 並對我們想要保護的方法進行註釋來實現這一點。
最後,我們介紹了另一種放鬆對公共資源的安全性的方法,這些資源不需要安全保護。