浏览代码

Polish and sync java and kotlin configuration docs

Issue gh-15029
Steve Riesenberg 1 年之前
父节点
当前提交
9b1e9c5db9

+ 5 - 7
docs/modules/ROOT/pages/servlet/configuration/java.adoc

@@ -185,10 +185,10 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
 The default configuration (shown in the preceding example):
 The default configuration (shown in the preceding example):
 
 
 * Ensures that any request to our application requires the user to be authenticated
 * Ensures that any request to our application requires the user to be authenticated
-* Lets users authenticate with form based login
+* Lets users authenticate with form-based login
 * Lets users authenticate with HTTP Basic authentication
 * Lets users authenticate with HTTP Basic authentication
 
 
-Note that this configuration is parallels the XML Namespace configuration:
+Note that this configuration parallels the XML namespace configuration:
 
 
 [source,xml]
 [source,xml]
 ----
 ----
@@ -206,7 +206,7 @@ This approach allows us to define distinct security configurations tailored to s
 
 
 We can configure multiple `HttpSecurity` instances just as we can have multiple `<http>` blocks in XML.
 We can configure multiple `HttpSecurity` instances just as we can have multiple `<http>` blocks in XML.
 The key is to register multiple `SecurityFilterChain` ``@Bean``s.
 The key is to register multiple `SecurityFilterChain` ``@Bean``s.
-The following example has a different configuration for URLs that begin with `/api/`.
+The following example has a different configuration for URLs that begin with `/api/`:
 
 
 [[multiple-httpsecurity-instances-java]]
 [[multiple-httpsecurity-instances-java]]
 [source,java]
 [source,java]
