I am trying to add a custom filter to a Spring HttpSecurity. This filter must check that the username is in a list provided externaly and injected into the filter as a Set.

No matter where I put the filter, its method attemptAuthentication is never called. Here is the filter code:

import java.io.IOException;
import java.util.Base64;
import java.util.Set;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

public class MyRoleFilter extends AbstractAuthenticationProcessingFilter {

    final Set<String> authorisedUsers;

    public WhoRoleFilter(String url, AuthenticationManager authenticationManager, Set<String> authorisedUsers) {
        super(new AntPathRequestMatcher(url));
        this.authorisedUsers= authorisedUsers;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
        throws AuthenticationException, IOException, ServletException {
        // In BASIC authentication user:password come as Base64 in the Authorization header
        final String authorization = request.getHeader("Authorization");
        final String[] userPasswd = new String(Base64.getDecoder().decode(authorization)).split(":");
        // The docs of AbstractAuthenticationProcessingFilter says it must throw an exception in case authentication fails
        // https://docs.spring.io/spring-security/site/docs/4.2.6.RELEASE/apidocs/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.html#attemptAuthentication-javax.servlet.http.HttpServletRequest-javax.servlet.http.HttpServletResponse-
        if (userPasswd.length!=2)
                throw new BadCredentialsException("Bad Credentials");
        if (authorisedUsers.contains(userPasswd[0])) {
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(userPasswd[0], userPasswd[1]);
            return this.getAuthenticationManager().authenticate(authRequest);
        } else {
            throw new BadCredentialsException("User has not the correct role");
        }
    }

}

And this is how I am trying to add it to HttpSecurity:

@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) {
        http.csrf().disable()
            .authorizeRequests()
            .antMatchers("/disabled")
            .permitAll()
            .anyRequest()
            .authenticated()
            .and()
            .addFilterBefore(new MyRoleFilter("**/path/services/whatever/**", this.authenticationManager() ,myUserNamesSet), BasicAuthenticationFilter.class)
            .httpBasic();
    }

}

I am not certain where during the build chain must the addFilterBefore() go. Moreover, a standard user+password aginst an LDAP server is required additionally to the usernames list filter. The LDAP authentication was alredy in place and in working order.

Update, this is the configureGlobal(AuthenticationManagerBuilder) at SecurityConfig

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
    try {
        auth.ldapAuthentication()
            .userDnPatterns(ConfigurationProvider.get().getProperty(Property.PROP_LDAP_USER_BASE_DN))
            .contextSource(contextSource())
            .passwordCompare()
            .passwordAttribute(ConfigurationProvider.get().getProperty(Property.PROP_LDAP_PASSWORD_ATTRIBUTE));
    } catch (Exception exc) {
        LOG.error(exc.getMessage(), exc);
    }
}

1 Answers

0
Serg M Ten On

Answering my own question after research. This is how I had to implement SecurityConfig and my authentication processing filter in order to add a custom role check on to of Basic authentication using LDAP for storing user credentials.

@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private String Set<String> myUsers;

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) {
        myUsers = new HashSet<>(Arrays.asList("John","James","Jeremy"));
        try {
            auth.ldapAuthentication()
                    .userDnPatterns("ldap.user.base.dn")
                    .contextSource(contextSource())
                    .passwordCompare()
                    .passwordAttribute("ldap.password.attribute");
            auth.authenticationEventPublisher(defaultAuthenticationEventPublisher());
        } catch (Exception exc) {
            // LOG.error(exc.getMessage(), exc);
        }
    }

    @Override
    @Autowired
    public void setAuthenticationConfiguration(AuthenticationConfiguration authenticationConfiguration) {
        super.setAuthenticationConfiguration(authenticationConfiguration);
    }

    @Override
    @Autowired
    public void setObjectPostProcessor(ObjectPostProcessor<Object> objectPostProcessor) {
        super.setObjectPostProcessor(objectPostProcessor);
    }

    @Override
    protected void configure(HttpSecurity http) {
        try {
            http.csrf().disable()
            .authorizeRequests()
            .antMatchers("/disabled")
            .permitAll()
            .anyRequest()
            .authenticated()
            .and()
            .addFilterAfter(new MyFilter("/services/my/path",  this.authenticationManager(), myUsers), BasicAuthenticationFilter.class)
            .httpBasic();

            http.logout().deleteCookies("JSESSIONID")
            .clearAuthentication(true)
            .invalidateHttpSession(true);
        } catch (Exception exc) {
            // LOG.error(exc.getMessage(), exc);
        }
    }
}

Note that I had to override successfulAuthentication from AbstractAuthenticationProcessingFilter as it was always trying to perform a redirect looking for a request header containing the target URL.

public class MyFilter extends AbstractAuthenticationProcessingFilter {

    final Set<String> authorisedUsers;

    public MyFilter(String url, AuthenticationManager authenticationManager, Set<String> authorisedUsers) {
        super(new AntPathRequestMatcher(url));
        this.authorisedUsers = authorisedUsers;
        this.setAuthenticationManager(authenticationManager);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
        throws AuthenticationException, IOException, ServletException {
        // In BASIC authentication user:password come as Base64 in the Authorization header
        final String authorization = request.getHeader("Authorization");
        String authorizationBasic = authorization; 
        if (authorization.startsWith("Basic")) {
            authorizationBasic = authorization.split(" ")[1];
        }
        final String[] userPasswd = new String(Base64.getDecoder().decode(authorizationBasic)).split(":");
        // The docs of AbstractAuthenticationProcessingFilter says it must throw an exception in case authentication fails
        // https://docs.spring.io/spring-security/site/docs/4.2.6.RELEASE/apidocs/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.html#attemptAuthentication-javax.servlet.http.HttpServletRequest-javax.servlet.http.HttpServletResponse-
        if (userPasswd==null || userPasswd.length!=2)
            throw new BadCredentialsException("Bad Credentials");
        if (authorisedUsers.contains(userPasswd[0])) {
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(userPasswd[0], userPasswd[1]);
            return getAuthenticationManager().authenticate(authRequest);
        } else {
            throw new BadCredentialsException("User " + userPasswd[0] + " has not the WHO role");
        }
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request,
            HttpServletResponse response, FilterChain chain, Authentication authResult)
            throws IOException, ServletException {

        SecurityContextHolder.getContext().setAuthentication(authResult);

        chain.doFilter(request, response);
    }
}