Spring security custom token filter

27,137

Solution 1

try to disable anonymous authentication and change to fully authentication to your security rule.

something like this :

http
    .addFilterBefore(authenticationTokenFilter, BasicAuthenticationFilter.class)
                    .antMatcher("/token")
                    .authenticationProvider(tokenAuthenticationProvider)
                    .authorizeUrls().anyRequest().fullyAuthenticated()
    .and()
                    .anonymous().disable()  

Solution 2

What you are missing is

<filter>
       <filter-name>springSecurityFilterChain</filter-name>
       <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
       <filter-name>springSecurityFilterChain</filter-name>
       <url-pattern>/*</url-pattern>
</filter-mapping>

in your web.xml or equivalent for intializers on your classpath:

import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;

@Order(value = 1)
public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {
}

This is separate from your WebApplicationInitializer. Note that:

  • your SecurityConfig (or anything annotated with @EnableWebSecurity) must be defined in the root context (not the dispatcher context)
  • you probably should understand order of initialization (I am not sure if I do):

"Ordering of WebApplicationInitializer"
If any servlet Filter mappings are added after AbstractSecurityWebApplicationInitializer is invoked, they might be accidentally added before springSecurityFilterChain. Unless an application contains Filter instances that do not need to be secured, springSecurityFilterChain should be before any other Filter mappings. The @Order annotation can be used to help ensure that any WebApplicationInitializer is loaded in a deterministic order.

Example:

@Order(value = 10)
public class AppWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

  @Override
  protected Class<?>[] getRootConfigClasses() {
      return new Class<?>[] { AppConfig.class, SecurityConfig.class };
  }

  @Override
  protected Class<?>[] getServletConfigClasses() {
      return new Class<?>[] { RestConfig.class };
  }

  @Override
  protected String[] getServletMappings() {
      return new String[] { "/rest/*"};
  }
}


To summarize, from Spring documentation:

When using servlet filters, you obviously need to declare them in your web.xml, or they will be ignored by the servlet container. In Spring Security, the filter classes are also Spring beans defined in the application context and thus able to take advantage of Spring's rich dependency-injection facilities and lifecycle interfaces. Spring's DelegatingFilterProxy provides the link between web.xml and the application context.

The Security Filter Chain

Share:
27,137
Federico Lenzi
Author by

Federico Lenzi

Updated on February 20, 2020

Comments

  • Federico Lenzi
    Federico Lenzi about 4 years

    I'm trying to perform a custom filter to get a token and validate it. I'm following the approach in this response.

    This is the relevant configuration:

    SecurityConfig:

    @Configuration
    @EnableWebSecurity
    @ComponentScan(basePackages = {"com.company.app"})
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Inject
    AuthenticationTokenFilter authenticationTokenFilter;
    
    @Inject
    TokenAuthenticationProvider tokenAuthenticationProvider;
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                .addFilterBefore(authenticationTokenFilter, BasicAuthenticationFilter.class)
                    .antMatcher("/*")
                    .authenticationProvider(tokenAuthenticationProvider)
                    .authorizeRequests()
                        .anyRequest().authenticated();
        }
    
    }
    

    AuthenticationTokenFilter:

    @Component
    public class AuthenticationTokenFilter implements Filter {
    
    private static final Logger logger = LoggerFactory.getLogger(AuthenticationTokenFilter.class);
    
    @Override
    public void init(FilterConfig fc) throws ServletException {
        logger.info("Init AuthenticationTokenFilter");
    }
    
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain fc) throws IOException, ServletException {
        SecurityContext context = SecurityContextHolder.getContext();
        if (context.getAuthentication() != null && context.getAuthentication().isAuthenticated()) {
            // do nothing
        } else {
            Map<String,String[]> params = req.getParameterMap();
            if (!params.isEmpty() && params.containsKey("auth_token")) {
                String token = params.get("auth_token")[0];
                if (token != null) {
                    Authentication auth = new TokenAuthentication(token);
                    SecurityContextHolder.getContext().setAuthentication(auth);
                }
            }
        }
    
        fc.doFilter(req, res);
    }
    
    @Override
    public void destroy() {
    
    }
    }
    

    TokenAuthentication:

    public class TokenAuthentication implements Authentication {
    private String token;
    
    public TokenAuthentication(String token) {
        this.token = token;
    }
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return new ArrayList<GrantedAuthority>(0);
    }
    @Override
    public Object getCredentials() {
        return token;
    }
    @Override
    public Object getDetails() {
        return null;
    }
    @Override
    public Object getPrincipal() {
        return null;
    }
    @Override
    public boolean isAuthenticated() {
        return false;
    }
    @Override
    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
    }
    @Override
    public String getName() {
        return null;
    }
    }
    

    TokenAuthenticationProvider:

    @Component
    public class TokenAuthenticationProvider implements AuthenticationProvider {
    
    private static final Logger logger = LoggerFactory.getLogger(TokenAuthenticationProvider.class);
    
    @Override
    public Authentication authenticate(Authentication auth) throws AuthenticationException {
        if (auth.isAuthenticated())
            return auth;
    
        String token = auth.getCredentials().toString();
        User user = userSvc.validateApiAuthenticationToken(token);
        if (user != null) {
            auth = new PreAuthenticatedAuthenticationToken(user, token);
            auth.setAuthenticated(true);
            logger.debug("Token authentication. Token: ");
        } else
            throw new BadCredentialsException("Invalid token " + token);
        return auth;
    }
    
    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }
    
    }
    

    But it's like the AuthenticationTokenFilter is not being added to the chain. Debugging I can see that when I do a call it enters to the SecurityConfig and configure method but not to the filter. What is missing?