@@ -216,7 +216,6 @@ The following example has a different configuration for URLs that begin with `/a
 public class MultiHttpSecurityConfig {
 public class MultiHttpSecurityConfig {
 	@Bean                                                             <1>
 	@Bean                                                             <1>
 	public UserDetailsService userDetailsService() throws Exception {
 	public UserDetailsService userDetailsService() throws Exception {
-		// ensure the passwords are encoded properly
 		UserBuilder users = User.withDefaultPasswordEncoder();
 		UserBuilder users = User.withDefaultPasswordEncoder();
 		InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
 		InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
 		manager.createUser(users.username("user").password("password").roles("USER").build());
 		manager.createUser(users.username("user").password("password").roles("USER").build());
@@ -411,7 +410,6 @@ public class BankingSecurityConfig {
 
 
     @Bean                                                              <1>
     @Bean                                                              <1>
     public UserDetailsService userDetailsService() {
     public UserDetailsService userDetailsService() {
-        // ensure the passwords are encoded properly
 		UserBuilder users = User.withDefaultPasswordEncoder();
 		UserBuilder users = User.withDefaultPasswordEncoder();
         InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
         InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
         manager.createUser(users.username("user1").password("password").roles("USER", "VIEW_BALANCE").build());
         manager.createUser(users.username("user1").password("password").roles("USER", "VIEW_BALANCE").build());
@@ -449,7 +447,7 @@ public class BankingSecurityConfig {
 
 
     @Bean                                                              <4>
     @Bean                                                              <4>
     public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
     public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
-		String[] allowedPaths = { "/user-login", "/user-logout", "/notices", "/contact", "/register" };
+		String[] allowedPaths = { "/", "/user-login", "/user-logout", "/notices", "/contact", "/register" };
         http
         http
             .authorizeHttpRequests(authorize -> authorize
             .authorizeHttpRequests(authorize -> authorize
 				.requestMatchers(allowedPaths).permitAll()
 				.requestMatchers(allowedPaths).permitAll()
@@ -478,7 +476,7 @@ public class BankingSecurityConfig {
 	This filter chain does not define any authentication because the next (default) filter chain contains that configuration.
 	This filter chain does not define any authentication because the next (default) filter chain contains that configuration.
 <4> Lastly, create an additional `SecurityFilterChain` instance without an `@Order` annotation.
 <4> Lastly, create an additional `SecurityFilterChain` instance without an `@Order` annotation.
 	This configuration will handle requests not covered by the other filter chains and will be processed last (no `@Order` defaults to last).
 	This configuration will handle requests not covered by the other filter chains and will be processed last (no `@Order` defaults to last).
-	Requests that match `/user-login`, `/user-logout`, `/notices`, `/contact` and `/register` allow access without authentication.
+	Requests that match `/`, `/user-login`, `/user-logout`, `/notices`, `/contact` and `/register` allow access without authentication.
 	Any other requests require the user to be authenticated to access any URL not explicitly allowed or protected by other filter chains.
 	Any other requests require the user to be authenticated to access any URL not explicitly allowed or protected by other filter chains.
 
 
 [[jc-custom-dsls]]
 [[jc-custom-dsls]]

+ 247 - 10
docs/modules/ROOT/pages/servlet/configuration/kotlin.adoc

@@ -38,7 +38,7 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain {
 [NOTE]
 [NOTE]
 Make sure to import the `org.springframework.security.config.annotation.web.invoke` function to enable the Kotlin DSL in your class, as the IDE will not always auto-import the method, causing compilation issues.
 Make sure to import the `org.springframework.security.config.annotation.web.invoke` function to enable the Kotlin DSL in your class, as the IDE will not always auto-import the method, causing compilation issues.
 
 
-The default configuration (shown in the preceding listing):
+The default configuration (shown in the preceding example):
 
 
 * Ensures that any request to our application requires the user to be authenticated
 * Ensures that any request to our application requires the user to be authenticated
 * Lets users authenticate with form-based login
 * Lets users authenticate with form-based login
@@ -55,12 +55,16 @@ Note that this configuration parallels the XML namespace configuration:
 </http>
 </http>
 ----
 ----
 
 
-== Multiple HttpSecurity Instances
+=== Multiple HttpSecurity Instances
 
 
-We can configure multiple `HttpSecurity` instances, just as we can have multiple `<http>` blocks.
+To effectively manage security in an application where certain areas need different protection, we can employ multiple filter chains alongside the `securityMatcher` DSL method.
+This approach allows us to define distinct security configurations tailored to specific parts of the application, enhancing overall application security and control.
+
+We can configure multiple `HttpSecurity` instances just as we can have multiple `<http>` blocks in XML.
 The key is to register multiple `SecurityFilterChain` ``@Bean``s.
 The key is to register multiple `SecurityFilterChain` ``@Bean``s.
-The following example has a different configuration for URLs that start with `/api/`:
+The following example has a different configuration for URLs that begin with `/api/`:
 
 
+[[multiple-httpsecurity-instances-kotlin]]
 [source,kotlin]
 [source,kotlin]
 ----
 ----
 import org.springframework.security.config.annotation.web.invoke
 import org.springframework.security.config.annotation.web.invoke
@@ -69,16 +73,16 @@ import org.springframework.security.config.annotation.web.invoke
 @EnableWebSecurity
 @EnableWebSecurity
 class MultiHttpSecurityConfig {
 class MultiHttpSecurityConfig {
     @Bean                                                            <1>
     @Bean                                                            <1>
-    public fun userDetailsService(): UserDetailsService {
-        val users: User.UserBuilder = User.withDefaultPasswordEncoder()
+    open fun userDetailsService(): UserDetailsService {
+        val users = User.withDefaultPasswordEncoder()
         val manager = InMemoryUserDetailsManager()
         val manager = InMemoryUserDetailsManager()
         manager.createUser(users.username("user").password("password").roles("USER").build())
         manager.createUser(users.username("user").password("password").roles("USER").build())
         manager.createUser(users.username("admin").password("password").roles("USER","ADMIN").build())
         manager.createUser(users.username("admin").password("password").roles("USER","ADMIN").build())
         return manager
         return manager
     }
     }
 
 
-    @Order(1)                                                        <2>
     @Bean
     @Bean
+    @Order(1)                                                        <2>
     open fun apiFilterChain(http: HttpSecurity): SecurityFilterChain {
     open fun apiFilterChain(http: HttpSecurity): SecurityFilterChain {
         http {
         http {
             securityMatcher("/api/**")                               <3>
             securityMatcher("/api/**")                               <3>
@@ -102,10 +106,243 @@ class MultiHttpSecurityConfig {
     }
     }
 }
 }
 ----
 ----
-
 <1> Configure Authentication as usual.
 <1> Configure Authentication as usual.
 <2> Create an instance of `SecurityFilterChain` that contains `@Order` to specify which `SecurityFilterChain` should be considered first.
 <2> Create an instance of `SecurityFilterChain` that contains `@Order` to specify which `SecurityFilterChain` should be considered first.
-<3> The `http.securityMatcher` states that this `HttpSecurity` is applicable only to URLs that start with `/api/`
+<3> The `http.securityMatcher()` states that this `HttpSecurity` is applicable only to URLs that begin with `/api/`.
 <4> Create another instance of `SecurityFilterChain`.
 <4> Create another instance of `SecurityFilterChain`.
-If the URL does not start with `/api/`, this configuration is used.
+If the URL does not begin with `/api/`, this configuration is used.
 This configuration is considered after `apiFilterChain`, since it has an `@Order` value after `1` (no `@Order` defaults to last).
 This configuration is considered after `apiFilterChain`, since it has an `@Order` value after `1` (no `@Order` defaults to last).
+
+=== Choosing `securityMatcher` or `requestMatchers`
+
+A common question is:
+
+> What is the difference between the `http.securityMatcher()` method and `requestMatchers()` used for request authorization (i.e. inside of `http.authorizeHttpRequests()`)?
+
+To answer this question, it helps to understand that each `HttpSecurity` instance used to build a `SecurityFilterChain` contains a `RequestMatcher` to match incoming requests.
+If a request does not match a `SecurityFilterChain` with higher priority (e.g. `@Order(1)`), the request can be tried against a filter chain with lower priority (e.g. no `@Order`).
+
+[NOTE]
+====
+The matching logic for multiple filter chains is performed by the xref:servlet/architecture.adoc#servlet-filterchainproxy[`FilterChainProxy`].
+====
+
+The default `RequestMatcher` matches *any request* to ensure Spring Security protects *all requests by default*.
+
+[NOTE]
+====
+Specifying a `securityMatcher` overrides this default.
+====
+
+[WARNING]
+====
+If no filter chain matches a particular request, the request is *not protected* by Spring Security.
+====
+
+The following example demonstrates a single filter chain that only protects requests that begin with `/secured/`:
+
+[[choosing-security-matcher-request-matchers-kotlin]]
+[source,kotlin]
+----
+import org.springframework.security.config.annotation.web.invoke
+
+@Configuration
+@EnableWebSecurity
+class PartialSecurityConfig {
+	@Bean
+	open fun userDetailsService(): UserDetailsService {
+		// ...
+	}
+
+	@Bean
+	open fun securedFilterChain(http: HttpSecurity): SecurityFilterChain {
+		http {
+			securityMatcher("/secured/**")                             <1>
+			authorizeHttpRequests {
+				authorize("/secured/user", hasRole("USER"))            <2>
+				authorize("/secured/admin", hasRole("ADMIN"))          <3>
+				authorize(anyRequest, authenticated)                   <4>
+			}
+			httpBasic { }
+			formLogin { }
+		}
+		return http.build()
+	}
+}
+----
+<1> Requests that begin with `/secured/` will be protected but any other requests are not protected.
+<2> Requests to `/secured/user` require the `ROLE_USER` authority.
+<3> Requests to `/secured/admin` require the `ROLE_ADMIN` authority.
+<4> Any other requests (such as `/secured/other`) simply require an authenticated user.
+
+[TIP]
+====
+It is _recommended_ to provide a `SecurityFilterChain` that does not specify any `securityMatcher` to ensure the entire application is protected, as demonstrated in the <<multiple-httpsecurity-instances-kotlin,earlier example>>.
+====
+
+Notice that the `requestMatchers` method only applies to individual authorization rules.
+Each request listed there must also match the overall `securityMatcher` for this particular `HttpSecurity` instance used to create the `SecurityFilterChain`.
+Using `anyRequest()` in this example matches all other requests within this particular `SecurityFilterChain` (which must begin with `/secured/`).
+
+[NOTE]
+====
+See xref:servlet/authorization/authorize-http-requests.adoc[Authorize HttpServletRequests] for more information on `requestMatchers`.
+====
+
+=== `SecurityFilterChain` Endpoints
+
+Several filters in the `SecurityFilterChain` directly provide endpoints, such as the `UsernamePasswordAuthenticationFilter` which is set up by `http.formLogin()` and provides the `POST /login` endpoint.
+In the <<choosing-security-matcher-request-matchers-kotlin,above example>>, the `/login` endpoint is not matched by `http.securityMatcher("/secured/**")` and therefore that application would not have any `GET /login` or `POST /login` endpoint.
+Such requests would return `404 Not Found`.
+This is often surprising to users.
+
+Specifying `http.securityMatcher()` affects what requests are matched by that `SecurityFilterChain`.
+However, it does not automatically affect endpoints provided by the filter chain.
+In such cases, you may need to customize the URL of any endpoints you would like the filter chain to provide.
+
+The following example demonstrates a configuration that secures requests that begin with `/secured/` and denies all other requests, while also customizing endpoints provided by the `SecurityFilterChain`:
+
+[[security-filter-chain-endpoints-kotlin]]
+[source,kotlin]
+----
+import org.springframework.security.config.annotation.web.invoke
+
+@Configuration
+@EnableWebSecurity
+class SecuredSecurityConfig {
+	@Bean
+	open fun userDetailsService(): UserDetailsService {
+		// ...
+	}
+
+	@Bean
+	@Order(1)
+	open fun securedFilterChain(http: HttpSecurity): SecurityFilterChain {
+		http {
+			securityMatcher("/secured/**")                             <1>
+			authorizeHttpRequests {
+				authorize(anyRequest, authenticated)                   <2>
+			}
+			formLogin {                                                <3>
+                loginPage = "/secured/login"
+                loginProcessingUrl = "/secured/login"
+                permitAll = true
+			}
+			logout {                                                   <4>
+                logoutUrl = "/secured/logout"
+                logoutSuccessUrl = "/secured/login?logout"
+                permitAll = true
+			}
+		}
+		return http.build()
+	}
+
+	@Bean
+    open fun defaultFilterChain(http: HttpSecurity): SecurityFilterChain {
+        http {
+            authorizeHttpRequests {
+                authorize(anyRequest, denyAll)                         <5>
+            }
+        }
+        return http.build()
+    }
+}
+----
+<1> Requests that begin with `/secured/` will be protected by this filter chain.
+<2> Requests that begin with `/secured/` require an authenticated user.
+<3> Customize form login to prefix URLs with `/secured/`.
+<4> Customize logout to prefix URLs with `/secured/`.
+<5> All other requests will be denied.
+
+[NOTE]
+====
+This example customizes the login and logout pages, which disables Spring Security's generated pages.
+You must xref:servlet/authentication/passwords/form.adoc#servlet-authentication-form-custom[provide your own] custom endpoints for `GET /secured/login` and `GET /secured/logout`.
+Note that Spring Security still provides `POST /secured/login` and `POST /secured/logout` endpoints for you.
+====
+
+=== Real World Example
+
+The following example demonstrates a slightly more real-world configuration putting all of these elements together:
+
+[[real-world-example-kotlin]]
+[source,kotlin]
+----
+import org.springframework.security.config.annotation.web.invoke
+
+@Configuration
+@EnableWebSecurity
+class BankingSecurityConfig {
+    @Bean                                                              <1>
+    open fun userDetailsService(): UserDetailsService {
+        val users = User.withDefaultPasswordEncoder()
+        val manager = 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>
+    open fun approvalsSecurityFilterChain(http: HttpSecurity): SecurityFilterChain {
+        val approvalsPaths = arrayOf("/accounts/approvals/**", "/loans/approvals/**", "/credit-cards/approvals/**")
+        http {
+            securityMatcher(approvalsPaths)
+            authorizeHttpRequests {
+				authorize(anyRequest, hasRole("ADMIN"))
+            }
+            httpBasic { }
+        }
+        return http.build()
+    }
+
+    @Bean
+    @Order(2)                                                          <3>
+	open fun bankingSecurityFilterChain(http: HttpSecurity): SecurityFilterChain {
+        val bankingPaths = arrayOf("/accounts/**", "/loans/**", "/credit-cards/**", "/balances/**")
+		val viewBalancePaths = arrayOf("/balances/**")
+        http {
+            securityMatcher(bankingPaths)
+            authorizeHttpRequests {
+                authorize(viewBalancePaths, hasRole("VIEW_BALANCE"))
+				authorize(anyRequest, hasRole("USER"))
+            }
+        }
+        return http.build()
+    }
+
+    @Bean                                                              <4>
+	open fun defaultSecurityFilterChain(http: HttpSecurity): SecurityFilterChain {
+        val allowedPaths = arrayOf("/", "/user-login", "/user-logout", "/notices", "/contact", "/register")
+        http {
+            authorizeHttpRequests {
+                authorize(allowedPaths, permitAll)
+				authorize(anyRequest, authenticated)
+            }
+			formLogin {
+                loginPage = "/user-login"
+                loginProcessingUrl = "/user-login"
+			}
+			logout {
+                logoutUrl = "/user-logout"
+                logoutSuccessUrl = "/?logout"
+			}
+        }
+        return http.build()
+    }
+}
+----
+<1> Begin by configuring authentication settings.
+<2> Define a `SecurityFilterChain` instance with `@Order(1)`, which means that this filter chain will have the highest priority.
+    This filter chain applies only to requests that begin with `/accounts/approvals/`, `/loans/approvals/` or `/credit-cards/approvals/`.
+	Requests to this filter chain require the `ROLE_ADMIN` authority and allow HTTP Basic Authentication.
+<3> Next, create another `SecurityFilterChain` instance with `@Order(2)` which will be considered second.
+    This filter chain applies only to requests that begin with `/accounts/`, `/loans/`, `/credit-cards/`, or `/balances/`.
+	Notice that because this filter chain is second, any requests that include `/approvals/` will match the previous filter chain and will *not* be matched by this filter chain.
+	Requests to this filter chain require the `ROLE_USER` authority.
+	This filter chain does not define any authentication because the next (default) filter chain contains that configuration.
+<4> Lastly, create an additional `SecurityFilterChain` instance without an `@Order` annotation.
+	This configuration will handle requests not covered by the other filter chains and will be processed last (no `@Order` defaults to last).
+	Requests that match `/`, `/user-login`, `/user-logout`, `/notices`, `/contact` and `/register` allow access without authentication.
+	Any other requests require the user to be authenticated to access any URL not explicitly allowed or protected by other filter chains.