Spring Boot Security configuring HttpSecurity - java

To authorize the requests we override the configure(HttpSecurity) method where we mention for accessing API we want which role. But the APIs that we don't mention can be accessible without login. Why is this behaviour?
I didn't write permitAll() for the APIs why is this default behaviour?
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/admin").hasRole("ADMIN")
.antMatchers("/user").hasAnyRole("ADMIN", "USER")
.antMatchers("/").hasRole("USER")
.and()
.formLogin();
}
}
In this code admin API can be accessed by admin role, user API can be accessed by admin as well as user role, / API can be accessed by user role and there is one more API /student which I didn't mention and I can access that without login.
Question is how even I didn't write permitAll() method for student API.

Change this line: .antMatchers("/").hasRole("USER") to this: .anyRequest().hasRole("USER").

Only configured URLs are checked. If you doesn't configure an URL, it will not be checked at all.
Therefore, you should add a configuration, that checks all other URLs, see Spring Security Reference:
11.2. Authorize HttpServletRequest with FilterSecurityInterceptor
[...]
protected void configure(HttpSecurity http) throws Exception {
http
// ...
.authorizeRequests(authorize -> authorize 1
.mvcMatchers("/resources/**", "/signup", "/about").permitAll() 2
.mvcMatchers("/admin/**").hasRole("ADMIN") 3
.mvcMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')") 4
.anyRequest().denyAll() 5
);
}
1 There are multiple authorization rules specified.
Each rule is considered in the order they were declared.
2 We specified multiple URL patterns that any user can access.
Specifically, any user can access a request if the URL starts with "/resources/", equals "/signup", or equals "/about".
3 Any URL that starts with "/admin/" will be restricted to users who have the role "ROLE_ADMIN".
You will notice that since we are invoking the hasRole method we do not need to specify the "ROLE_" prefix.
4 Any URL that starts with "/db/" requires the user to have both "ROLE_ADMIN" and "ROLE_DBA".
You will notice that since we are using the hasRole expression we do not need to specify the "ROLE_" prefix.
5 Any URL that has not already been matched on is denied access.
This is a good strategy if you do not want to accidentally forget to update your authorization rules.

To protect the API which is not configured in configure method , have to add line .anyRequest().denyAll()
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/admin").hasRole("ADMIN")
.antMatchers("/user").hasAnyRole("ADMIN", "USER")
.antMatchers("/").hasRole("USER")
.anyRequest().denyAll()
.and()
.formLogin();
}
}
For every request to be protected without configuring it in configure method
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorize -> authorize
.anyRequest().authenticated()
);
}

Related

Spring security default form login page failing when antmatcher defined

