Skip to content

Commit 2482e8e

Browse files
committed
Polish gh-15029
1 parent 046a1fc commit 2482e8e

File tree

1 file changed

+207
-70
lines changed
  • docs/modules/ROOT/pages/servlet/configuration

1 file changed

+207
-70
lines changed

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

Lines changed: 207 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
176176
.authorizeHttpRequests(authorize -> authorize
177177
.anyRequest().authenticated()
178178
)
179-
.formLogin(withDefaults())
180-
.httpBasic(withDefaults());
179+
.formLogin(Customizer.withDefaults())
180+
.httpBasic(Customizer.withDefaults());
181181
return http.build();
182182
}
183183
----
@@ -199,12 +199,16 @@ Note that this configuration is parallels the XML Namespace configuration:
199199
</http>
200200
----
201201

202-
== Multiple HttpSecurity Instances
202+
=== Multiple HttpSecurity Instances
203+
204+
To effectively manage security in an application where certain areas need different protection, we can employ multiple filter chains alongside the `securityMatcher` DSL method.
205+
This approach allows us to define distinct security configurations tailored to specific parts of the application, enhancing overall application security and control.
203206

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

211+
[[multiple-httpsecurity-instances-java]]
208212
[source,java]
209213
----
210214
@Configuration
@@ -228,7 +232,7 @@ public class MultiHttpSecurityConfig {
228232
.authorizeHttpRequests(authorize -> authorize
229233
.anyRequest().hasRole("ADMIN")
230234
)
231-
.httpBasic(withDefaults());
235+
.httpBasic(Customizer.withDefaults());
232236
return http.build();
233237
}
234238
@@ -238,111 +242,244 @@ public class MultiHttpSecurityConfig {
238242
.authorizeHttpRequests(authorize -> authorize
239243
.anyRequest().authenticated()
240244
)
241-
.formLogin(withDefaults());
245+
.formLogin(Customizer.withDefaults());
242246
return http.build();
243247
}
244248
}
245249
----
246250
<1> Configure Authentication as usual.
247251
<2> Create an instance of `SecurityFilterChain` that contains `@Order` to specify which `SecurityFilterChain` should be considered first.
248-
<3> The `http.securityMatcher` states that this `HttpSecurity` is applicable only to URLs that start with `/api/`.
252+
<3> The `http.securityMatcher()` states that this `HttpSecurity` is applicable only to URLs that begin with `/api/`.
249253
<4> Create another instance of `SecurityFilterChain`.
250-
If the URL does not start with `/api/`, this configuration is used.
254+
If the URL does not begin with `/api/`, this configuration is used.
251255
This configuration is considered after `apiFilterChain`, since it has an `@Order` value after `1` (no `@Order` defaults to last).
252256

