Multiple rules for the same url? - java

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).

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 Security: what do authorizeRequests(), anyRequest() and authenticated() do?

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

How do I process the request in a controller after successful stateless authentication in Spring Security 5 & Spring Boot 2?

I'm trying to implement the following request flow:
GET request is sent to /api/example with authentication GET parameters
Parameters are parsed in a filter and a generated authentication object is sent for authentication
Provider successfully authenticates the object and returns it to the filter
Filter lets the request through to a controller mapped to /api/example
Controller returns the response
Points 1 to 3 are already implemented and working, the successfulAuthentication method gets called in the filter and there are no issues passing the request to an authentication success handler. I already ran into similar questions (such as this one) but most of them are outdated, use a lot of XML configuration and most importantly, the answers are not applicable to this use case (usually, some sort of cookie and/or token is expected to be passed both ways between the parties or a controller is not used).
The authentication parameters are mandatory and sent with every request, so there is zero need to remember any authentication states outside of the single request scope. The other party sending the GET request also does not need to be informed beyond sending a 401 or returning the expected response.
Unfortunately, I do not know how to achieve point 4 in the list above. Spring Security authentication success handler attempts to redirect by default which is undesirable as it either causes a infinite redirect loop (in case of redirect to /api/example) or invoking an entirely different part of the application (in case of the default /).
HTTP security configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.anonymous().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable();
http
.antMatcher("/api/**")
.addFilterBefore(this.apiAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.anyRequest()
.authenticated();
}
Relevant snippet from the authentication filter (extends AbstractAuthenticationProcessingFilter):
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authenticationResult) throws IOException, ServletException {
SecurityContextHolder.getContext().setAuthentication(authenticationResult);
if (this.eventPublisher != null) {
eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authenticationResult, this.getClass()));
}
this.getSuccessHandler().onAuthenticationSuccess(request, response, authenticationResult);
}
There is also a custom ApiAuthenticationProvider, but it's working without any issues. I'd like to avoid any XML configuration and if possible, also redirects and cookies. Also keep in mind that there are multiple mappings to be authenticated with the same configuration (e.g. /api/example, /api/sample...).
Approaches I've already tried:
Redirecting to URL obtained from the request (causes an infinite redirect loop)
Leaving the onAuthenticationSuccess method of the ApiAuthenticationSuccessHandler empty (returns a blank page)
Continuing the filtering in the successfulAuthentication method of the filter (eventually causes a NullPointerException to be thrown)
Extending SimpleUrlAuthenticationSuccessHandler and using setUseReferer(true) in the constructor (redirects to /)
Using setDefaultTargetUrl in the handler when extending SimpleUrlAuthenticationSuccessHandler and passing URL from the request as an argument (causes an infinite redirect loop)

Spring security requests authorization

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.

Spring Security - doesn't access database

I am using spring boot and very new to spring security, but I wanted basic security to my web application. What I did was add on my pom.xml the needed dependencies and added this java class to my project:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/**","/event/**","/ticket/**")
.hasRole("USER")
.and()
.formLogin();
}
}
After running my web application, I run into the login page, where I put user/password and then it goes to my web application. However, the commands don't work. I am pushing some buttons that should send signals to my MySql database, but nothing happens. It's like the front-end isn't connected to the back-end anymore. I am using AngularJS for front-end and a View Controller that navigates between pages. Rest of the application is REST-based. Any idea why this might happen?
Later Edit: Now I understand, the problem that I have is that after authenticating, I get 403 status codes on my end-points. Any idea how I might fix it?
Later Editv2: Looks like I don't get authorized on my POST requests, my GET ones work fine...here are some of my POST end-points: /event/buy_ticket/{id} , /ticket//cancel_ticket/{id}
angular.min.js:101 POST http://localhost:8080/event/buy_ticket/2 403 ()
I even tried to explicitly say it to permit it, but I still get 403...
http.authorizeRequests()
.antMatchers("/**","/event/**","/ticket/**","/event/buy_ticket/2")
.permitAll()
.and()
.formLogin();
Later later later edit:
Disabling csrf worked
Getting 403 Forbidden error codes means that Spring is receiving your requests but choosing to stop processing them. From the Wiki page on HTTP 403:
Authentication was provided, but the authenticated user is not
permitted to perform the requested operation.
If I had to wager, I would say the problem is that you have not specified what resources and endpoints should be accessible and how. If memory serves me right, Spring Security will, by default, lock down everything super tightly so you need to explicitly tell it what to leave open. Below is a sample from my own security configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests() // require authorization
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll() // for the CORS preflight check
.antMatchers("/login", "/api/open/**", "/resources/**").permitAll() // the open API endpoints and resources
.antMatchers("/logout", "/api/secured/**").authenticated(); // lock down these endpoints
...additional configurations...
}
All endpoints that should be freely available are prefaced with "/api/open/" while all endpoints that should be protected by Spring Security are prefaced with "/api/secured/". The exceptions are the logout and login endpoints, since those tie into Spring Security directly.
Here's a great blog post - and the related repo - that shows off how to implement Spring Security that plays nice with AngularJS, even as a Single Page Application, which are notoriously annoying to secure.
Edit: You might be running up against CSRF protection, which is enabled by default in Spring Security - see this post. CSRF will allow HTTP "safe" methods like GET, HEAD, and OPTION but block "unsafe" methods like PUT, POST, DELETE, etc if those methods do not provide the proper token (since no CSRF was configured, those request don't have a token -> blocked). For testing purposes, you can disable it by adding http.csrf().disable() to the configure() method.

Categories