Spring Security: what do authorizeRequests(), anyRequest() and authenticated() do? - java

In the below code what do the different chained methods do?
protected void configure(HttpSecurity http ) throws Exception {
http.authorizeRequests()
.antMatchers(PUBLIC_URL).permitAll()
.anyRequest().authenticated();
}
NOTE: PUBLIC_URL is an array of strings containing public URLs.

authorizeRequests() Allows restricting access based upon the HttpServletRequest using RequestMatcher implementations.
permitAll() This will allow the public access that is anyone can access endpoint PUBLIC_URL without authentication.
anyRequest().authenticated() will restrict the access for any other endpoint other than PUBLIC_URL, and the user must be authenticated.
We can also configure access based on authorities, can manage the sessions, HTTPS channel and much more. You may find more details from configure(HttpSecurity http).

It means that all requests must be authenticated except those matching PUBLIC_URL

Related

How to secure some URLS while keeping others without authentication?

I have a Spring Boot based application. I want the URL /camunda/app/welcome/default/#!/login to be accessible without any authentication, while the URLs
/camunda/app/welcome/default/#!/welcome,
/camunda/app/welcome/default/#!/dashboard,
/camunda/app/tasklist/**, and
/camunda/app/admin/**
must be secured (i. e. only authenticated users should be able to access them).
To achieve this, I wrote the following configuration:
#Configuration
#EnableWebSecurity
public class MyConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.and()
.authorizeRequests()
.antMatchers("/camunda/app/welcome/default/#!/login").permitAll()
.antMatchers("/camunda/app/welcome/default/#!/welcome",
"/camunda/app/welcome/default/#!/dashboard",
"/camunda/app/tasklist/**",
"/camunda/app/admin/**",
"/oauth2/authorization/**",
"/oauth2/code/myredirecturl")
.authenticated()
.and()
.oauth2Login(...)
.logout()
.logoutRequestMatcher(...)
.logoutSuccessHandler(...);
}
}
However with this configuration unauthenticated users can access URLs that are supposed to be protected (/camunda/app/welcome/default/#!/welcome, /camunda/app/welcome/default/#!/dashboard, /camunda/app/tasklist/**, /camunda/app/admin/**).
What is wrong with my configuration and how can I fix it?
Sadly to say, but that will not work, because there is actually only one url:
/camunda/app/welcome/default/
and parts after '#' symbol are called 'anchors':
#!/welcome,
#!/dashboard,
Anchors are not processed on backend, because they point to some place in html document that was loaded on client side.
https://www.w3docs.com/snippets/html/how-to-create-an-anchor-link-to-jump-to-a-specific-part-of-a-page.html
So you cant solve it by Spring only, there must be some frontend logic.
Also these two masks:
/camunda/app/tasklist/, and
/camunda/app/admin/
could be covered by Spring Boot, because point to different urls, not anchors.
Make sure you use the URL encoding of #, which is %23 when calling the endpoints. Otherwise, the characters after the # will not be considered.
Making a request to /camunda/app/welcome/default/#!/welcome without properly encoding will be interpreted as a request to /camunda/app/welcome/default/. Since that endpoint doesn't require authentication then anyone will be allowed to access it.
Since all endpoints except /camunda/app/welcome/default/#!/login require authentication you condense your HttpSecurity configuration. I'll rewrite it below using the lambda style configuration to make it more readable:
http
// no need to add requestMatchers since you aren't changing the default configuration
.authorizeRequests(authz -> authz
.antMatchers("/camunda/app/welcome/default/#!/login").permitAll()
.anyRequest().authenticated() // any request that does not match the above rule ^ will require an authenticated user
)
.oauth2Login(...)
.logout(...)

Spring Boot REST API - enable / disable CSRF protection by client type (browser / non-browser)?

I have a Spring Boot REST API. Due to a security policy I need to have CSRF protection enabled for endpoints accessed by the browser. However, this API will also be accessed by non-browsers. Is there a way I can create two sets of endpoints, one accessible by browsers only with CSRF enabled and the other accessible by non-browsers only with CSRF disabled?
When you configure your CSRF protection using the DSL, like this http.csrf()... you can tell which requests you want the CSRF protection to be applied by passing a RequestMatcher, like so:
http.csrf(csrf -> csrf.requireCsrfProtectionMatcher(new MyBrowserRequestMatcher()));
And your implementation of RequestMatcher could verify if the HttpServletRequest contains the header X-Requested-With: XMLHttpRequest or check the User-Agent.
Just keep in mind that the headers can be changed and you have no guarantee that the request actually come from a browser or non-browser app.
I think you could have separate URL bases for the browser requests and API requests.
For example, you could have all the endpoints that are to be queried by non-browsers under /api/... and in your SpringBootSecurityConfiguration class and configure(HttpSecurity http) method you could conditionally disable CSRF with http.csrf().disable(); if the pattern matches (great tutorial can be found here)
Edit: here is another answer that might be useful.
As #ferrouskid said, I created two URL one for browsers and other for non-browsers:
In spring security config:
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.csrf().ignoringAntMatchers("/withoutCsrf/**")
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.cors().disable();
//complete your configuration }
In controller:
#Controller
#RequestMapping({"books","withoutCsrf/books"})
public class BookController {}

Adding Spring Boot Keycloak configuration of HTTP requests

Currently in my SecurityConfig.java class file where I define my KeycloakWebSecurityConfigurerAdapter I want to define so that every GET request can be done by two different roles. But only one role can do the other types of HTTP requests (POST, PUT, PATCH etc). How can this be achieved in my code below:
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.authorizeRequests()
.antMatchers(HttpMethod.GET).hasAnyRole("user", "admin")
.anyRequest().hasRole("admin");
}
What happens is that when trying to do POST request I get access denied 403. GET requests works fine. Any ideas?
You should disable csrf on your configure method :
public void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.csrf().disable().authorizeRequests().anyRequest().authenticated();
}
}
You should not use KeycloakWebSecurityConfigurerAdapter nor anything else from Keycloak libs for Spring, it is deprecated.
Instead, you can follow this tutorial which proposes two solutions based on:
spring-boot-starter-oauth2-resource-server which requires quite some Java conf
spring-addons-webmvc-jwt-resource-server which enables to configure most of security from properties (way simpler than preceding)
All tutorials linked here show how to map Keycloak roles to spring-security authorities (and will keep CSRF protection enabled, even for stateless resource-servers).

Multiple rules for the same url?

I've a use case where i need an endpoint
/api/v1/resources
that should be public and private at the same time. What does it mean?
If basic authentication is provided, i would like to perform the authentication as usual, that stores the stuff in SecurityContextHolder.
On the other hand, if there is no authentication provided, i would also like to allow the request.
I tried a bunch of stuff overriding
#Override
protected void configure(HttpSecurity http) throws Exception {
without luck. if i do
http.authorizeRequests()
.antMatchers("/api/v1/resources").permitAll()
and i call the endpoint with basic auth, it obviously ignores and despite the request go ahead, i don't have the auth information that i need to forward to another service.
if i do
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
i get that endpoint working and having the basic auth result stored so i can forward the information, but then i get 401 when calling it without authentication.
Is there a way I can apply multiple rules to the same endpoint? Or something similar to achieve this?
Thanks in advance.
EDIT:
For now, the only way i found to achieve this was adding a custom filter before the whole chain that checks for the Authorization Header, and if there no header, i inject the header in the request as an attibute(i need to wrap the request object to override the getHeader method to search for attributes if the header is missing so i can retrieve that header afterwards).

Security config when using #PreAuthorize

My security config class (which inherits from WebSecurityConfigurerAdapter) has a method like the following.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/restaurant/**").access("hasRole('ROLE_USER')")
.and()
.formLogin();
}
However I'd rather use #PreAuthorize on my controllers instead. If I remove the method everything requires auth. What should my method look like so everything is available and access is only determined by PreAuthorize?
As has been already stated, it is not very common to use method level security to secure controller methods but rather to secure methods with business logic. And even if you need to perform authorization based on request attributes, it should be possible to achieve this with URL based security and web security expressions.
Available expressions are defined by WebSecurityExpressionRoot class, an instance of which is used as the expression root object when evaluation web-access expressions. This object also directly exposed the HttpServletRequest object under the name request so you can invoke the request directly in an expression.
Here you can find more details on when to use URL based security and when method level security.
It is rather uncommon to use #PreAuthorize on controller methods, but there may me use cases, if the decision depends on request parameters ...
If you do not want to do any authorization at the request level, you can simply have :
#Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin();
}
You only declare a form login, and no request security. But do not forget that request security uses less resources than method security.
Instead of .access("hasRole('ROLE_USER')"), try .access("permitAll"). Note that for request mappings that doesn't have a #PreAuthorize, everyone will be given access.

Categories