How to have different filters for different request paths on spring security? - spring

I have two filters and I want to apply one on "/relatorios/**" and another for the rest.
How to do it?
Here is my (not working) version:
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.antMatcher("/relatorios/**")
.csrf()
.disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilter(relatoriosFilter)
.addFilterBefore(new ExceptionTranslationFilter(new Http403ForbiddenEntryPoint()),
relatoriosFilter.getClass())
.authorizeRequests()
.and()
.antMatcher("/**")
.csrf()
.disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilter(authHeaderTokenFilter)
.addFilterBefore(new ExceptionTranslationFilter(new Http403ForbiddenEntryPoint()),
authHeaderTokenFilter.getClass())
.authorizeRequests()
.anyRequest()
.authenticated();
}
UPDATE:
With this version, when I call any request path, both filters are called. I want "/relatorios/**" to call one filter and everything else to call another filter.

Here are two ways to define the URL paths that filters are applied on.
Firstly, you should be aware that creating a bean of a filter class implementing the Filter interface, the filter is then automatically registered to all endpoints. Since you are trying to achieve different filters for different paths, remove this if you are doing so in your code.
Now you may register your filters in one of the two following methods.
Method 1 - Register Filters with FilterRegistrationBean
In this method, your defined security chain should not define your customer filters, so remove both the addFilter methods from there. You will be setting the paths not via the chain, but rather via registration beans.
#Configuration
public class FilterConfiguration {
#Bean
public FilterRegistrationBean<RelatoriosFilter> relatoriosFilter(){
FilterRegistrationBean<RelatoriosFilter> registrationBean
= new FilterRegistrationBean<>();
registrationBean.setFilter(new RelatoriosFilter());
registrationBean.addUrlPattern("/relatorios/*");
registrationBean.setOrder(ORDERED.HIGHEST_PRECEDENCE);
return registrationBean;
}
#Bean
public FilterRegistrationBean<AuthHeaderTokenFilter> filter2(){
FilterRegistrationBean<AuthHeaderTokenFilter> registrationBean
= new FilterRegistrationBean<>();
registrationBean.setFilter(new AuthHeaderTokenFilter());
registrationBean.addUrlPattern("/*");
registrationBean.setOrder(ORDERED.HIGHEST_PRECEDENCE);
return registrationBean;
}
}
In this manner, you should also control the order of filters by calling setOrder method and giving a lower number for a higher precedence in the filtering chain. The necessary order will depend on the version of Spring you are using and in which part of the chain you are interested to inject in the chain. In my example, it will be the first filter.
Method 2 - Splitting the WebSecurityConfigurerAdapter configurations
A WebSecurityConfigurerAdapter chain cannot define two different filter configurations by path matching. This is one of various limitations of this chain.
This can be solved easily by creating an additional configuration, so that each configuration matches a different path and applies a different filter. You will end up with two configurations.
Configuration 1
#Configuration
#Order(1)
public class RelatoriosSecurity extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.authorizeRequests()
.and()
.antMatcher("/relatorios/**")
.addFilterBefore(new RelatoriosFilter(), ChannelProcessingFilter.class)
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests().anyRequest().authenticated();
}
}
Configuration 2
#Configuration
#Order(2)
public class GeneralSecurity extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.antMatcher("/**")
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(new AuthHeaderTokenFilter(), ChannelProcessingFilter.class)
.authorizeRequests().anyRequest().authenticated();
}
}
In this method, #Order annotation defines the order of execution of the chains, so much like your original solution, the RelatoriosSecurity chain will be executed before GeneralSecurity chain.
Also, the addFilterBefore will define before which filter the supplied filter will run. The class of the of the filter to come before will depend on your Spring version, but in mine ChannelProcessingFilter is the first, therefore our supplied filter will be executed first, before ChannelProcessingFilter.

Related

Spring security - create 2 filter chains with specific matchers

