I am using Spring-security in a Spring-boot appplication. By default, all methods are restricted to authenticated users thanks to this configuration :
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
Now, I would like to mark some urls as public. I could do it with ant matchers but I would prefer to be able to directly mark the relevant methods with an annotation.
I saw that some #PreAuthorize annotation exist but I could have it work only if I remove those lines from the configuration :
.authorizeRequests()
.anyRequest().authenticated()
This forces me to annotate manually every method that I want to secure with this annotation :
#PreAuthorize("isAuthenticated()")
This is highly dangerous because every forgotten url will be publicly accessible by default. Is there a way to make every url accessible to authenticated users by default and open some urls with
#PreAuthorize("permitAll()")
Also, I saw in another post that the OP was answered :
But it is really a bad idea to use pre-post annotation on a controller, for what could be done directly in HttpSecurity configuration. It forced you to use proxyTargetClass = true.
What is wrong with that? (Also, I did not need to use proxyTargetClass = true)
Related
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(...)
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).
I've set up some security filters in my Spring Boot application and I have defined specific URL patterns I want
#Bean
public FilterRegistrationBean<CustomAuthenticationFilter> authenticationFilter(){
FilterRegistrationBean<CustomAuthenticationFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new CustomAuthenticationFilter());
registrationBean.addUrlPatterns("/data/*", "/record/*","/records/*","/storage/*","/query/*");
return registrationBean;
}
I'm adding the filter into the WebConfig like so
#Override
public final void configure(HttpSecurity http) throws Exception {
http.addFilterAfter(new CustomAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.csrf() .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.antMatcher(ApplicationInfoService.API_PATH + "/info")
.authorizeRequests().anyRequest().permitAll();
}
When I make a request to something like the following;
http://localhost:8080/api/record/v1/record/search?offset=0&count=0
the filter isn't activated. However, I did notice the url in the antMatcher did activiate the filter, so maybe there's something there.
Either way, I'm still trying to grasp how much of the api path the addUrlPattern needs? Where or what else do I need to add beside adding addFilter<Before|After> in the WebSecurityConfig class.
I have taken a look at a whole lot of examples and questions in SO, but none seem to have helped me. Hoping someone can help me understand what else I could be missing.
Javadoc of .antMatcher(...) says
Allows configuring the HttpSecurity to only be invoked when matching the provided ant pattern.
This means that your security is only ever applied to /info requests. All others bypass security.
The code you wrote is the same as:
http.antMatcher(ApplicationInfoService.API_PATH + "/info");
http.addFilterAfter(new CustomAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
http.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
http.authorizeRequests()
.anyRequest().permitAll();
Note that the following are two entirely different things:
http.antMatcher("/foo/**");
http.authorizeRequests()
.antMatchers("/foo").permitAll()
.anyRequest().hasRole("ADMIN");
Without the first, security processes all requests. With it, only requests with path /foo are processed by the security module, while all other requests entirely bypasses security.
The second specifies that requests with path /foo are permitted, and that all other requests requires a user with role ADMIN.
I am new to spring security and was checking how to authorize requests to URLs in my application.
According to the documentation here, we add authorization as follow:
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/resources/**", "/signup", "/about").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")
.anyRequest().authenticated()
.and()
// ...
.formLogin();
}
As this method worked fine for me, I was wondering if there's another dynamic way to specify this configuration. By using some sort of annotations for our REST controllers for example?
I have a solution in mind that would be really practical, but I wanted to make sure that there's no other way to do this before starting to develop my own code.
Thank you for your help.
Yes there is an annotations as #Secured/#PreAuthorize/#PostAuthorize. this annotations are preferred way for applying method-level security, and supports Spring Expression Language out of the box, and provide expression-based access control.
for e.g
#PreAuthorize("hasRole('ADMIN')")
public String yourControllerMethod() {
return response;
}
for detail check here.
The only other way is to use the #Secured/#PreAuthorize/#PostAuthorize annotations. But you must put them on all webservices you want to secure.
Usually, when I build a webservices application, I like to authorize all requests on the WebSecurityConfigurerAdapter, and then secure requests one by one with these annotations.
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.