253-
To effectively manage security in an application where certain areas and the entire app need protection, we can employ multiple filter chains alongside the securityMatcher. This approach allows us to define distinct security configurations tailored to specific parts while also ensuring overall application security. The provided example showcases distinct configurations for URLs starting with "/account/", "/balance", "/loans-approval/", "/credit-cards-approval/", "/loans", "/cards", "/notices", "/contact", "/login", "/logout" and "/register". This approach allows tailored security settings for specific endpoints, enhancing overall application security and control.
257+
=== Choosing `securityMatcher` or `requestMatchers`
258+
259+
A common question is:
260+
261+
> What is the difference between the `http.securityMatcher()` method and `requestMatchers()` used for request authorization (i.e. inside of `http.authorizeHttpRequests()`)?
262+
263+
To answer this question, it helps to understand that each `HttpSecurity` instance used to build a `SecurityFilterChain` contains a `RequestMatcher` to match incoming requests.
264+
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`).
265+
266+
[NOTE]
267+
====
268+
The matching logic for multiple filter chains is performed by the xref:servlet/architecture.adoc#servlet-filterchainproxy[`FilterChainProxy`].
269+
====
270+
271+
The default `RequestMatcher` matches *any request* to ensure Spring Security protects *all requests by default*.
272+
273+
[NOTE]
274+
====
275+
Specifying a `securityMatcher` overrides this default.
276+
====
277+
278+
[WARNING]
279+
====
280+
If no filter chain matches a particular request, the request is *not protected* by Spring Security.
281+
====
282+
283+
The following example demonstrates a single filter chain that only protects requests that begin with `/secured/`:
284+
285+
[[choosing-security-matcher-request-matchers-java]]
286+
[source,java]
287+
----
288+
@Configuration
289+
@EnableWebSecurity
290+
public class PartialSecurityConfig {
291+
292+
@Bean
293+
public UserDetailsService userDetailsService() throws Exception {
294+
// ...
295+
}
296+
297+
@Bean
298+
public SecurityFilterChain securedFilterChain(HttpSecurity http) throws Exception {
299+
http
300+
.securityMatcher("/secured/**") <1>
301+
.authorizeHttpRequests(authorize -> authorize
302+
.requestMatchers("/secured/user").hasRole("USER") <2>
303+
.requestMatchers("/secured/admin").hasRole("ADMIN") <3>
304+
.anyRequest().authenticated() <4>
305+
)
306+
.httpBasic(Customizer.withDefaults())
307+
.formLogin(Customizer.withDefaults());
308+
return http.build();
309+
}
310+
}
311+
----
312+
<1> Requests that begin with `/secured/` will be protected but any other requests are not protected.
313+
<2> Requests to `/secured/user` require the `ROLE_USER` authority.
314+
<3> Requests to `/secured/admin` require the `ROLE_ADMIN` authority.
315+
<4> Any other requests (such as `/secured/other`) simply require an authenticated user.
316+
317+
[TIP]
318+
====
319+
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-java,earlier example>>.
320+
====
321+
322+
Notice that the `requestMatchers` method only applies to individual authorization rules.
323+
Each request listed there must also match the overall `securityMatcher` for this particular `HttpSecurity` instance used to create the `SecurityFilterChain`.
324+
Using `anyRequest()` in this example matches all other requests within this particular `SecurityFilterChain` (which must begin with `/secured/`).
325+
326+
[NOTE]
327+
====
328+
See xref:servlet/authorization/authorize-http-requests.adoc[Authorize HttpServletRequests] for more information on `requestMatchers`.
329+
====
330+
331+
=== `SecurityFilterChain` Endpoints
332+
333+
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.
334+
In the <<choosing-security-matcher-request-matchers-java,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.
335+
Such requests would return `404 Not Found`.
336+
This is often surprising to users.
337+
338+
Specifying `http.securityMatcher()` affects what requests are matched by that `SecurityFilterChain`.
339+
However, it does not automatically affect endpoints provided by the filter chain.
340+
In such cases, you may need to customize the URL of any endpoints you would like the filter chain to provide.
341+
342+
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`:
343+
344+
[[security-filter-chain-endpoints-java]]
345+
[source,java]
346+
----
347+
@Configuration
348+
@EnableWebSecurity
349+
public class SecuredSecurityConfig {
350+
351+
@Bean
352+
public UserDetailsService userDetailsService() throws Exception {
353+
// ...
354+
}
355+
356+
@Bean
357+
@Order(1)
358+
public SecurityFilterChain securedFilterChain(HttpSecurity http) throws Exception {
359+
http
360+
.securityMatcher("/secured/**") <1>
361+
.authorizeHttpRequests(authorize -> authorize
362+
.anyRequest().authenticated() <2>
363+
)
364+
.formLogin(formLogin -> formLogin <3>
365+
.loginPage("/secured/login")
366+
.loginProcessingUrl("/secured/login")
367+
.permitAll()
368+
)
369+
.logout(logout -> logout <4>
370+
.logoutUrl("/secured/logout")
371+
.logoutSuccessUrl("/secured/login?logout")
372+
.permitAll()
373+
)
374+
.formLogin(Customizer.withDefaults());
375+
return http.build();
376+
}
377+
378+
@Bean
379+
public SecurityFilterChain defaultFilterChain(HttpSecurity http) throws Exception {
380+
http
381+
.authorizeHttpRequests(authorize -> authorize
382+
.anyRequest().denyAll() <5>
383+
);
384+
return http.build();
385+
}
386+
}
387+
----
388+
<1> Requests that begin with `/secured/` will be protected by this filter chain.
389+
<2> Requests that begin with `/secured/` require an authenticated user.
390+
<3> Customize form login to prefix URLs with `/secured/`.
391+
<4> Customize logout to prefix URLs with `/secured/`.
392+
<5> All other requests will be denied.
393+
394+
[NOTE]
395+
====
396+
This example customizes the login and logout pages, which disables Spring Security's generated pages.
397+
You must xref:servlet/authentication/passwords/form.html#servlet-authentication-form-custom[provide your own] custom endpoints for `GET /secured/login` and `GET /secured/logout`.
398+
Note that Spring Security still provides `POST /secured/login` and `POST /secured/logout` endpoints for you.
399+
====
400+
401+
=== Real World Example
402+
403+
The following example demonstrates a slightly more real-world configuration putting all of these elements together:
254404