I'm in the process of implementing ADFS support to an existing spring project.
Since we already have our own JWT authentication, which we want to work in parallel to ADFS authentication, I want to implement a new filter chain that will handle only certain API request paths.
By this I mean I want to create:
ADFS filter chain that will handle all the /adfs/saml/** API calls
Leave the default filter chain that will handle all the rest API calls
I'm using the ADFS spring security lib that defines the filter chain like this:
public abstract class SAMLWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
//some code
protected final HttpSecurity samlizedConfig(final HttpSecurity http) throws Exception {
http.httpBasic().authenticationEntryPoint(samlEntryPoint())
.and()
.csrf().ignoringAntMatchers("/saml/**")
.and()
.authorizeRequests().antMatchers("/saml/**").permitAll()
.and()
.addFilterBefore(metadataGeneratorFilter(), ChannelProcessingFilter.class)
.addFilterAfter(filterChainProxy(), BasicAuthenticationFilter.class);
// store CSRF token in cookie
if (samlConfigBean().getStoreCsrfTokenInCookie()) {
http.csrf()
.csrfTokenRepository(csrfTokenRepository())
.and()
.addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class);
}
return http;
}
}
And I extend this class:
#EnableWebSecurity
#Configuration
#Order(15)
#RequiredArgsConstructor
public class ADFSSecurityConfiguration extends SAMLWebSecurityConfigurerAdapter {
#Override
protected void configure(final HttpSecurity http) throws Exception {
samlizedConfig(http)
.authorizeRequests()
.antMatchers("/adfs")
.authenticated();
}
}
But when debugging I see that this new filter chain is set to match "any" request.
So I'm probably setting the matchers wrong.
Actually, after reading the official docs the answer was a simple one:
(see "Creating and Customizing Filter Chains" section)
#Override
protected void configure(final HttpSecurity http) throws Exception {
samlizedConfig(http)
.antMatcher("/adfs/**");
}
It should not be put after .authorizeRequests() but strait on the first matcher.

How to make a custom UsernamePasswordAuthenticationFilter register at an endpoint other than /login?

I've been following a tutorial to implementing JWT authentication in Spring Boot but am trying to adapt it to a case where I have two WebSecurityConfigurerAdapter classes, one for my API (/api/** endpoints) and one for my web front-end (all other endpoints). In the tutorial, a JWTAuthenticationFilter is created as a subclass of UsernamePasswordAuthenticationFilter and added to the chain. According to the author, this filter will automatically register itself with the "/login" endpoint, but I want it to point somewhere different, such as "/api/login" because I'm using this authentication method for my API only.
Here's the security configuration code for both the API and front-end (with some abbrevation):
#EnableWebSecurity
public class MultipleSecurityConfigurations {
#Configuration
#Order(1)
public static class APISecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/**")
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()));
}
}
#Configuration
public static class FrontEndSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.loginPage("/login").permitAll()
.defaultSuccessUrl("/")
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/?logout")
.and()
.authorizeRequests()
.mvcMatchers("/").permitAll()
.mvcMatchers("/home").authenticated()
.anyRequest().denyAll()
;
}
}
}
The question is: how can I define an endpoint such as "/api/login" as the endpoint for my custom JWTAuthenticationFilter?
Or, do I need to change the filter to not be a subclass of UsernamePasswordAuthenticationFilter and if so, how would I configure that?
EDIT: Something I've tried:
I guessed that the /api/login endpoint needed to be .permitAll() and I tried using formLogin().loginProcessingUrl(), even though it's not really a form login - it's a JSON login. This doesn't work. When i POST to /api/login I end up getting redirected to the HTML login form as if I were not logged in. Moreover, my Spring boot app throws a weird exception:
org.springframework.security.web.firewall.RequestRejectedException: The request was rejected because the URL contained a potentially malicious String ";"
The configuration I'm trying now:
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/**")
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.formLogin().loginProcessingUrl("/api/login").and()
.authorizeRequests()
.antMatchers("/api/login").permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()));
}
Since JWTAuthenticationFilter is a UsernamePasswordAuthenticationFilter, you could change the login endpoint directly on the filter instance:
JWTAuthenticationFilter customFilter = new JWTAuthenticationFilter(authenticationManager());
customFilter.setFilterProcessesUrl("/api/login");
http.addFilter(customFilter);
This configures JWTAuthenticationFilter to attempt to authenticate POST requests to /api/login.
If you wish also to change the default POST to another method (e.g. GET), you can set the RequiresAuthenticationRequestMatcher instead. For instance:
customFilter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/api/login", "GET"));

spring security: disable multiple sessions on the same user not working

HttpSecurity object configed like this:
http.authorizeRequests().antMatchers("/login","/loginPage","/static/login.html","/","/index","/static/authenticationErr.html","/static/duplicatedUserErr.html").permitAll()
.and()
.httpBasic()
.authenticationEntryPoint(customAuthenticationEntryPoint)
.and()
.addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginProcessingUrl("/login")
.loginPage("/loginPage")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessHandler(customLogoutHandler)
.permitAll()
.and()
.sessionManagement() // not working??
.maximumSessions(1)
.maxSessionsPreventsLogin(true)
.expiredUrl("/static/duplicatedUserErr.html")
;
Here is what I tried: by following the spring security reference at https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#concurrent-sessions and source code trace, I found out the key to determine if this is a duplicate login session is this part of code written in method onAuthentication of class ConcurrentSessionControlAuthenticationStrategy:
final List<SessionInformation> sessions = sessionRegistry.getAllSessions(
authentication.getPrincipal(), false);
then base on the size of this list, compare with the maximumSessions limit deined in HttpSecurity config to check if this is an duplicated user. After debugging, I know every time a user try to login, this line of code will be called, however no matter how many times try to login in my browsers, sessions object always be null, it turns out the principals field defined in SessionRegistryImpl has been a empty map since it is created and never be filled with new elements.
Here is other detail of my config:
AuthenticationProvider: org.springframework.security.authentication.dao.DaoAuthenticationProvider
UserDetailService:
CustomUserDetailsService implements UserDetailsService
UserDetails:
org.springframework.security.core.userdetails.User
AuthenticationProcessingFilter:
CustomAuthenticationFilter extends AbstractAuthenticationProcessingFilter
Updated:
#Bean
public CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
CustomAuthenticationFilter filter = new CustomAuthenticationFilter("/login");
filter.setAuthenticationManager(this.authenticationManagerBean());
filter.setAuthenticationFailureHandler(failureHandler);
filter.setAuthenticationSuccessHandler(successHandler);
filter.setSessionAuthenticationStrategy(sessionControlAuthenticationStrategy());
return filter;
}
Can someone give me a light of this?
Finally I have figured it out, you have to create beans by following this reference https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#concurrent-sessions, and you have to set CompositeSessionAuthenticationStrategy bean manually to CustomAuthenticationFilter , and then in your ConcurrentSessionControlAuthenticationStrategy bean, set ExceptionIfMaximumExceeded as true so it will throw a SessionAuthenticationException when a duplicate seesion of the same user created.
My code of above description is like this:
#Bean
public CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
CustomAuthenticationFilter filter = new CustomAuthenticationFilter("/login");
filter.setAuthenticationManager(this.authenticationManagerBean());
filter.setAuthenticationFailureHandler(failureHandler);
filter.setAuthenticationSuccessHandler(successHandler);
//If don't set it here, spring will inject a compositeSessionAuthenticationStrategy bean automatically, but looks like it didn't work as expected for me
filter.setSessionAuthenticationStrategy(compositeSessionAuthenticationStrategy());
return filter;
}
#Bean
public ConcurrentSessionControlAuthenticationStrategy sessionControlAuthenticationStrategy() {
ConcurrentSessionControlAuthenticationStrategy csas = new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry());
csas.setExceptionIfMaximumExceeded(true);
return csas;
}

Spring Security: Multiple http elements with Multiple AuthenticationManagers

I am struggling with Java Config for Spring Security. I have multiple entry points but I cannot get the AuthenticationManagers provisioned correctly.
My first configuration file is like this:
#Configuration
#EnableWebSecurity
#Order(100)
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.antMatcher("/service/**")
.addFilterAfter(requestHeaderAuthenticationFilter(), SecurityContextPersistenceFilter.class)
.authorizeRequests()
.antMatchers("/service/**").authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER)
.and()
.csrf().disable();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
auth.authenticationProvider(preAuthenticatedAuthenticationProvider(null));
}
#Bean
public RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter() throws Exception
{
// Takes the value of the specified header as the user principal
RequestHeaderAuthenticationFilter filter = new RequestHeaderAuthenticationFilter();
filter.setPrincipalRequestHeader("SECRET_HEADER");
filter.setAuthenticationManager(authenticationManager());
filter.setExceptionIfHeaderMissing(false);
return filter;
}
This all works correctly. When I set a breakpoint in the RequestHeaderAuthenticationFilter I see an AuthenticationManager with one AuthenticationProvider, and that is the preAuthenticatedAuthenticationProvider (not shown because is just a regular old bean).
I also have a special security chain for admin users and the like:
#Configuration
#Order(101)
public class AdminSecurity extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.authenticationProvider(mainSiteLoginAuthenticationProvider())
.authorizeRequests()
.antMatchers("/public/**").permitAll()
.antMatchers("/admin/**").access("SECRET ADMIN ACCESS EXPRESSION")
.antMatchers("/internal/**").access("SECRET INTERNAL ACCESS EXPRESSION")
.anyRequest().permitAll()
.and()
.formLogin()
.defaultSuccessUrl("/admin/thing")
.loginPage("/login")
.loginProcessingUrl("/do_login")
.defaultSuccessUrl("/admin/thing")
.failureUrl("/login?error=true")
.usernameParameter("username")
.passwordParameter("password")
.and()
.logout()
.and()
.exceptionHandling()
//.authenticationEntryPoint(null) // entry-point-ref="loginEntryPoint"
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) // create-session="ifRequired"
.and()
.csrf().disable();
}
This is now working (after a lot of struggle), but if I put a breakpoint in the UsernamePasswordAuthenticationFilter, I see that this filter has a different AuthenticationManager instance, which is provisioned with the mainSiteLoginAuthenticationProvider as expected. However, it has a parent AuthenticationManager which is provisioned with the default DaoAuthenticationProvider that generates a temporary password in the logs:
Using default security password: 47032daf-813e-4da1-a224-b6014a705805
So my questions are:
How can I get both security configs to use the same AuthenticationManager? I thought that the SecurityConfig, being order 100, would create one, and then AdminConfig, being 101, would just use it. But I have been unable to get them to use the same AuthenticationManager.
Failing that, how can I prevent the AuthenticationManger of AdminConfig from generating a parent that has the default DaoAuthenticationProvider?
I am using Spring Boot 1.5.9.RELEASE, which means Spring Security 4.2.3.RELEASE.

Why does HttpSecurity configuration via DSL not seem to work the same as explicit configuration?

I went through the trouble to write a DSL to configure the HttpSecurity for my custom authentication mechanism, but most of the configuration I apply to it doesn't seem to be in effect when the application runs, while everything works perfectly when I configure it all manually in the webapp.
First, the manual configuration, which results in my EntryPoint firing, authenticationProvider being queried, the filter being added to the chain, and my rememberMeServices being added to that filter. Everything correct.
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/auth/callback").permitAll()
.anyRequest().authenticated()
.and()
.authenticationProvider(authProvider)
.rememberMe()
.rememberMeServices(rememberMeServices)
.and()
.exceptionHandling()
.authenticationEntryPoint(entryPoint)
.and()
.addFilterAfter(filter, UsernamePasswordAuthenticationFilter.class);
/* The following code is basically what gets run when the DSL is in use
http
.apply(new EPIdentityDsl())
// lots of setters called here, removed for clarity
.and()
.authorizeRequests().anyRequest().authenticated();
*/
}
}
However, the code in the DSL looks like this, and when it is used, the authenticationEntryPoint never fires. The rememberMeServices do get configured, and it looks like the filter gets added to the chain correctly, but I just get an error page for a 403 response instead of seeing the entryPoint redirection.
public class EPIdentityDsl extends AbstractHttpConfigurer<EPIdentityDsl, HttpSecurity> {
#Override
public void init(HttpSecurity http) throws Exception {
// any method that adds/removes another configurer
// must be done in the init method
log.debug("dsl init");
http
.exceptionHandling()
.and()
.rememberMe();
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers(filterProcessesUrl).permitAll()
.and()
.authenticationProvider(authProvider)
.exceptionHandling()
.authenticationEntryPoint(entryPoint)
.and()
.rememberMe()
.rememberMeServices(rememberMeServices)
.and()
.addFilterAfter(filter, UsernamePasswordAuthenticationFilter.class);
}
}
Clearly, there's some subtle interaction that I'm missing in the documentation or something, causing my DSL-based configuration of entryPoint to get lost. Any idea why? If I had to guess, it would be that I'm doing something wrong with the way I'm specifying paths, but I can't figure it out.
I had a similar problem. I solved it by moving entryPoint to init

Resources