How to use Spring Security .antMatchers() with multiple paths - java

Is there a difference if I use Spring Securitys "antMatchers()"-method like
.antMatchers(
"/",
"/app/**",
"/profiles/**",
"/captcha/**",
c440_START_PAGE,
FAVICON_ICO,
C440_LOGIN,
getCustomerRessourcePath(),
getCustomerWebRessourcePath(),
"/services/userService/**",
"/services/applicationService/**",
"/services/textContentService/**",
"/services/textContentBlockService/**",
"/services/menuItemService/**",
"/services/calculatorService/**"
).permitAll()
or instead
.antMatchers("/").permitAll()
.antMatchers("/app/**").permitAll()
.antMatchers("/profiles/**").permitAll()
.antMatchers("/captcha/**").permitAll()
.antMatchers(c440_START_PAGE).permitAll()
.antMatchers(FAVICON_ICO).permitAll()
.antMatchers(C440_LOGIN).permitAll()
.antMatchers(getCustomerRessourcePath()).permitAll()
.antMatchers(getCustomerWebRessourcePath()).permitAll()
.antMatchers("/services/userService/**").permitAll()
.antMatchers("/services/applicationService/**").permitAll()
.antMatchers("/services/textContentService/**").permitAll()
.antMatchers("/services/textContentBlockService/**").permitAll()
.antMatchers("/services/menuItemService/**").permitAll()
.antMatchers("/services/calculatorService/**").permitAll()
? I'm new to Spring Security and not sure about this...

Both of them are same with your implementation. But the second way provides more flexibility in providing role based authorization etc.
For eg, if you want the role ADMIN to be authorized to access "/" and the role USER to be authorized to access "/app/*", then that would be achieved as below:
.antMatchers("/").hasRole("ADMIN")
.antMatchers("/app/**").hasRole("USER")
Do note that .permitAll() needs to be added only once at the end for every set of patterns with same configuration and not necessarily on every line.
One of the signatures of antMatchers method is
public C antMatchers(java.lang.String... antPatterns)
That means you can pass one or more patterns to the method. More on that can be found on the spring documentation for antMatchers

Related

How should I set my WebSecurityConfig antMatcher to permit all frontend Router URL if I want to serve static React build files through Spring Boot

