TL;DR
Since Spring 6.0 release configuration methods antMatchers()
, mvcMathcers()
and regexMatchers()
have been removed from the API.
And several flavors of requestMatchers()
method has been provided as a replacement.
gh-11939 - Remove deprecated antMatchers, mvcMatchers, regexMatchers helper methods from Java Configuration. Instead, use requestMatchers
or HttpSecurity#securityMatchers
.
Also, an overloaded method authorizeHttpRequests()
was introduced to replace Deprecated authorizeRequests()
.
Even if you're using an earlier Spring version in your project and not going to update to Spring 6 very soon, antMatchers()
isn't the best tool you can choose for securing requests to your application.
While applying security rules using antMatchers()
you need to be very careful because if you secure let's say path "/foo"
these restrictions wouldn't be applied to other aliases of this path like "/foo/"
, "/foo.thml"
. As a consequence, it's very easy to misconfigure security rules and introduce a vulnerability (for instance, a path that is supposed to be accessible only for Admins becomes available for any authenticated user, it's surprising that none of the answers above mentions this).
According to the documentation, the recommended way to restrict access to certain URL since 5.8
is to use HttpSecurity.authorizeHttpRequests()
, which as well as its predecessor comes in two flavors:
A parameterless version, which returns an object responsible for configuring the requests (to be precise, an instance of this lengthy-name class). So we can chain requestMatchers()
calls directly on it.
And the second one expecting an instance of the Customizer
interface which allow to apply enhanced DSL (domain-specific language), by using lambda expressions. See this post, it uses outdated matching methods but nicely illustrates the key idea of Lambda DSL, which makes the configuration more intuitive to read by visually grouping configuration options and eliminates the need to use method and()
.
Which version of authorizeHttpRequests()
to use is a stylistic choice (both are valid and supported in 6.0
).
Now requestMatchers()
are coming into play, this method has four overloaded versions which can replace any of the removed matching methods:
requestMatchers( String ... )
- expects a varargs of String
patterns. This matcher uses the same rules for matching as Spring MVC. I.e. it would act in the same way as old mvcMatchers()
, so that pattern /foo
would match all existing aliases of that path like "/foo"
, "/foo/"
, "/foo.html"
. All other versions of requestMatchers()
have the same matching behavior, it eliminates the possibility of misconfiguration, which was an Achilles' heel of antMatchers()
. Note that the corresponding restriction (hasRole()
, access()
, etc.) would be applied to any matching request regardless of its HttpMethod.
Example:
.requestMatchers("/foo/*").hasRole("ADMIN") // only authenticated user with role ADMIN can access path /foo/something
.requestMatchers("/bar/*", "/baz/*").hasRole("ADMIN") // only authenticated requests to paths /foo/something and /baz/something are allowed
requestMatchers( HttpMethod )
- expects an HttpMethod
as argument. The corresponding restriction (hasRole()
, access()
, etc.) would be applied to any request handled by the current SecurityFilterChain
having specified HttpMethod. If null
provided as an argument, any request would match.
Example:
.requestMatchers(HttpMethod.POST, "/bar/**").hasAnyRole("USER", "ADMIN") // any authenticated POST-requests should from an ADMIN or USER are allowed
Example:
.requestMatchers(HttpMethod.POST, "/bar/**").hasAnyRole("USER", "ADMIN") // any POST-request should be authenticatd
.requestMatchers(HttpMethod.DELETE, "/baz/**").hasRole( "ADMIN") // only ADMINs can issue DELETE-requests to these paths
requestMatchers( RequestMatcher ... )
- the last version is probably the most flexible one, it allows to provide an arbitrary number of combines RequestMatcher
instances. We don't need to implement this interface ourself (unless there's special need), there are several implementations available out of the box including RegexRequestMatcher
(which can be used to replace outdated regexMatchers()
).
Example:
.requestMatchers(new RegexRequestMatcher("/foo/bar", "POST")).authenticated()
Example - Addressing the Original problem
I would like to allow POST
requests only to the "/api/subscription"
path.
For that we would need to use this flavor of requestMatchers(HttpMethod, String...)
which allows to specify HTTP-method and pattern for path "/api/subscription"
.
Reminder: WebSecurityConfigureAdapter
is deprecated since Spring Security 5.7
release, and now HttpSecurity
is being configured through SecurityFilterChain
which should be defined as Bean.
Here's how we can secure requests to paths defined in the question using Spring 6 and lambda DSL:
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) {
return http
// other configuration options
.authorizeHttpRequests(authCustomizer -> authCustomizer
.requestMatchers(HttpMethod.POST, "/api/subscriptions").permitAll()
.requestMatchers(
"/api/register", "/api/register", "/api/authenticate"
).permitAll()
.requestMatchers("/api/logs/**").hasAuthority(AuthoritiesConstants.ADMIN)
.requestMatchers("/api/**").authenticated()
)
.build();
}
}
Note
Security rules are being evaluated from top to bottom, the first matching rule would be applied. Make sure that rules are declared in the proper order.
Apply deny-by-default policy. While an application evolves, new paths get introduced, and there's a possibility that during these changes your colleagues might forget to update security restrictions and some end point would appear to be unprotected. To avoid that in each SecurityFilterChain
(we can declare more than one filter chain, to configure different parts of the system and specify their order) as the last constraint you can introduce a matcher that encompasses all unspecified URLs governed by this SecurityFilterChain
like "/foo/**"
(i.e. all paths under /foo) applying either authenticated()
or denyAll()
. And SecurityFilterChain
which would be considered as the last one to handle the request should cut out request to all unspecified URLs from the root with requestMatchers("/**").denyAll()
. That configuration would prevent both unauthenticated access and disclosing what paths are valid in your system. If a newly introduced endpoint is not accessible (for instance it should be permitted for non-authorize request) would be obvious for your colleague immediately during development process. It's way safer to specify what should be open and keep everything else closed by default.
Spring 5.7 and Earlier
As I've said at beginning even with an Spring version antMatchers()
because by using them you can end up with intoducing Broken access control valnarability.
Here's one more related link: Deprecate trailing slash match.
Instead consider applying either mvcMathcers()
which as it name suggests uses Spring MVC matching rules, or regex-based regexMatchers()
. Both mvcMathcers()
and regexMatchers()
have an overloaded version that allows specifing an HTTP-method.
Here how to secure the API from the question using mvcMathcers()
and Spring Security 5.2 Lambda DSL:
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain1(HttpSecurity http) {
return http
// other configuration options
.authorizeRequests(authCustomizer -> authCustomizer
.mvcMatchers(
"/api/register", "/api/register", "/api/authenticate"
).permitAll()
.mvcMatchers("/api/logs/**").hasAuthority(AuthoritiesConstants.ADMIN)
.mvcMatchers(HttpMethod.POST, "/api/subscriptions").permitAll()
.mvcMatchers("/api/**").authenticated()
.regexMatchers("/**").denyAll()
)
.build();
}
}