1

On user authentication i need to retrieve his remote address and remote host.
I'm trying to implement a custom filter to support this, but i'm getting "authenticationManager must be specified".
Another doubt is... What is the correct way to register a custom filter using programmatically ?

Configuration using annotations:

@Configuration
@EnableWebSecurity
public class SecurityApplicationConfiguration extends WebSecurityConfigurerAdapter {

  @Autowired
  private SCAAuthenticationFilter scaAuthenticationFilter;

  @Autowired
  private SCAAuthenticationProvider scaAuthenticationProvider;

  @Autowired
  public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth.authenticationProvider(scaAuthenticationProvider);
  }

  @Override
  public void configure(WebSecurity web) throws Exception {
    web.ignoring().antMatchers("/resources/**");
  }

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
      .addFilter(scaAuthenticationFilter) // What is the right way ?
      .addFilterBefore(scaAuthenticationFilter, AbstractAuthenticationProcessingFilter.class) // What is the right way ?
      .csrf().disable()
      .authorizeRequests()
        .antMatchers("/manual/**").authenticated()
        .and()
      .formLogin()
        .loginPage("/login")
        .loginProcessingUrl("/login")
        .failureUrl("/login?error=true")
        .defaultSuccessUrl("/manual")
        .permitAll()
        .and()
      .logout()
        .logoutUrl("/logout")
        .logoutSuccessUrl("/login")
        .permitAll()
        .and();
  }
}

The custom filter:

@Component
public class SCAAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

  public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
    if (!request.getMethod().equals("POST")) {
      throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
    }
    String username = obtainUsername(request);
    String password = obtainPassword(request);
    String remoteHost = request.getRemoteHost();
    String remoteAddr = request.getRemoteAddr();
    if (username == null) {
      username = "";
    }
    if (password == null) {
      password = "";
    }
    username = username.trim();
    SCAAuthenticationToken scaAuthenticationToken = new SCAAuthenticationToken(username, password, remoteHost, remoteAddr);
    setDetails(request, scaAuthenticationToken);
    return getAuthenticationManager().authenticate(scaAuthenticationToken);
  }

}
Sandro Simas
  • 1,268
  • 3
  • 21
  • 35
  • The `addFilter` and `addFilterBefore` behaviour should be pretty clear from the [Javadocs](http://docs.spring.io/spring-security/site/docs/3.2.4.RELEASE/apidocs/org/springframework/security/config/annotation/web/HttpSecurityBuilder.html). – Shaun the Sheep May 22 '14 at 17:10
  • Yes, the filter is called but it's occurs "authenticationManager must be specified". I don't know how to set the authentication manager. I try with @Autowired authenticationManager but doesn't work. – Sandro Simas May 22 '14 at 17:13

4 Answers4

4

You need set a authenticationManagerBean for your extended filter and config it corr

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    public ExUsernamePasswordAuthenticationFilter exUsernamePasswordAuthenticationFilter()
            throws Exception {
        ExUsernamePasswordAuthenticationFilter exUsernamePasswordAuthenticationFilter = new ExUsernamePasswordAuthenticationFilter();
        exUsernamePasswordAuthenticationFilter
                .setAuthenticationManager(authenticationManagerBean());
        return exUsernamePasswordAuthenticationFilter;
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        RequestMatcher requestMatcher = new RequestMatcher() {
            @Override
            public boolean matches(HttpServletRequest httpServletRequest) {
                if (httpServletRequest.getRequestURI().indexOf("/api", 0) >= 0) {
                    return true;
                }
                return false;
            }
        };

        http
                .addFilterBefore(exUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
                ...
    }
}
Nicholas Lu
  • 1,655
  • 15
  • 14
2

Your custom filter extends Spring Security's UsernamePasswordAuthenticationFilter, which means it needs a reference to the authentication manager. I would create your filter as an @Bean in the security configuration, then follow this answer which explains different options for getting a reference to the AuthenticationManager.

Community
  • 1
  • 1
Shaun the Sheep
  • 22,353
  • 1
  • 72
  • 100
1

In the class that is extending WebSecurityConfigurerAdapter, override the authenticationManagerBean() method and annotate it with @Bean as such:

@Configuration
@EnableWebMvcSecurity
public class YourCustomConfig extends WebSecurityConfigurerAdapter{

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

Now you will be able to @Autowire the AuthenticationManager in other classes.

Ivan
  • 2,632
  • 1
  • 24
  • 12
0

Another option is to create a configurer for your filter and delegate all the work concerning filter initialization to it (the same way UsernamePasswordAuthenticationFilter is configured through the FormLoginConfigurer and AbstractAuthenticationProcessingFilter is configured by the AbstractAuthenticationFilterConfigurer).

public class SCAAuthenticationConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>() {

    public static SCAAuthenticationConfigurer scaAuthentication() {
        return new SCAAuthenticationConfigurer()
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {

        SCAAuthenticationFilter filter = new SCAAuthenticationFilter()
        filter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));

        // add postProcess(filter) call if require to autowire some fields

        http.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class)
    }
}

Having such configurer your SecurityConfig will be looking more tidy:

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .apply(scaAuthentication())
            .and()

            // do the rest of configuration
    }
}

You may even delegate filter initialization to the ApplicationContext (for example, if you have configuration to inject):

public class FilterWithSettingsConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>() {

    @Configuration
    @EnableConfigurationProperties(SomeSettings.class)
    private static class Config {}

    @Override
    public void configure(HttpSecurity http) throws Exception {

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()
        context.parent = http.getSharedObject(ApplicationContext.class)
        context.register(Config.class)
        context.refresh()

        FilterWithSettings filter = 
            context.getAutowireCapableBeanFactory().createBean(FilterWithSettings.class)

        filter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));

        http.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class)
    }
}

For the comprehensive example take a look at the https://github.com/shiver-me-timbers/smt-spring-security-parent/blob/master/smt-spring-security-jwt/src/main/java/shiver/me/timbers/spring/security/JwtSpringSecurityAdaptor.java

Ilya Serbis
  • 21,149
  • 6
  • 87
  • 74