I export my React build folder to the SpringBoot resources folder and want to serve them statically. I set the antMatcher for my backend endpoints to permit some of urls access,
and rest of urls which are not permitted would need some authentication.
http
.cors().and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/api/v1/registration","/api/v1/login")
.permitAll()
.anyRequest()
.authenticated().and()
.exceptionHandling().and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
The current situation is that, my frontend React Router URL is also blocked by the antMatcher.
e.g.
http://localhost:8080/login being blocked
I can solve it by just manully hard coding the frontend url into antMatchers, like
.antMatchers("/api/v1/registration","/api/v1/login", "/login","/registration"...)
.permitAll()
but I wonder if there is any elegant way to tell backend not to block frontend url? If the website is growing big, adding URL into antMatcher would be time-consuming.
First of all, we have some basic principles to think about:
Your web server could have some files that should be open to all (such as robots.txt and favicon.ico), on top of the static frontend (which may or may not need to be public without respect to this).
When configuring security, it's better to have a baseline which is to deny all requests and then instead whitelist the requests you want to let through, as opposed to permit all except for a few which you deny. It is easier to forget about paths this way and accidentally allow access to things that should be restricted.
Typically with an open static frontend, the sensitive content is provided from api endpoints which serve the dynamic content (this is NOT the case with a dynamic frontend rendered on the server of course).
An approach that I like is to use multiple ordered configurations for different groups. As far as I know, this can't be accomplished so easily with one configuration, and since complexity is also an enemy of security, this is an approach I like:
#EnableWebSecurity(debug = true)
#Order(1)
class OpenConfig: WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http
.requestMatchers()
.antMatchers("/*", "/locales", "/avatars", "robots.txt")
.and()
.authorizeRequests()
.anyRequest()
.permitAll()
}
}
#EnableWebSecurity
#Order(2)
class RestrictedConfig: WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http
.requestMatchers()
.antMatchers("/api/**")
.and()
.authorizeRequests()
// more fine-grained control within matched group of requests here
.anyRequest()
.authenticated()
.and()
.httpBasic()
}
}
#EnableWebSecurity
#Order(3)
class CatchAllConfig: WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http
.authorizeRequests()
.anyRequest()
.denyAll()
}
}
With .requestMatchers() you determine which requests you want to process in this config and after .authorizeRequests(), you can determine more in details what you want to do with the matched requests. If .requestMatchers() (or a similar method) is omitted, all requests are matched by the configuration. The configurations are looked up in order and the first that matches the request in question is used.
You CAN do something similar to the above with one configuration but I think both that it's easier to think about it structurally when dividing it into multiple configurations and also Spring security seems to behave more consistently (once you involve authentication methods, your choices after .authorizeRequests() can sometimes not have the intended results if you try to control paths in widely different ways).
If you want to do it in the most secure way, then you should explicitly whitelist every static asset that you want public access to, but given that you rebuild the frontend every now and then with new hashes attached to filenames, this becomes tedious. The above is a middle way where you assume that all content that is one level under / belongs to the static frontend and should thus be available without authentication (you could work with file suffixes here as a complement if you want). Any folders under the static frontend must be specifically specified. All you have to do now is make sure that any dynamic content is placed in some path which is secured and that you don't have any packages which automatically adds paths to your application which you don't want to be open and that are added at the root (typically Swagger UI and actuators can do this even though the latter often put them under the /actuator/ path.

Spring WebFlux + Security - do we have the "remember-me" feature?

When I develop a non-reactive application and use rememberMe feature in authentication, I just extending WebSecurityConfigurerAdapter class and overriding configure(HttpSecurity httpSecurity) method. In that case, I have a rememberMe() method in a httpSecurity object.
But there is a difference when I use Spring WebFlux. As far as I know, all I have to do is defining a SecurityWebFilterChain bean using the instance of ServerHttpSecurity class, by invoking chain like:
serverHttpSecurity.authorizeExchange()
.anyExchange().authenticated()
.and()
.httpBasic()
.build();
But there is no method to handle rememberMe cookie here like in the HttpSecurity object where I could handle it in that way:
httpSecurity.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic()
.and()
.rememberMe()
Do you know any solution?
Unfortunately, it seems impossible to do this.
There is an old issue on github and unfortunately, it is not known when it is going to be solved.
The comments recommend using a longer session expiration and offloading the sessions into an external data store (i.e. Redis). This way, you can store information in a database instead of in cookies.
They recommend using the Spring Session project.
The comments say that using a longer session expiration and offloading the sessions into an external data store (i.e. Redis). This way, you can store information in a database instead of in cookies.
They say use the Spring Session project.

Spring - Access denied for an Anonymous Authentication

I don't have much expertise with Spring Security and I have a question that could be a bit silly. I've been trying to solve an issue for few days and maybe I misunderstood the way to configure my app. I'd appreciate if anyone can bring some light into this.
I implemented a REST API with Spring and I wanted to add security to it. From the Spring Security docs I read ...
It’s generally considered good security practice to adopt a "deny-by-default" where you explicitly specify what is allowed and disallow everything else.
...
which I agree. So I added that Authentication requirement to my configuration.
However, I'd like to make few of the calls to the API public (from my GUI), so I thought that Anonymous Authentication would work. Again, from the docs I read...
no real conceptual difference between a user who is "anonymously authenticated" and an unauthenticated user
so, all good.
However, when I execute those calls, I get a 403 response (AbstractSecurityInterceptor throws an AccessDeniedException on decide about the Authentication). So, I leave here several questions + my configuration lines in case you know what problems and misunderstandings I have.
Config...
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.cors().and()
.anonymous().and()
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers(HttpMethod.POST, "/users/login").permitAll()
.anyRequest().authenticated().and()
// We filter the api/login requests
.addFilterBefore(new JwtLoginFilter("/users/login", authenticationManager()),
UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new JwtAuthenticationFilter(),
UsernamePasswordAuthenticationFilter.class);
}
Questions...
1) AnonymousAuthenticationToken is still authenticated, right?
2) Is the anonymous() line needed if I don't customize the configuration for Anonymous Authentication? The Anonymous token is there (when I debug) when an authentication is missing
3) Is there anything missing in the configuration to accept those requests?
4) The docs mention the ExceptionTranslatorFilter that should process the AccessDeniedException to the AuthenticationEntryPoint. Do I need to define an AuthenticationEntryPoint? If so, with what purpose?
I've tried to be as much precise as possible. I hope someone can reply.
Thank you very much for your help!
You need to put this into your configuration file:
http.authorizeRequests().antMatchers(HttpMethod.OPTIONS, "/**").permitAll();
Do you have unauthenticated access to the root '/'? I ask this because looks like is what you intend to with the line:
.antMatchers("/").permitAll()
I guess in order to allow unauthenticated request (or anonymous authenticated request, note that for Spring Anonymous and unauthenticated is basically the same) you will need to add the ant match for those paths, something like:
.antMatchers("/public-page").permitAll()

Spring Security - Roles not accessible in HttpServletRequest unless specified via mappableAuthorities