405+
[[real-world-example-java]]
255406
[source,java]
256407
----
257408
@Configuration
258409
@EnableWebSecurity
259-
public class CustomSecurityFilterChainConfig {
410+
public class BankingSecurityConfig {
260411
261-
@Bean <1>
412+
@Bean <1>
262413
public UserDetailsService userDetailsService() {
263414
// ensure the passwords are encoded properly
415+
UserBuilder users = User.withDefaultPasswordEncoder();
264416
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
265-
manager.createUser(User.withDefaultPasswordEncoder().username("user").password("password").roles("USER").build());
266-
manager.createUser(User.withDefaultPasswordEncoder().username("admin").password("password").roles("USER", "ADMIN").build());
417+
manager.createUser(users.username("user1").password("password").roles("USER", "VIEW_BALANCE").build());
418+
manager.createUser(users.username("user2").password("password").roles("USER").build());
419+
manager.createUser(users.username("admin").password("password").roles("ADMIN").build());
267420
return manager;
268421
}
269422
270423
@Bean
271-
@Order(1) <2>
272-
public SecurityFilterChain dashBoardFilterChain(HttpSecurity http) throws Exception {
424+
@Order(1) <2>
425+
public SecurityFilterChain approvalsSecurityFilterChain(HttpSecurity http) throws Exception {
426+
String[] approvalsPaths = { "/accounts/approvals/**", "/loans/approvals/**", "/credit-cards/approvals/**" };
273427
http
274-
.securityMatcher("/account/**", "/loans/**", "/cards/**") <3>
275-
.authorizeHttpRequests(authorizeRequests -> authorizeRequests
276-
.anyRequest().hasRole("USER") <4>
277-
)
278-
.httpBasic(Customizer.withDefaults());
279-
return http.build();
280-
}
281-
282-
@Bean
283-
@Order(2) <5>
284-
public SecurityFilterChain balanceFilterChain(HttpSecurity http) throws Exception {
285-
http
286-
.securityMatcher("/balance/**") <6>
287-
.authorizeHttpRequests(authorizeRequests -> authorizeRequests
288-
.anyRequest().hasAnyRole("USER", "ADMIN") <7>
428+
.securityMatcher(approvalsPaths)
429+
.authorizeHttpRequests(authorize -> authorize
430+
.anyRequest().hasRole("ADMIN")
289431
)
290432
.httpBasic(Customizer.withDefaults());
291433
return http.build();
292434
}
293435
294436
@Bean
295-
@Order(3) <8>
296-
public SecurityFilterChain approvalsFilterChain(HttpSecurity http) throws Exception {
437+
@Order(2) <3>
438+
public SecurityFilterChain bankingSecurityFilterChain(HttpSecurity http) throws Exception {
439+
String[] bankingPaths = { "/accounts/**", "/loans/**", "/credit-cards/**", "/balances/**" };
440+
String[] viewBalancePaths = { "/balances/**" };
297441
http
298-
.securityMatcher("/loans-approval/**", "/credit-cards-approval/**")<9>
299-
.authorizeHttpRequests(authorizeRequests -> authorizeRequests
300-
.anyRequest().hasRole("ADMIN") <10>
301-
)
302-
.httpBasic(Customizer.withDefaults());
442+
.securityMatcher(bankingPaths)
443+
.authorizeHttpRequests(authorize -> authorize
444+
.requestMatchers(viewBalancePaths).hasRole("VIEW_BALANCE")
445+
.anyRequest().hasRole("USER")
446+
);
303447
return http.build();
304448
}
305449
306-
@Bean
307-
@Order(4) <11>
308-
public SecurityFilterChain allowedFilterChain(HttpSecurity http) throws Exception {
450+
@Bean <4>
451+
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
452+
String[] allowedPaths = { "/user-login", "/user-logout", "/notices", "/contact", "/register" };
309453
http
310-
.authorizeHttpRequests(authorizeRequests -> authorizeRequests
311-
.requestMatchers("/login","/logout","/notices", "/contact", "/register") <12>
312-
.permitAll() <13>
454+
.authorizeHttpRequests(authorize -> authorize
455+
.requestMatchers(allowedPaths).permitAll()
456+
.anyRequest().authenticated()
313457
)
314-
.formLogin(Customizer.withDefaults())
315-
.httpBasic(Customizer.withDefaults());
316-
return http.build();
317-
}
318-
319-
@Bean <14>
320-
public SecurityFilterChain defaultFilterChain(HttpSecurity http) throws Exception {
321-
http
322-
.authorizeHttpRequests(authorizeRequests -> authorizeRequests
323-
.anyRequest().authenticated() <15>
324-
)
325-
.formLogin(Customizer.withDefaults());
458+
.formLogin(formLogin -> formLogin
459+
.loginPage("/user-login")
460+
.loginProcessingUrl("/user-login")
461+
)
462+
.logout(logout -> logout
463+
.logoutUrl("/user-logout")
464+
.logoutSuccessUrl("/?logout")
465+
);
326466
return http.build();
327467
}
328468
}
329469
----
330470
<1> Begin by configuring authentication settings.
331-
<2> Define a SecurityFilterChain instance with @Order(1), which means that this chain will have the highest priority.
332-
<3> Specify that the http.securityMatcher applies only to "/account", "/loans", and "/cards" URLs.
333-
<4> Requires the user to have the role "USER" to access the URLs "/account", "/loans", and "/cards".
334-
<5> Next, create another SecurityFilterChain instance with @Order(2), this chain will be considered second.
335-
<6> Indicate that the http.securityMatcher applies only to "/balance" URL.
336-
<7> Requires the user to have the role "USER" or the role "ADMIN" to access the URL "/balance"
337-
<8> Next, create another SecurityFilterChain instance with @Order(3), this particular security filter chain will be the third in the order of execution.
338-
<9> The http.securityMatcher applies only to "/loans-approval" and "/credit-cards-approval" URLs.
339-
<10> The user must have the role "ADMIN" to access the URLs "/loans-approval" and "/credit-cards-approval"
340-
<11> Define a SecurityFilterChain instance with @Order(4) this chain will be considered fourth.
341-
<12> The http.securityMatcher applies only to "/login", "/logout", "/notices", "/contact", "/register" URLs.
342-
<13> Allows access to these specific URLs without authentication.
343-
<14> 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).
344-
<15> Requires the user to be authenticated to access any URL not explicitly allowed or protected by other filter chains.
345-
471+
<2> Define a `SecurityFilterChain` instance with `@Order(1)`, which means that this filter chain will have the highest priority.
472+
This filter chain applies only to requests that begin with `/accounts/approvals/`, `/loans/approvals/` or `/credit-cards/approvals/`.
473+
Requests to this filter chain require the `ROLE_ADMIN` authority and allow HTTP Basic Authentication.
474+
<3> Next, create another `SecurityFilterChain` instance with `@Order(2)` which will be considered second.
475+
This filter chain applies only to requests that begin with `/accounts/`, `/loans/`, `/credit-cards/`, or `/balances/`.
476+
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.
477+
Requests to this filter chain require the `ROLE_USER` authority.
478+
This filter chain does not define any authentication because the next (default) filter chain contains that configuration.
479+
<4> Lastly, create an additional `SecurityFilterChain` instance without an `@Order` annotation.
480+
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.
482+
Any other requests require the user to be authenticated to access any URL not explicitly allowed or protected by other filter chains.
346483

347484
[[jc-custom-dsls]]
348485
== Custom DSLs

0 commit comments

Comments
 (0)