I am having an issue with Spring 5 form based authentication. This is only an example at this point as I am trying to isolate the issue I'm having. My plan is to include this configuration in a larger project with multiple configurations. The below code (which can be found anywhere) works as expected:
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
auth.inMemoryAuthentication()
.passwordEncoder(passwordEncoder)
.withUser("user").password(passwordEncoder.encode("password")).roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.and().csrf().disable();
}
}
and following code does not work after adding antmatcher:
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
auth.inMemoryAuthentication()
.passwordEncoder(passwordEncoder)
.withUser("user").password(passwordEncoder.encode("password")).roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception
{
http.antMatcher("/test/**")
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.and().csrf().disable();
}
}
Notice that I've only added an antmatcher to this config. I only want the config to apply to /test/**. And, in this case, I'm not doing anything custom with regards to the form login and would like to get Spring default form. Of course in a real application I would not use the default but this is only for example purposes. My first config displays the default internal Spring form correctly when I GET the following URL: http://localhost:8080/webapp/test/anything. And I can authenticate successfully. For the second configuration I get 404 errors when issuing the same URL.
Spring security does in fact try to redirect to: http://localhost:8080/webapp/login however, the url does not exist. I did try the following change:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/test/**").authorizeRequests().anyRequest().authenticated()
.and().formLogin().permitAll().loginPage("/test/login").loginProcessingUrl("/test/login")
.and().logout().logoutUrl("/test/logout").invalidateHttpSession(true).logoutSuccessUrl("/");
}
This did not work either. I made several attempts, but have not been successful. Is it possible that the default form behavior is disabled when an antmatcher is defined?
This is ideal solution for your requirement
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.requestMatchers().antMatchers("/test/**", "/customLoginPage", "/logout").and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/customLoginPage") //If you don't configure default is "/login"
.usernameParameter("userName").passwordParameter("password") //In case of custom form parameters
.defaultSuccessUrl("/app/user/dashboard") //If you don't configure default is "/"
.failureUrl("/login?error=true") //If you don't configure default is "/login?error"
.and().csrf().disable();
}
Let me explain Some cases how spring security deals with
Case 1:
http.authorizeRequests()
is equals to
http.antMatcher("/**").authorizeRequests()
A proxy filter will be defined and url-pattern for that filter will be "/**". With this type of configuration there will be no problem as it is a wild card. But in some cases we don't want to define wild card "/**" then we should configure requestMatchers correctly otherwise we will end up in lot of unguessable problems.
Case 2:
http.antMatcher("/test/**").authorizeRequests()
Here proxy filter will be added with URL pattern "/test/**" and requests with /login and /logout can't pass through the added filter. To overcome this .requestMatchers() should be used as given below
http
.requestMatchers().antMatchers("/test/**", "/customLoginPage", "/logout")
.and().authorizeRequests()
This means filter with filter mapping as given below
<filter-mapping>
<filter-name>/test/**</filter-name>
<url-pattern>/customLoginPage</url-pattern>
<url-pattern>/logout</url-pattern>
</filter-mapping>
You can try out some of basic examples(Working) from my github repository
After many tries, the following is working for me. Only showing the configure method:
#Override
protected void configure(HttpSecurity http) throws Exception
{
http.antMatcher("/test/**")
.authorizeRequests()
.antMatchers("/test/login").permitAll()
.antMatchers("/test/logout").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.jsp").permitAll()
.loginProcessingUrl("/test/login").permitAll()
.failureUrl("/login.jsp?error=yes")
.and()
.logout().permitAll().logoutUrl("/test/logout").permitAll()
.and().csrf().disable();
}
I did need to create my own login form (login.jsp), however the processing of the login form (the POST) is handled by Spring default processing. Really, that was the important piece. Another key point to make is that the login form POST needs to point to /webapp/test/login. I suppose that the Spring configuration needs these additional "tips" in order to trigger the default form processing in cases where the antMatcher call must be present.

Spring Security ignore path filter still executed when Authorization header present

I have a fully working spring security process set up with some paths requiring authentication (via a token) and others I want to keep open and accessible without token. The issue I am running into is that when a request comes in to one of those open paths without the Authorization header, then the filters are ignored and the proper response is generated. However, when the Authorization header is present, even though on the ignored path, the request goes through the entire security filter chain when the ideal procedure would be to entirely skip the filter chain.
Below is my configuration.
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(DEFAULT_IGNORE_REQUESTS);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
authenticationTokenHeaderFilter.setAuthenticationManager(authenticationManager);
http.authorizeRequests()
.antMatchers("/example/**")
.authenticated()
.and()
.exceptionHandling()
.accessDeniedHandler((request, response, accessDeniedException) -> {
response.sendError(HttpServletResponse.SC_FORBIDDEN, accessDeniedException.getMessage());
})
.authenticationEntryPoint(new HttpAuthenticationEntryPoint())
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.cors()
.and()
.csrf().disable()
.addFilter(authenticationTokenHeaderFilter)
.addFilterBefore(new ExceptionTranslationFilter(
new Http403ForbiddenEntryPoint()),
authenticationTokenHeaderFilter.getClass()
);
}
public class AuthenticationTokenHeaderFilter extends AbstractPreAuthenticatedProcessingFilter {
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest httpServletRequest) {
return httpServletRequest.getHeader(HttpHeaders.AUTHORIZATION);
}
#Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest httpServletRequest) {
return "N/A";
}
#Override
#Autowired
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
super.setAuthenticationManager(authenticationManager);
}
}
I have additionally tried putting the ignored paths to the HttpSecurity with permitAll() with no success.
Clarification
String[] DEFAULT_IGNORE_REQUESTS = new String[]{ "/actuator" };
In what is described above, any requests going to /example/** should go through the security chain and through my filter to make sure the user is authenticated. Any requests going to /actuator should not go through the security filter chain. The /example/** works correctly and as expected. The /actuator however does not.
When I make a request without the Authorization header, the security chain is not invoked.
When I make a request with the Authorization header present, the security chain is invoked and the Authorization value (token) is verified. In the event that the token is invalid, a custom exception gets thrown inside the filter. Even though the error gets thrown, I get the expected response from /actuator with a 200. The thrown error in this case however gets logged and a stack trace gets generated, which I do not want as it's not an error in that case.
In Spring Boot, any #Bean of type Filter gets added as a servlet filter. What's most likely happening is that your filter is being added as a filter entirely separate from the filter chain.
Instead of declaring your filter as a #Bean, you could initialize AuthenticationTokenHeaderFilter in your WebSecurityConfigurerAdapter and set the AuthenticationManager directly (which you already do anyways). So you can remove the #Autowired annotation in the filter.

Spring security application of antMatcher() vs. antMatchers()

Just want to see whether I'm interpreting the answer to this question the right way.
If we only need to secure one path like this:
http.antMatcher("/api/**").authorizeRequests()....
Then use antMatcher().
If we need to secure multiple URL paths like this:
http
.authorizeRequests()
.antMatchers("/high_level_url_A/sub_level_1").hasRole('USER')
.antMatchers("/high_level_url_A/sub_level_2").hasRole('USER2')
...
Then use antMatchers().
There are two answers in this question, but the example provided in each of them contradicts example given in the other. The first answer says that the author does not need antMatcher() and the second says to always start with `antMatcher() IIUC.
HttpSecurity.antMatcher() changes the default request matcher for the HttpSecurity instance to an AntPathRequestMatcher from AnyRequestMatcher. ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry.antMatchers() is used for applying authorization rules to a subset of endpoints associated with the current HttpSecurity instance.
Example code:
http
.antMatcher("/api/**")
.httpBasic()
.disable()
.authorizeRequests()
.antMatchers("/api/user/**", "/api/ticket/**", "/index")
.hasRole("USER");
In the example above, basic authorization is disabled for all endpoints matching /api/**. Additionally, endpoints matching /api/user/** or /api/ticket/** will require the request's Authentication to contain ROLE_USER. However, when a user attempts to access /index, they will be met with a basic auth prompt. Upon entering credentials, the user will be granted access to the endpoint regardless of whether or not the request's Authentication contains ROLE_USER. This is because .antMatcher("/api/**") is limiting the scope of the entire HttpSecurity instance to that specific AntMatcher.
The example below would ensure that the HttpSecurity's scope includes the three previous AntMatchers and nothing else:
http
.requestMatchers()
.antMatchers("/api/user/**", "/api/ticket/**", "/index")
.and()
.httpBasic()
.disable()
.authorizeRequests()
.any()
.hasRole("USER");
EDIT
If you use #hasRole(), then your role should not start with "ROLE_" as this is automatically inserted.
antMatcher() allows configuring the HttpSecurity to only be invoked when matching the provided ant pattern.
If more advanced configuration is necessary, consider using requestMatchers() or requestMatcher(RequestMatcher).
Invoking antMatcher() will override previous invocations of antMatcher(), mvcMatcher(), requestMatchers(), regexMatcher(), and requestMatcher()
See the example bellow for using requestMatchers
#Configuration
#EnableWebSecurity
public class RequestMatchersSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers((requestMatchers) ->
requestMatchers
.antMatchers("/api/**")
.antMatchers("/oauth/**")
)
.authorizeRequests((authorizeRequests) ->
authorizeRequests
.antMatchers("/**").hasRole("USER")
)
.httpBasic(withDefaults());
}
}
The configuration below is also the same as the above configuration.
#Configuration
#EnableWebSecurity
public class RequestMatchersSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers((requestMatchers) ->
requestMatchers
.antMatchers("/api/**")
)
.requestMatchers((requestMatchers) ->
requestMatchers
.antMatchers("/oauth/**")
)
.authorizeRequests((authorizeRequests) ->
authorizeRequests
.antMatchers("/**").hasRole("USER")
)
.httpBasic(withDefaults());
}
}

spring boot ldap group and restricted endpoints

I want to restrict certain rest endpoints to be only for LDAP users in a certain group.
I followed the guide https://spring.io/guides/gs/authenticating-ldap/ to setup LDAP authentication which is working perfectly. So how do I restrict certain rest endpoints?
I tried
#PreAuthorize("hasRole('developers')")
#RequestMapping("/foo")
public String foo(HttpServletRequest request) {
return "Welcome to FOO " + request.getRemoteUser();
}
but it still lets users not in the developers group access that endpoint
You can modify your WebSecurityConfigurerAdapter configuration to something like:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().fullyAuthenticated()
.and()
.antMatchers("/foo").hasRole("developers")
.and()
.formLogin();
}
I am not exactly sure of the syntax and if that first rule will override your second rule, but it will be similar to that.
Or, you can try configuring security on a method by method basis like this sample.
#EnableGlobalMethodSecurity(securedEnabled=true) needed to be added to the webSecurityConfig. Once I did that I was able to use #Secured("ROLE_DEVELOPERS") and that method was then restricted to that role.

Spring Security 3.1.3 request querystring stripped

I am securing my application using Spring Security 3.1.3 and I have a requirement to allow users to login via a link in a third-party application.
However, the link in the third-party application will redirect to a specific resource and not to the login page, where the resource that the user wishes
to access will be defined as a querystring parameter. So, for example, the link would be of the form :
//server.com/app/build/panel.jsp?resourceid='blah'
When a user clicks this link they should be taken to the login page defined in my Spring Security configuration and if authenticated then should be redirected
to the original link including the querystring parameter. The querystring parameter has no influence on how the user should be authenticated it's
merely an id of resource.
Now, this all works fine apart from the querystring, which gets stripped by Spring Security before it enters the request processing flow.
This is shown in the debug output from Spring Security;
org.springframework.security.web.savedrequest.HttpSessionRequestCache: DefaultSavedRequest added to Session:
DefaultSavedRequest[http://server.com:8080/app/build/panel.jsp]
ie, the querystring is not saved and resourceid='blah' has been removed.
Note, I'm currently using Ant matching. I have no need to actually match against the querystring.
In earlier versions of Spring Security, it seemed like you could influence this behaviour by using a BeanPostProcessor as per this post,
Spring Security - Url with request parameters rules ignored. But the method
DefaultFilterInvocationSecurityMetadataSource.setStripQueryStringFromUrls() has been removed from Spring Security 3.1.3.
How do I configure Spring Security to not strip the querystring from the original request? So that when the user is redirected after the login to
the original URL the querystring parameter will be retained?
Many Thanks
Howard
U can get it from SuccessHandler
SecurityConfiguration class
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
SuccessHandler getSuccessHandler;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/dashboard/**",
"/feedback/**"
).access("hasRole('ROLE_SYSTEM_ADMIN') or hasRole('ROLE_COMPANY_ADMIN')")
.and().formLogin().loginPage("/login").successHandler(getSuccessHandler)
.loginProcessingUrl("/login").usernameParameter("ssoId").passwordParameter("password")
.and().csrf()
.and().exceptionHandling().accessDeniedPage("/Access_Denied")
.and()
.sessionManagement().invalidSessionUrl("/login").maximumSessions(1).expiredUrl("/login").and().sessionAuthenticationErrorUrl("/login").sessionFixation().migrateSession()
.sessionCreationPolicy(SessionCreationPolicy.ALWAYS); //always, IF_REQUIRED,never ,stateless
http.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login")
.invalidateHttpSession(true)
.permitAll();
}
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/static/**")
.antMatchers("/images/**");
}
}
SuccessHandler class
#Component
public class SuccessHandler implements AuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
HttpSession session = request.getSession();
response.sendRedirect(request.getContextPath() + "/dashboard/index");
}
}
Is basically the success handler.
You can take a look at this example:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login*")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.successHandler(new RefererAuthenticationSuccessHandler());
}
More info about it : http://www.baeldung.com/spring-security-redirect-login
For others on a similar issue, refer the link:
https://docs.spring.io/spring-security/site/docs/3.1.x/reference/springsecurity-single.html
Extract:There is a danger that when an application is deployed in a container which does not strip path parameters from these values, an attacker could add them to the requested URL in order to cause a pattern match to succeed or fail unexpectedly.
However this stripping is meant to firmly protect the pattern matching for login. It doesnt means the query parameters are not available from the HTTP request, they should be.

Categories