I have a Spring Boot web application that is run on a Tomcat application server and authenticates against a third party IdP.
We currently do role based authentication in a number of our apps using <security-role> and <security-constraint> in a web.xml, and it works properly.
Now, attempting to use Spring Security, I have added the following configuration class:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(final HttpSecurity http) throws Exception {
String[] publicPaths = /*get public paths from properties*/
String[] authorizedPaths = /*get authorized paths from properties*/
String[] authorizedRoles = /*get authorized roles from properties*/
http.csrf().disable()
.jee()
.mappableAuthorities(authorizedRoles)
.and()
.authorizeRequests()
.antMatchers(publicPaths).permitAll()
.antMatchers(authorizedPaths).hasAnyRole(authorizedRoles)
.and()
.logout().disable()
;
}
}
The authorizedRoles in the config above are roles that are authorized to access this application. However, there are other manual checks in the app that just call HttpServletRequest.isUserInRole() to determine if a user has a certain role. Before using Spring Security, that call would return true if that user had that role in the original request. After adding Spring Boot, that call only returns true if the role is one of those passed to .mappableAuthorities() in the example above. The roles that are checked via HttpServletRequest.isUserInRole() are stored in a database and can be updated often, so passing them to .mappableAuthorities() when the application loads is not feasible.
So, to get to the point of my question, it seems like Spring Security is modifying the original HttpServletRequest and taking out any roles that are not contained in the authorizedRoles passed to .mappableAuthorities().
Is there a way to avoid this behavior, or possibly pass some kind of wildcard to .mappableAuthorities(), so that you don't have to know all roles on application startup for them to be accessible via a call to HttpServletRequest.isUserInRole()? I've been looking at Spring Security documentation for hours and haven't found anything.
You can see only mapped roles, because SecurityContextHolderAwareRequestFilter wraps the HttpServletRequest:
A Filter which populates the ServletRequest with a request wrapper which implements the servlet API security methods.
It uses SecurityContextHolderAwareRequestWrapper to implement the servlet API security methods:
A Spring Security-aware HttpServletRequestWrapper, which uses the SecurityContext-defined Authentication object to implement the servlet API security methods:
getUserPrincipal()
isUserInRole(String)
HttpServletRequestWrapper.getRemoteUser().
To customize the roles mapping see J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource:
Implementation of AuthenticationDetailsSource which converts the user's J2EE roles (as obtained by calling HttpServletRequest.isUserInRole(String)) into GrantedAuthoritys and stores these in the authentication details object.
It uses a MappableAttributesRetriever to get the mappable roles:
Interface to be implemented by classes that can retrieve a list of mappable security attribute strings (for example the list of all available J2EE roles in a web or EJB application).
You could write your own MappableAttributesRetriever which loads the mappable roles from your database.
Or you can use WebXmlMappableAttributesRetriever, which retrieves the roles from web.xml:
This MappableAttributesRetriever implementation reads the list of defined J2EE roles from a web.xml file and returns these from getMappableAttributes().

Spring Security - Authorize Request for certain URL & HTTP-Method using HttpSecurity

Is there any way to authorize a POST http-request to a specific URL using org.springframework.security.config.annotation.web.builders.HttpSecurity ?
I'm using HttpSecurity as:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterAfter(new CsrfCookieGeneratorFilter(), CsrfFilter.class)
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.rememberMe()
.rememberMeServices(rememberMeServices)
.key(env.getProperty("jhipster.security.rememberme.key"))
.and()
.formLogin()
.loginProcessingUrl("/api/authentication")
.successHandler(ajaxAuthenticationSuccessHandler)
.failureHandler(ajaxAuthenticationFailureHandler)
.usernameParameter("j_username")
.passwordParameter("j_password")
.permitAll()
.and()
.logout()
.logoutUrl("/api/logout")
.logoutSuccessHandler(ajaxLogoutSuccessHandler)
.deleteCookies("JSESSIONID")
.permitAll()
.and()
.headers()
.frameOptions()
.disable()
.authorizeRequests()
.antMatchers("/api/register").permitAll()
.antMatchers("/api/activate").permitAll()
.antMatchers("/api/authenticate").permitAll()
.antMatchers("/api/logs/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/api/subscriptions").permitAll()
.antMatchers("/api/**").authenticated();
}
I would like to allow only POST requests to the path "/api/subscription".
Take a look here https://github.com/spring-projects/spring-data-examples/tree/master/rest/security which has
http
.httpBasic().and()
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/employees").hasRole("ADMIN")
.antMatchers(HttpMethod.PUT, "/employees/**").hasRole("ADMIN")
.antMatchers(HttpMethod.PATCH, "/employees/**").hasRole("ADMIN");
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).
Spring 6.0 - requestMatchers()
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
requestMatchers( HttpMethod, String ... ) - this method combines the previous two, it allows to specify an HttpMethod and one or more String patterns.
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();
}
}
I know this question is a bit old but I don't believe disabling csrf support is an acceptable answer. I had this same problem but don't feel good able using csrf.disable(). Instead I added the following line at the bottom of the page inside the form tags.
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />

Categories

Resources