Skip to content

Commit 9b1e9c5

Browse files
committed
Polish and sync java and kotlin configuration docs
Issue gh-15029
1 parent 133c87a commit 9b1e9c5

File tree

2 files changed

+252
-17
lines changed

2 files changed

+252
-17
lines changed

docs/modules/ROOT/pages/servlet/configuration/java.adoc

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -185,10 +185,10 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
185185
The default configuration (shown in the preceding example):
186186

187187
* Ensures that any request to our application requires the user to be authenticated
188-
* Lets users authenticate with form based login
188+
* Lets users authenticate with form-based login
189189
* Lets users authenticate with HTTP Basic authentication
190190

191-
Note that this configuration is parallels the XML Namespace configuration:
191+
Note that this configuration parallels the XML namespace configuration:
192192

193193
[source,xml]
194194
----
@@ -206,7 +206,7 @@ This approach allows us to define distinct security configurations tailored to s
206206

207207
We can configure multiple `HttpSecurity` instances just as we can have multiple `<http>` blocks in XML.
208208
The key is to register multiple `SecurityFilterChain` ``@Bean``s.
209-
The following example has a different configuration for URLs that begin with `/api/`.
209+
The following example has a different configuration for URLs that begin with `/api/`:
210210

211211
[[multiple-httpsecurity-instances-java]]
212212
[source,java]
@@ -216,7 +216,6 @@ The following example has a different configuration for URLs that begin with `/a
216216
public class MultiHttpSecurityConfig {
217217
@Bean <1>
218218
public UserDetailsService userDetailsService() throws Exception {
219-
// ensure the passwords are encoded properly
220219
UserBuilder users = User.withDefaultPasswordEncoder();
221220
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
222221
manager.createUser(users.username("user").password("password").roles("USER").build());
@@ -411,7 +410,6 @@ public class BankingSecurityConfig {
411410
412411
@Bean <1>
413412
public UserDetailsService userDetailsService() {
414-
// ensure the passwords are encoded properly
415413
UserBuilder users = User.withDefaultPasswordEncoder();
416414
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
417415
manager.createUser(users.username("user1").password("password").roles("USER", "VIEW_BALANCE").build());
@@ -449,7 +447,7 @@ public class BankingSecurityConfig {
449447
450448
@Bean <4>
451449
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
452-
String[] allowedPaths = { "/user-login", "/user-logout", "/notices", "/contact", "/register" };
450+
String[] allowedPaths = { "/", "/user-login", "/user-logout", "/notices", "/contact", "/register" };
453451
http
454452
.authorizeHttpRequests(authorize -> authorize
455453
.requestMatchers(allowedPaths).permitAll()
@@ -478,7 +476,7 @@ public class BankingSecurityConfig {
478476
This filter chain does not define any authentication because the next (default) filter chain contains that configuration.
479477
<4> Lastly, create an additional `SecurityFilterChain` instance without an `@Order` annotation.
480478
This configuration will handle requests not covered by the other filter chains and will be processed last (no `@Order` defaults to last).
481-
Requests that match `/user-login`, `/user-logout`, `/notices`, `/contact` and `/register` allow access without authentication.
479+
Requests that match `/`, `/user-login`, `/user-logout`, `/notices`, `/contact` and `/register` allow access without authentication.
482480
Any other requests require the user to be authenticated to access any URL not explicitly allowed or protected by other filter chains.
483481

484482
[[jc-custom-dsls]]

docs/modules/ROOT/pages/servlet/configuration/kotlin.adoc

Lines changed: 247 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain {
3838
[NOTE]
3939
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.
4040

41-
The default configuration (shown in the preceding listing):
41+
The default configuration (shown in the preceding example):
4242

4343
* Ensures that any request to our application requires the user to be authenticated
4444
* Lets users authenticate with form-based login
@@ -55,12 +55,16 @@ Note that this configuration parallels the XML namespace configuration:
5555
</http>
5656
----
5757

58-
== Multiple HttpSecurity Instances
58+
=== Multiple HttpSecurity Instances
5959

60-
We can configure multiple `HttpSecurity` instances, just as we can have multiple `<http>` blocks.
60+
To effectively manage security in an application where certain areas need different protection, we can employ multiple filter chains alongside the `securityMatcher` DSL method.
61+
This approach allows us to define distinct security configurations tailored to specific parts of the application, enhancing overall application security and control.
62+
63+
We can configure multiple `HttpSecurity` instances just as we can have multiple `<http>` blocks in XML.
6164
The key is to register multiple `SecurityFilterChain` ``@Bean``s.
62-
The following example has a different configuration for URLs that start with `/api/`:
65+
The following example has a different configuration for URLs that begin with `/api/`:
6366

67+
[[multiple-httpsecurity-instances-kotlin]]
6468
[source,kotlin]
6569
----
6670
import org.springframework.security.config.annotation.web.invoke
@@ -69,16 +73,16 @@ import org.springframework.security.config.annotation.web.invoke
6973
@EnableWebSecurity
7074
class MultiHttpSecurityConfig {
7175
@Bean <1>
72-
public fun userDetailsService(): UserDetailsService {
73-
val users: User.UserBuilder = User.withDefaultPasswordEncoder()
76+
open fun userDetailsService(): UserDetailsService {
77+
val users = User.withDefaultPasswordEncoder()
7478
val manager = InMemoryUserDetailsManager()
7579
manager.createUser(users.username("user").password("password").roles("USER").build())
7680
manager.createUser(users.username("admin").password("password").roles("USER","ADMIN").build())
7781
return manager
7882
}
7983
80-
@Order(1) <2>
8184
@Bean
85+
@Order(1) <2>
8286
open fun apiFilterChain(http: HttpSecurity): SecurityFilterChain {
8387
http {
8488
securityMatcher("/api/**") <3>
@@ -102,10 +106,243 @@ class MultiHttpSecurityConfig {
102106
}
103107
}
104108
----
105-
106109
<1> Configure Authentication as usual.
107110
<2> Create an instance of `SecurityFilterChain` that contains `@Order` to specify which `SecurityFilterChain` should be considered first.
108-
<3> The `http.securityMatcher` states that this `HttpSecurity` is applicable only to URLs that start with `/api/`
111+
<3> The `http.securityMatcher()` states that this `HttpSecurity` is applicable only to URLs that begin with `/api/`.
109112
<4> Create another instance of `SecurityFilterChain`.
110-
If the URL does not start with `/api/`, this configuration is used.
113+
If the URL does not begin with `/api/`, this configuration is used.
111114
This configuration is considered after `apiFilterChain`, since it has an `@Order` value after `1` (no `@Order` defaults to last).
115+
116+
=== Choosing `securityMatcher` or `requestMatchers`
117+
118+
A common question is:
119+
120+
> What is the difference between the `http.securityMatcher()` method and `requestMatchers()` used for request authorization (i.e. inside of `http.authorizeHttpRequests()`)?
121+
122+
To answer this question, it helps to understand that each `HttpSecurity` instance used to build a `SecurityFilterChain` contains a `RequestMatcher` to match incoming requests.
123+
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`).
124+
125+
[NOTE]
126+
====
127+
The matching logic for multiple filter chains is performed by the xref:servlet/architecture.adoc#servlet-filterchainproxy[`FilterChainProxy`].
128+
====
129+
130+
The default `RequestMatcher` matches *any request* to ensure Spring Security protects *all requests by default*.
131+
132+
[NOTE]
133+
====
134+
Specifying a `securityMatcher` overrides this default.
135+
====
136+
137+
[WARNING]
138+
====
139+
If no filter chain matches a particular request, the request is *not protected* by Spring Security.
140+
====
141+
142+
The following example demonstrates a single filter chain that only protects requests that begin with `/secured/`:
143+
144+
[[choosing-security-matcher-request-matchers-kotlin]]
145+
[source,kotlin]
146+
----
147+
import org.springframework.security.config.annotation.web.invoke
148+
149+
@Configuration
150+
@EnableWebSecurity
151+
class PartialSecurityConfig {
152+
@Bean
153+
open fun userDetailsService(): UserDetailsService {
154+
// ...
155+
}
156+
157+
@Bean
158+
open fun securedFilterChain(http: HttpSecurity): SecurityFilterChain {
159+
http {
160+
securityMatcher("/secured/**") <1>
161+
authorizeHttpRequests {
162+
authorize("/secured/user", hasRole("USER")) <2>
163+
authorize("/secured/admin", hasRole("ADMIN")) <3>
164+
authorize(anyRequest, authenticated) <4>
165+
}
166+
httpBasic { }
167+
formLogin { }
168+
}
169+
return http.build()
170+
}
171+
}
172+
----
173+
<1> Requests that begin with `/secured/` will be protected but any other requests are not protected.
174+
<2> Requests to `/secured/user` require the `ROLE_USER` authority.
175+
<3> Requests to `/secured/admin` require the `ROLE_ADMIN` authority.
176+
<4> Any other requests (such as `/secured/other`) simply require an authenticated user.
177+
178+
[TIP]
179+
====
180+
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>>.
181+
====
182+
183+
Notice that the `requestMatchers` method only applies to individual authorization rules.
184+
Each request listed there must also match the overall `securityMatcher` for this particular `HttpSecurity` instance used to create the `SecurityFilterChain`.
185+
Using `anyRequest()` in this example matches all other requests within this particular `SecurityFilterChain` (which must begin with `/secured/`).
186+
187+
[NOTE]
188+
====
189+
See xref:servlet/authorization/authorize-http-requests.adoc[Authorize HttpServletRequests] for more information on `requestMatchers`.
190+
====
191+
192+
=== `SecurityFilterChain` Endpoints
193+
194+
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.
195+
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.
196+
Such requests would return `404 Not Found`.
197+
This is often surprising to users.
198+
199+
Specifying `http.securityMatcher()` affects what requests are matched by that `SecurityFilterChain`.
200+
However, it does not automatically affect endpoints provided by the filter chain.
201+
In such cases, you may need to customize the URL of any endpoints you would like the filter chain to provide.
202+
203+
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`:
204+
205+
[[security-filter-chain-endpoints-kotlin]]
206+
[source,kotlin]
207+
----
208+
import org.springframework.security.config.annotation.web.invoke
209+
210+
@Configuration
211+
@EnableWebSecurity
212+
class SecuredSecurityConfig {
213+
@Bean
214+
open fun userDetailsService(): UserDetailsService {
215+
// ...
216+
}
217+
218+
@Bean
219+
@Order(1)
220+
open fun securedFilterChain(http: HttpSecurity): SecurityFilterChain {
221+
http {
222+
securityMatcher("/secured/**") <1>
223+
authorizeHttpRequests {
224+
authorize(anyRequest, authenticated) <2>
225+
}
226+
formLogin { <3>
227+
loginPage = "/secured/login"
228+
loginProcessingUrl = "/secured/login"
229+
permitAll = true
230+
}
231+
logout { <4>
232+
logoutUrl = "/secured/logout"
233+
logoutSuccessUrl = "/secured/login?logout"
234+
permitAll = true
235+
}
236+
}
237+
return http.build()
238+
}
239+
240+
@Bean
241+
open fun defaultFilterChain(http: HttpSecurity): SecurityFilterChain {
242+
http {
243+
authorizeHttpRequests {
244+
authorize(anyRequest, denyAll) <5>
245+
}
246+
}
247+
return http.build()
248+
}
249+
}
250+
----
251+
<1> Requests that begin with `/secured/` will be protected by this filter chain.
252+
<2> Requests that begin with `/secured/` require an authenticated user.
253+
<3> Customize form login to prefix URLs with `/secured/`.
254+
<4> Customize logout to prefix URLs with `/secured/`.
255+
<5> All other requests will be denied.
256+
257+
[NOTE]
258+
====
259+
This example customizes the login and logout pages, which disables Spring Security's generated pages.
260+
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`.
261+
Note that Spring Security still provides `POST /secured/login` and `POST /secured/logout` endpoints for you.
262+
====
263+
264+
=== Real World Example
265+
266+
The following example demonstrates a slightly more real-world configuration putting all of these elements together:
267+
268+
[[real-world-example-kotlin]]
269+
[source,kotlin]
270+
----
271+
import org.springframework.security.config.annotation.web.invoke
272+
273+
@Configuration
274+
@EnableWebSecurity
275+
class BankingSecurityConfig {
276+
@Bean <1>
277+
open fun userDetailsService(): UserDetailsService {
278+
val users = User.withDefaultPasswordEncoder()
279+
val manager = InMemoryUserDetailsManager()
280+
manager.createUser(users.username("user1").password("password").roles("USER", "VIEW_BALANCE").build())
281+
manager.createUser(users.username("user2").password("password").roles("USER").build())
282+
manager.createUser(users.username("admin").password("password").roles("ADMIN").build())
283+
return manager
284+
}
285+
286+
@Bean
287+
@Order(1) <2>
288+
open fun approvalsSecurityFilterChain(http: HttpSecurity): SecurityFilterChain {
289+
val approvalsPaths = arrayOf("/accounts/approvals/**", "/loans/approvals/**", "/credit-cards/approvals/**")
290+
http {
291+
securityMatcher(approvalsPaths)
292+
authorizeHttpRequests {
293+
authorize(anyRequest, hasRole("ADMIN"))
294+
}
295+
httpBasic { }
296+
}
297+
return http.build()
298+
}
299+
300+
@Bean
301+
@Order(2) <3>
302+
open fun bankingSecurityFilterChain(http: HttpSecurity): SecurityFilterChain {
303+
val bankingPaths = arrayOf("/accounts/**", "/loans/**", "/credit-cards/**", "/balances/**")
304+
val viewBalancePaths = arrayOf("/balances/**")
305+
http {
306+
securityMatcher(bankingPaths)
307+
authorizeHttpRequests {
308+
authorize(viewBalancePaths, hasRole("VIEW_BALANCE"))
309+
authorize(anyRequest, hasRole("USER"))
310+
}
311+
}
312+
return http.build()
313+
}
314+
315+
@Bean <4>
316+
open fun defaultSecurityFilterChain(http: HttpSecurity): SecurityFilterChain {
317+
val allowedPaths = arrayOf("/", "/user-login", "/user-logout", "/notices", "/contact", "/register")
318+
http {
319+
authorizeHttpRequests {
320+
authorize(allowedPaths, permitAll)
321+
authorize(anyRequest, authenticated)
322+
}
323+
formLogin {
324+
loginPage = "/user-login"
325+
loginProcessingUrl = "/user-login"
326+
}
327+
logout {
328+
logoutUrl = "/user-logout"
329+
logoutSuccessUrl = "/?logout"
330+
}
331+
}
332+
return http.build()
333+
}
334+
}
335+
----
336+
<1> Begin by configuring authentication settings.
337+
<2> Define a `SecurityFilterChain` instance with `@Order(1)`, which means that this filter chain will have the highest priority.
338+
This filter chain applies only to requests that begin with `/accounts/approvals/`, `/loans/approvals/` or `/credit-cards/approvals/`.
339+
Requests to this filter chain require the `ROLE_ADMIN` authority and allow HTTP Basic Authentication.
340+
<3> Next, create another `SecurityFilterChain` instance with `@Order(2)` which will be considered second.
341+
This filter chain applies only to requests that begin with `/accounts/`, `/loans/`, `/credit-cards/`, or `/balances/`.
342+
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.
343+
Requests to this filter chain require the `ROLE_USER` authority.
344+
This filter chain does not define any authentication because the next (default) filter chain contains that configuration.
345+
<4> Lastly, create an additional `SecurityFilterChain` instance without an `@Order` annotation.
346+
This configuration will handle requests not covered by the other filter chains and will be processed last (no `@Order` defaults to last).
347+
Requests that match `/`, `/user-login`, `/user-logout`, `/notices`, `/contact` and `/register` allow access without authentication.
348+
Any other requests require the user to be authenticated to access any URL not explicitly allowed or protected by other filter chains.

0 commit comments

Comments
 (0)