Java 配置
Hello Web Security Java 配置
第一步是建立我們的 Spring Security Java 配置。該配置建立了一個名為 springSecurityFilterChain 的 Servlet 過濾器,它負責應用程式中的所有安全(保護應用程式 URL、驗證提交的使用者名稱和密碼、重定向到登入表單等)。以下示例顯示了 Spring Security Java 配置最基本的示例。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.*;
import org.springframework.security.config.annotation.authentication.builders.*;
import org.springframework.security.config.annotation.web.configuration.*;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
@Bean
public UserDetailsService userDetailsService() {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withDefaultPasswordEncoder().username("user").password("password").roles("USER").build());
return manager;
}
}
此配置不復雜也不廣泛,但它做了很多工作
-
要求對應用程式中的每個 URL 進行身份驗證
-
為您生成登入表單
-
允許使用者使用使用者名稱
user和密碼password進行基於表單的身份驗證 -
允許使用者登出
-
CSRF 攻擊防護
-
會話固定保護
-
安全頭部整合
-
用於安全請求的 HTTP 嚴格傳輸安全
-
快取控制(您以後可以在應用程式中覆蓋以允許快取您的靜態資源)
-
X-Frame-Options 整合,有助於防止 點選劫持
-
-
與以下 Servlet API 方法整合
AbstractSecurityWebApplicationInitializer
下一步是將 springSecurityFilterChain 註冊到 WAR 檔案中。您可以在 Servlet 3.0+ 環境中透過 Spring 的 WebApplicationInitializer 支援在 Java 配置中完成此操作。不出所料,Spring Security 提供了一個基類 (AbstractSecurityWebApplicationInitializer) 來確保 springSecurityFilterChain 為您註冊。我們使用 AbstractSecurityWebApplicationInitializer 的方式取決於我們是否已經在使用 Spring,或者 Spring Security 是否是我們應用程式中唯一的 Spring 元件。
-
沒有現有 Spring 的 AbstractSecurityWebApplicationInitializer - 如果您尚未使用 Spring,請使用這些說明
-
帶有 Spring MVC 的 AbstractSecurityWebApplicationInitializer - 如果您已在使用 Spring,請使用這些說明
沒有現有 Spring 的 AbstractSecurityWebApplicationInitializer
如果您沒有使用 Spring 或 Spring MVC,您需要將 WebSecurityConfig 傳遞給超類以確保配置被拾取
import org.springframework.security.web.context.*;
public class SecurityWebApplicationInitializer
extends AbstractSecurityWebApplicationInitializer {
public SecurityWebApplicationInitializer() {
super(WebSecurityConfig.class);
}
}
SecurityWebApplicationInitializer
-
自動為應用程式中的每個 URL 註冊
springSecurityFilterChain過濾器。 -
新增一個載入 WebSecurityConfig 的
ContextLoaderListener。
帶有 Spring MVC 的 AbstractSecurityWebApplicationInitializer
如果我們在應用程式的其他地方使用 Spring,我們可能已經有一個正在載入我們的 Spring 配置的 WebApplicationInitializer。如果我們使用前面的配置,我們將得到一個錯誤。相反,我們應該將 Spring Security 註冊到現有的 ApplicationContext 中。例如,如果我們使用 Spring MVC,我們的 SecurityWebApplicationInitializer 可能如下所示
import org.springframework.security.web.context.*;
public class SecurityWebApplicationInitializer
extends AbstractSecurityWebApplicationInitializer {
}
這隻為應用程式中的每個 URL 註冊 springSecurityFilterChain。之後,我們需要確保在現有的 ApplicationInitializer 中載入了 WebSecurityConfig。例如,如果使用 Spring MVC,它會被新增到 getServletConfigClasses() 中。
public class MvcWebApplicationInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { WebSecurityConfig.class, WebMvcConfig.class };
}
// ... other overrides ...
}
這樣做的原因是 Spring Security 需要能夠檢查一些 Spring MVC 配置,以便適當地配置底層請求匹配器,因此它們需要位於相同的應用程式上下文中。將 Spring Security 放在 getRootConfigClasses 中會將其放置在父應用程式上下文中,該上下文可能無法找到 Spring MVC 的 PathPatternParser。
為多個 Spring MVC Dispatcher 配置
如果需要,任何與 Spring MVC 無關的 Spring Security 配置都可以放在不同的配置類中,如下所示
public class MvcWebApplicationInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { NonWebSecurityConfig.class };
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { WebSecurityConfig.class, WebMvcConfig.class };
}
// ... other overrides ...
}
如果您有多個 AbstractAnnotationConfigDispatcherServletInitializer 例項,並且不想在它們之間重複通用的安全配置,這會很有幫助。
HttpSecurity
到目前為止,我們的 WebSecurityConfig 只包含有關如何驗證使用者的資訊。Spring Security 如何知道我們需要所有使用者都經過身份驗證?Spring Security 如何知道我們想要支援基於表單的身份驗證?實際上,有一個配置類(稱為 SecurityFilterChain)在幕後被呼叫。它透過以下預設實現進行配置
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults())
.httpBasic(Customizer.withDefaults());
return http.build();
}
預設配置(如前例所示)
-
確保對應用程式的任何請求都需要使用者經過身份驗證
-
允許使用者使用基於表單的登入進行身份驗證
-
允許使用者使用 HTTP 基本身份驗證進行身份驗證
請注意,此配置與 XML 名稱空間配置並行
<http>
<intercept-url pattern="/**" access="authenticated"/>
<form-login />
<http-basic />
</http>
多個 HttpSecurity 例項
為了有效地管理應用程式中的安全性,其中某些區域需要不同的保護,我們可以結合 securityMatcher DSL 方法使用多個過濾器鏈。這種方法允許我們定義針對應用程式特定部分量身定製的不同安全配置,從而增強整體應用程式安全性和控制。
我們可以像在 XML 中有多個 <http> 塊一樣配置多個 HttpSecurity 例項。關鍵是註冊多個 SecurityFilterChain @Bean。以下示例為以 /api/ 開頭的 URL 提供了不同的配置
@Configuration
@EnableWebSecurity
public class MultiHttpSecurityConfig {
@Bean (1)
public UserDetailsService userDetailsService() throws Exception {
UserBuilder users = User.withDefaultPasswordEncoder();
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(users.username("user").password("password").roles("USER").build());
manager.createUser(users.username("admin").password("password").roles("USER","ADMIN").build());
return manager;
}
@Bean
@Order(1) (2)
public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/api/**") (3)
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().hasRole("ADMIN")
)
.httpBasic(Customizer.withDefaults());
return http.build();
}
@Bean (4)
public SecurityFilterChain formLoginFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults());
return http.build();
}
}
| 1 | 照常配置身份驗證。 |
| 2 | 建立一個包含 @Order 的 SecurityFilterChain 例項,以指定應首先考慮哪個 SecurityFilterChain。 |
| 3 | http.securityMatcher() 表示此 HttpSecurity 僅適用於以 /api/ 開頭的 URL。 |
| 4 | 建立另一個 SecurityFilterChain 例項。如果 URL 不以 /api/ 開頭,則使用此配置。此配置在 apiFilterChain 之後考慮,因為它具有 1 之後的 @Order 值(沒有 @Order 預設為最後)。 |
選擇 securityMatcher 或 requestMatchers
一個常見問題是
http.securityMatcher()方法與用於請求授權的requestMatchers()(即在http.authorizeHttpRequests()內部)有什麼區別?
為了回答這個問題,瞭解每個用於構建 SecurityFilterChain 的 HttpSecurity 例項都包含一個 RequestMatcher 來匹配傳入請求會很有幫助。如果請求不匹配具有更高優先順序(例如 @Order(1))的 SecurityFilterChain,則可以針對具有較低優先順序(例如沒有 @Order)的過濾器鏈嘗試該請求。
|
多個過濾器鏈的匹配邏輯由 |
預設的 RequestMatcher 匹配 任何請求,以確保 Spring Security 預設保護所有請求。
|
指定 |
|
如果沒有過濾器鏈匹配特定請求,則該請求 不受 Spring Security 的保護。 |
以下示例演示了一個僅保護以 /secured/ 開頭的請求的單個過濾器鏈
@Configuration
@EnableWebSecurity
public class PartialSecurityConfig {
@Bean
public UserDetailsService userDetailsService() throws Exception {
// ...
}
@Bean
public SecurityFilterChain securedFilterChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/secured/**") (1)
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/secured/user").hasRole("USER") (2)
.requestMatchers("/secured/admin").hasRole("ADMIN") (3)
.anyRequest().authenticated() (4)
)
.httpBasic(Customizer.withDefaults())
.formLogin(Customizer.withDefaults());
return http.build();
}
}
| 1 | 以 /secured/ 開頭的請求將受到保護,但任何其他請求不受保護。 |
| 2 | 對 /secured/user 的請求需要 ROLE_USER 許可權。 |
| 3 | 對 /secured/admin 的請求需要 ROLE_ADMIN 許可權。 |
| 4 | 任何其他請求(例如 /secured/other)只需經過身份驗證的使用者。 |
|
建議提供一個不指定任何 |
請注意,requestMatchers 方法僅適用於單個授權規則。此處列出的每個請求也必須與用於建立 SecurityFilterChain 的此特定 HttpSecurity 例項的整體 securityMatcher 匹配。此示例中使用 anyRequest() 匹配此特定 SecurityFilterChain 內的所有其他請求(該請求必須以 /secured/ 開頭)。
|
有關 |
SecurityFilterChain 端點
SecurityFilterChain 中的幾個過濾器直接提供端點,例如 UsernamePasswordAuthenticationFilter,它由 http.formLogin() 設定並提供 POST /login 端點。在上面的示例中,/login 端點與 http.securityMatcher("/secured/**") 不匹配,因此該應用程式將沒有任何 GET /login 或 POST /login 端點。此類請求將返回 404 Not Found。這通常讓使用者感到驚訝。
指定 http.securityMatcher() 會影響該 SecurityFilterChain 匹配哪些請求。但是,它不會自動影響過濾器鏈提供的端點。在這種情況下,您可能需要自定義過濾器鏈要提供的任何端點的 URL。
以下示例演示了一個配置,該配置保護以 /secured/ 開頭的請求並拒絕所有其他請求,同時還自定義了 SecurityFilterChain 提供的端點
@Configuration
@EnableWebSecurity
public class SecuredSecurityConfig {
@Bean
public UserDetailsService userDetailsService() throws Exception {
// ...
}
@Bean
@Order(1)
public SecurityFilterChain securedFilterChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/secured/**") (1)
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated() (2)
)
.formLogin((formLogin) -> formLogin (3)
.loginPage("/secured/login")
.loginProcessingUrl("/secured/login")
.permitAll()
)
.logout((logout) -> logout (4)
.logoutUrl("/secured/logout")
.logoutSuccessUrl("/secured/login?logout")
.permitAll()
);
return http.build();
}
@Bean
public SecurityFilterChain defaultFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().denyAll() (5)
);
return http.build();
}
}
| 1 | 以 /secured/ 開頭的請求將受到此過濾器鏈的保護。 |
| 2 | 以 /secured/ 開頭的請求需要經過身份驗證的使用者。 |
| 3 | 自定義表單登入以 /secured/ 作為 URL 字首。 |
| 4 | 自定義登出以 /secured/ 作為 URL 字首。 |
| 5 | 所有其他請求將被拒絕。 |
|
此示例自定義了登入和登出頁面,這會停用 Spring Security 生成的頁面。您必須為 |
實際示例
以下示例演示了一個稍微更真實的配置,將所有這些元素組合在一起
@Configuration
@EnableWebSecurity
public class BankingSecurityConfig {
@Bean (1)
public UserDetailsService userDetailsService() {
UserBuilder users = User.withDefaultPasswordEncoder();
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(users.username("user1").password("password").roles("USER", "VIEW_BALANCE").build());
manager.createUser(users.username("user2").password("password").roles("USER").build());
manager.createUser(users.username("admin").password("password").roles("ADMIN").build());
return manager;
}
@Bean
@Order(1) (2)
public SecurityFilterChain approvalsSecurityFilterChain(HttpSecurity http) throws Exception {
String[] approvalsPaths = { "/accounts/approvals/**", "/loans/approvals/**", "/credit-cards/approvals/**" };
http
.securityMatcher(approvalsPaths)
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().hasRole("ADMIN")
)
.httpBasic(Customizer.withDefaults());
return http.build();
}
@Bean
@Order(2) (3)
public SecurityFilterChain bankingSecurityFilterChain(HttpSecurity http) throws Exception {
String[] bankingPaths = { "/accounts/**", "/loans/**", "/credit-cards/**", "/balances/**" };
String[] viewBalancePaths = { "/balances/**" };
http
.securityMatcher(bankingPaths)
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers(viewBalancePaths).hasRole("VIEW_BALANCE")
.anyRequest().hasRole("USER")
);
return http.build();
}
@Bean (4)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
String[] allowedPaths = { "/", "/user-login", "/user-logout", "/notices", "/contact", "/register" };
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers(allowedPaths).permitAll()
.anyRequest().authenticated()
)
.formLogin((formLogin) -> formLogin
.loginPage("/user-login")
.loginProcessingUrl("/user-login")
)
.logout((logout) -> logout
.logoutUrl("/user-logout")
.logoutSuccessUrl("/?logout")
);
return http.build();
}
}
| 1 | 首先配置身份驗證設定。 |
| 2 | 定義一個帶有 @Order(1) 的 SecurityFilterChain 例項,這意味著此過濾器鏈將具有最高優先順序。此過濾器鏈僅適用於以 /accounts/approvals/、/loans/approvals/ 或 /credit-cards/approvals/ 開頭的請求。對此過濾器鏈的請求需要 ROLE_ADMIN 許可權並允許 HTTP 基本身份驗證。 |
| 3 | 接下來,建立另一個帶有 @Order(2) 的 SecurityFilterChain 例項,該例項將排在第二位。此過濾器鏈僅適用於以 /accounts/、/loans/、/credit-cards/ 或 /balances/ 開頭的請求。請注意,由於此過濾器鏈是第二個,任何包含 /approvals/ 的請求都將匹配前一個過濾器鏈,並且 不會 被此過濾器鏈匹配。對此過濾器鏈的請求需要 ROLE_USER 許可權。此過濾器鏈未定義任何身份驗證,因為下一個(預設)過濾器鏈包含該配置。 |
| 4 | 最後,建立另一個沒有 @Order 註解的 SecurityFilterChain 例項。此配置將處理未被其他過濾器鏈覆蓋的請求,並將最後處理(沒有 @Order 預設為最後)。匹配 /、/user-login、/user-logout、/notices、/contact 和 /register 的請求允許未經身份驗證的訪問。任何其他請求都需要使用者進行身份驗證才能訪問任何未明確允許或受其他過濾器鏈保護的 URL。 |
自定義 DSL
您可以在 Spring Security 中提供自己的自定義 DSL
-
Java
-
Kotlin
public class MyCustomDsl extends AbstractHttpConfigurer<MyCustomDsl, HttpSecurity> {
private boolean flag;
@Override
public void init(HttpSecurity http) throws Exception {
// any method that adds another configurer
// must be done in the init method
http.csrf().disable();
}
@Override
public void configure(HttpSecurity http) throws Exception {
ApplicationContext context = http.getSharedObject(ApplicationContext.class);
// here we lookup from the ApplicationContext. You can also just create a new instance.
MyFilter myFilter = context.getBean(MyFilter.class);
myFilter.setFlag(flag);
http.addFilterBefore(myFilter, UsernamePasswordAuthenticationFilter.class);
}
public MyCustomDsl flag(boolean value) {
this.flag = value;
return this;
}
public static MyCustomDsl customDsl() {
return new MyCustomDsl();
}
}
class MyCustomDsl : AbstractHttpConfigurer<MyCustomDsl, HttpSecurity>() {
var flag: Boolean = false
override fun init(http: HttpSecurity) {
// any method that adds another configurer
// must be done in the init method
http.csrf().disable()
}
override fun configure(http: HttpSecurity) {
val context: ApplicationContext = http.getSharedObject(ApplicationContext::class.java)
// here we lookup from the ApplicationContext. You can also just create a new instance.
val myFilter: MyFilter = context.getBean(MyFilter::class.java)
myFilter.setFlag(flag)
http.addFilterBefore(myFilter, UsernamePasswordAuthenticationFilter::class.java)
}
companion object {
@JvmStatic
fun customDsl(): MyCustomDsl {
return MyCustomDsl()
}
}
}
|
這實際上是 |
然後您可以使用自定義 DSL
-
Java
-
Kotlin
@Configuration
@EnableWebSecurity
public class Config {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.with(MyCustomDsl.customDsl(), (dsl) -> dsl
.flag(true)
)
// ...
return http.build();
}
}
@Configuration
@EnableWebSecurity
class Config {
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http
.with(MyCustomDsl.customDsl()) {
flag = true
}
// ...
return http.build()
}
}
程式碼按以下順序呼叫
-
呼叫
Config.filterChain方法中的程式碼 -
呼叫
MyCustomDsl.init方法中的程式碼 -
呼叫
MyCustomDsl.configure方法中的程式碼
如果您願意,可以使用 SpringFactories 讓 HttpSecurity 預設新增 MyCustomDsl。例如,您可以在類路徑上建立一個名為 META-INF/spring.factories 的資源,其內容如下
org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer = sample.MyCustomDsl
您也可以顯式停用預設值
-
Java
-
Kotlin
@Configuration
@EnableWebSecurity
public class Config {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.with(MyCustomDsl.customDsl(), (dsl) -> dsl
.disable()
)
...;
return http.build();
}
}
@Configuration
@EnableWebSecurity
class Config {
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http
.with(MyCustomDsl.customDsl()) {
disable()
}
// ...
return http.build()
}
}
模組化 HttpSecurity 配置
許多使用者傾向於將 Spring Security 配置集中在一個地方,並選擇在單個 SecurityFilterChain 例項中進行配置。但是,有時使用者可能希望將配置模組化。這可以透過以下方式完成
如果您正在使用 Spring Security 的 Kotlin 配置,那麼您還可以像 模組化 HttpSecurityDsl 配置中概述的那樣公開 *Dsl → Unit Bean。 |
Customizer<HttpSecurity> Bean
如果您想模組化您的安全配置,可以將邏輯放入 Customizer<HttpSecurity> Bean 中。例如,以下配置將確保所有 HttpSecurity 例項都配置為
-
Java
-
Kotlin
@Bean
ThrowingCustomizer<HttpSecurity> httpSecurityCustomizer() {
return (http) -> http
.headers((headers) -> headers
.contentSecurityPolicy((csp) -> csp
(1)
.policyDirectives("object-src 'none'")
)
)
(2)
.redirectToHttps(Customizer.withDefaults());
}
@Bean
fun httpSecurityCustomizer(): ThrowingCustomizer<HttpSecurity> {
return ThrowingCustomizer { http -> http
.headers { headers -> headers
.contentSecurityPolicy { csp -> csp
(1)
.policyDirectives("object-src 'none'")
}
}
(2)
.redirectToHttps(Customizer.withDefaults())
}
}
| 1 | 將 內容安全策略 設定為 object-src 'none' |
| 2 | 將任何請求重定向到 https |
頂級 HttpSecurity Customizer Bean
如果您希望進一步模組化您的安全配置,Spring Security 將自動應用任何頂級 HttpSecurity Customizer Bean。
頂級 HttpSecurity Customizer 型別可以概括為任何與 public HttpSecurity.*(Customizer<T>) 匹配的 Customizer<T>。這意味著任何作為 HttpSecurity 公共方法的單個引數的 Customizer<T>。
幾個例子可以幫助澄清。如果 Customizer<ContentTypeOptionsConfig> 作為 Bean 釋出,它將不會自動應用,因為它是一個引數 HeadersConfigurer.contentTypeOptions(Customizer),而這不是 HttpSecurity 上定義的方法。但是,如果 Customizer<HeadersConfigurer<HttpSecurity>> 作為 Bean 釋出,它將自動應用,因為它是一個引數 HttpSecurity.headers(Customizer)。
例如,以下配置將確保 內容安全策略 設定為 object-src 'none'
-
Java
-
Kotlin
@Bean
Customizer<HeadersConfigurer<HttpSecurity>> headersSecurity() {
return (headers) -> headers
.contentSecurityPolicy((csp) -> csp
(1)
.policyDirectives("object-src 'none'")
);
}
@Bean
fun headersSecurity(): Customizer<HeadersConfigurer<HttpSecurity>> {
return Customizer { headers -> headers
.contentSecurityPolicy { csp -> csp
(1)
.policyDirectives("object-src 'none'")
}
}
}
Customizer Bean 排序
首先,使用 ObjectProvider#orderedStream() 應用每個 Customizer<HttpSecurity> Bean。這意味著如果存在多個 Customizer<HttpSecurity> Bean,則可以將 @Order 註解新增到 Bean 定義以控制排序。
接下來,查詢每個 頂級 HttpSecurity Customizer Bean 型別,並使用 ObjectProvider#orderedStream() 應用每個型別。如果存在兩個 Customizer<HeadersConfigurer<HttpSecurity>> Bean 和兩個 Customizer<HttpsRedirectConfigurer<HttpSecurity>> 例項,則呼叫每個 Customizer 型別的順序是未定義的。但是,每個 Customizer<HttpsRedirectConfigurer<HttpSecurity>> 例項的順序由 ObjectProvider#orderedStream() 定義,並且可以使用 Bean 定義上的 @Order 進行控制。
最後,HttpSecurity Bean 作為 Bean 注入。所有 Customizer 例項在 HttpSecurity Bean 建立之前應用。這允許覆蓋由 Customizer Bean 提供的自定義。
您可以在下面找到一個說明排序的示例
-
Java
-
Kotlin
@Bean (4)
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((requests) -> requests
.anyRequest().authenticated()
);
return http.build();
}
@Bean
@Order(Ordered.LOWEST_PRECEDENCE) (2)
ThrowingCustomizer<HttpSecurity> userAuthorization() {
return (http) -> http
.authorizeHttpRequests((requests) -> requests
.requestMatchers("/users/**").hasRole("USER")
);
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE) (1)
ThrowingCustomizer<HttpSecurity> adminAuthorization() {
return (http) -> http
.authorizeHttpRequests((requests) -> requests
.requestMatchers("/admins/**").hasRole("ADMIN")
);
}
(3)
@Bean
Customizer<HeadersConfigurer<HttpSecurity>> contentSecurityPolicy() {
return (headers) -> headers
.contentSecurityPolicy((csp) -> csp
.policyDirectives("object-src 'none'")
);
}
@Bean
Customizer<HeadersConfigurer<HttpSecurity>> contentTypeOptions() {
return (headers) -> headers
.contentTypeOptions(Customizer.withDefaults());
}
@Bean
Customizer<HttpsRedirectConfigurer<HttpSecurity>> httpsRedirect() {
return Customizer.withDefaults();
}
@Bean (4)
fun springSecurity(http: HttpSecurity): SecurityFilterChain {
http
.authorizeHttpRequests({ requests -> requests
.anyRequest().authenticated()
})
return http.build()
}
@Bean
@Order(Ordered.LOWEST_PRECEDENCE) (2)
fun userAuthorization(): ThrowingCustomizer<HttpSecurity> {
return ThrowingCustomizer { http -> http
.authorizeHttpRequests { requests -> requests
.requestMatchers("/users/**").hasRole("USER")
}
}
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE) (1)
fun adminAuthorization(): ThrowingCustomizer<HttpSecurity> {
return ThrowingCustomizer { http -> http
.authorizeHttpRequests { requests -> requests
.requestMatchers("/admins/**").hasRole("ADMIN")
}
}
}
(3)
@Bean
fun contentSecurityPolicy(): Customizer<HeadersConfigurer<HttpSecurity>> {
return Customizer { headers -> headers
.contentSecurityPolicy { csp -> csp
.policyDirectives("object-src 'none'")
}
}
}
@Bean
fun contentTypeOptions(): Customizer<HeadersConfigurer<HttpSecurity>> {
return Customizer { headers -> headers
.contentTypeOptions(Customizer.withDefaults())
}
}
@Bean
fun httpsRedirect(): Customizer<HttpsRedirectConfigurer<HttpSecurity>> {
return Customizer.withDefaults<HttpsRedirectConfigurer<HttpSecurity>>()
}
| 1 | 首先應用所有 Customizer<HttpSecurity> 例項。adminAuthorization Bean 具有最高的 @Order,因此它最先應用。如果在 Customizer<HttpSecurity> Bean 上沒有 @Order 註解,或者 @Order 註解具有相同的值,則 Customizer<HttpSecurity> 例項的應用順序是未定義的。 |
| 2 | 由於 userAuthorization 是 Customizer<HttpSecurity> 的例項,因此接下來應用它。 |
| 3 | Customizer 型別的順序是未定義的。在此示例中,contentSecurityPolicy、contentTypeOptions 和 httpsRedirect 的順序是未定義的。如果將 @Order(Ordered.HIGHEST_PRECEDENCE) 新增到 contentTypeOptions,那麼我們就會知道 contentTypeOptions 在 contentSecurityPolicy 之前(它們是相同型別),但我們不知道 httpsRedirect 在 Customizer<HeadersConfigurer<HttpSecurity>> Bean 之前還是之後。 |
| 4 | 在所有 Customizer Bean 應用之後,HttpSecurity 作為 Bean 傳入。 |
後處理配置物件
Spring Security 的 Java 配置不公開它配置的每個物件的每個屬性。這簡化了大多數使用者的配置。畢竟,如果每個屬性都暴露,使用者可以使用標準的 bean 配置。
雖然有充分的理由不直接公開每個屬性,但使用者可能仍然需要更高階的配置選項。為了解決這個問題,Spring Security 引入了 ObjectPostProcessor 的概念,它可以用於修改或替換由 Java 配置建立的許多 Object 例項。例如,要配置 FilterSecurityInterceptor 上的 filterSecurityPublishAuthorizationSuccess 屬性,您可以使用以下內容
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
public <O extends FilterSecurityInterceptor> O postProcess(
O fsi) {
fsi.setPublishAuthorizationSuccess(true);
return fsi;
}
})
);
return http.build();
}