Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

anyExchange().authenticated() causes AuthenticationWebFilter and AuthenticationManager invoke twice #5420

Closed
eximius313 opened this issue Jun 12, 2018 · 11 comments
Assignees
Labels
status: invalid An issue that we don't feel is valid

Comments

@eximius313
Copy link

Summary

I'm trying to authenticate against specific header in request.
My Security config is as follows:

@Configuration
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SecurityConfiguration {

    private static final String[] AUTH_WHITELIST = {
            // swagger ui:
            "/swagger-ui.html",
            "/swagger-resources",
            "/swagger-resources/**",
            "/v2/api-docs"
     };

    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(final ServerHttpSecurity http,
            final AuthenticationWebFilter authenticationWebFilter,
            final UnauthenticatedEntryPoint entryPoint) {
        http
            .exceptionHandling()
                .authenticationEntryPoint(entryPoint)
            .and()
            .addFilterAt(authenticationWebFilter, SecurityWebFiltersOrder.AUTHENTICATION)
            .authorizeExchange()
                .pathMatchers(AUTH_WHITELIST).permitAll()
                .anyExchange().authenticated()
            .and()
            .httpBasic().disable()
            .formLogin().disable()
            .csrf().disable()
            .logout().disable();
        return http.build();
    }

    @Bean
    public AuthenticationWebFilter authenticationWebFilter(final ReactiveAuthenticationManager authenticationManager,
            final AuthenticationConverter converter,
            final UnauthenticatedEntryPoint entryPoint,
            final HeadersExchangeMatcher matcher) {
        final AuthenticationWebFilter filter = new AuthenticationWebFilter(authenticationManager);
        filter.setAuthenticationConverter(converter);
        filter.setAuthenticationFailureHandler(new ServerAuthenticationEntryPointFailureHandler(entryPoint));
        filter.setRequiresAuthenticationMatcher(matcher);
        
        return filter; 
    }
}

Actual Behavior

Every time I make request to the resource that should be protected, both AuthenticationWebFilter#filer and AuthenticationManager#authenticate are called twice in a row.
If I comment out .anyExchange().authenticated() - they're called once as expected

Expected Behavior

.anyExchange().authenticated() should not cause AuthenticationManager#authenticate to be called twice.

Version

5.0.5.RELEASE

@rwinch
Copy link
Member

rwinch commented Jun 12, 2018

Are you using Spring Boot? If so any Filter exposed as a Bean is also registered with the servlet container. You might also post more details on how to reproduce this since AuthenticationWebFilter is a custom filter that we cannot review the code for

@rwinch rwinch self-assigned this Jun 12, 2018
@rwinch rwinch added the status: waiting-for-feedback We need additional information before we can continue label Jun 12, 2018
@eximius313
Copy link
Author

eximius313 commented Jun 12, 2018

  1. Yes, I'm using SpringBoot, but the filter is separate class annotated as @Component. I made it @Bean just in example code
  2. I put a breakpoint on http.build() and listed ServerHttpSecurity#webFilters, my filter is there only once:
    [OrderedWebFilter{webFilter=org.springframework.security.web.server.header.HttpHeaderWriterWebFilter@7fa65318, order=100}, OrderedWebFilter{webFilter=org.springframework.security.web.server.context.ReactorContextWebFilter@75679dff, order=300}, OrderedWebFilter{webFilter=com.example.authorization.AuthenticationWebFilter@11f0552c, order=600}, OrderedWebFilter{webFilter=org.springframework.security.web.server.context.SecurityContextServerWebExchangeWebFilter@61ba2ebc, order=900}, OrderedWebFilter{webFilter=org.springframework.security.web.server.savedrequest.ServerRequestCacheWebFilter@2cc98f00, order=1000}, OrderedWebFilter{webFilter=org.springframework.security.web.server.authorization.ExceptionTranslationWebFilter@65886703, order=1200}, OrderedWebFilter{webFilter=org.springframework.security.web.server.authorization.AuthorizationWebFilter@76c217ab, order=1300}]
  1. The whole code for AuthenticationWebFilter is this:
@Component
public final class AuthenticationWebFilter extends org.springframework.security.web.server.authentication.AuthenticationWebFilter {

    public AuthenticationWebFilter(final ReactiveAuthenticationManager authenticationManager,
            final AuthenticationConverter converter,
            final UnauthenticatedEntryPoint entryPoint,
            final HeadersExchangeMatcher matcher) {
        super(authenticationManager);
        setAuthenticationConverter(converter);
        setAuthenticationFailureHandler(new ServerAuthenticationEntryPointFailureHandler(entryPoint));
        setRequiresAuthenticationMatcher(matcher);
    }
}

@eximius313
Copy link
Author

eximius313 commented Jun 12, 2018

@rwinch I've prepared sample application for you: SpringSecurityReactiveSample.zip

Just run Postman/Curl and make GET request to /test with header Token set to test and observe console output

By the way - I'd have very kind request to you: this piece of code is result of me trying to understand Spring Security documentation, so if you notice anything that is not "Spring way" of doing it - please let me know.

@taerimmk
Copy link

taerimmk commented Jun 14, 2018

@eximius313

here is my code

//@component <---- remove bean
public class JwtAuthenticationWebFilter extends AuthenticationWebFilter {
.............

//SecurityConfig.java

public SecurityWebFilterChain springSecurityFilterChain(
        ServerHttpSecurity http,
        JwtReactiveAuthenticationManager authenticationManager,
        JwtAuthenticationConverter converter,
        UnauthorizedAuthenticationEntryPoint entryPoint

) {

    http
            .exceptionHandling()
            .and()
            .addFilterAt(new JwtAuthenticationWebFilter(authenticationManager, converter, entryPoint)
                    , SecurityWebFiltersOrder.AUTHENTICATION)
            .authorizeExchange()
            .pathMatchers(AUTH_WHITELIST).permitAll()
            .anyExchange().authenticated()
            .and()
            .httpBasic().disable()
            .formLogin().disable()
            .csrf().disable()
            .logout().disable();
    return http.build();
}

@eximius313
Copy link
Author

@taerimmk, it works indeed, but why?
I debugged the code and verified that webFilters list is exactly the same in both solutions:
[OrderedWebFilter{webFilter=org.springframework.security.web.server.header.HttpHeaderWriterWebFilter@6f410de3, order=100}, OrderedWebFilter{webFilter=org.springframework.security.web.server.context.ReactorContextWebFilter@8d04dbc, order=300}, OrderedWebFilter{webFilter=showcase.authentication.AuthenticationWebFilter@1360b1d9, order=600}, OrderedWebFilter{webFilter=org.springframework.security.web.server.context.SecurityContextServerWebExchangeWebFilter@538c4daf, order=900}, OrderedWebFilter{webFilter=org.springframework.security.web.server.savedrequest.ServerRequestCacheWebFilter@34457c28, order=1000}, OrderedWebFilter{webFilter=org.springframework.security.web.server.authorization.ExceptionTranslationWebFilter@3e727ab1, order=1200}, OrderedWebFilter{webFilter=org.springframework.security.web.server.authorization.AuthorizationWebFilter@288aeadf, order=1300}]

I think this is not correct behavior

@rwinch
Copy link
Member

rwinch commented Jun 18, 2018

@Component creates it as a Bean when classpath scanning is added. You can reject it from being added by using FilterRegistrationBean. See https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-disable-registration-of-a-servlet-or-filter

@eximius313
Copy link
Author

But where it resides if webFilters is not changed?
And why it magically works if this line: .anyExchange().authenticated() is being commented out?

@ivpal
Copy link

ivpal commented Mar 14, 2019

@eximius313

here is my code

//@component <---- remove bean
public class JwtAuthenticationWebFilter extends AuthenticationWebFilter {
.............

//SecurityConfig.java

public SecurityWebFilterChain springSecurityFilterChain(
        ServerHttpSecurity http,
        JwtReactiveAuthenticationManager authenticationManager,
        JwtAuthenticationConverter converter,
        UnauthorizedAuthenticationEntryPoint entryPoint

) {

    http
            .exceptionHandling()
            .and()
            .addFilterAt(new JwtAuthenticationWebFilter(authenticationManager, converter, entryPoint)
                    , SecurityWebFiltersOrder.AUTHENTICATION)
            .authorizeExchange()
            .pathMatchers(AUTH_WHITELIST).permitAll()
            .anyExchange().authenticated()
            .and()
            .httpBasic().disable()
            .formLogin().disable()
            .csrf().disable()
            .logout().disable();
    return http.build();
}

I have some problem. Remove @Component fix it.

@eximius313
Copy link
Author

@rwinch what kind of feedback regarding waiting-for-feedback label do you require?

@rwinch rwinch removed their assignment Jul 29, 2019
@jhamberg
Copy link

jhamberg commented May 27, 2020

To anyone else who finds their way here because their WebFilter (e.g. AuthenticationWebFilter) is being called twice:

Spring Boot automatically registers any filters marked as a @Bean or @Component which means that if you want more precise control—like with addFilterAt—you should remove the annotation.

I'm only restating here what @rwinch and others have said in an attempt to make the information a bit more available through search engines.

@rwinch
Copy link
Member

rwinch commented May 29, 2020

@jhamberg I'd work with the Spring Boot team on that since it is Spring Boot that adds the WebFilters automatically

@rwinch rwinch closed this as completed May 29, 2020
@rwinch rwinch added status: invalid An issue that we don't feel is valid and removed status: waiting-for-feedback We need additional information before we can continue labels May 29, 2020
@rwinch rwinch self-assigned this May 29, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: invalid An issue that we don't feel is valid
Projects
None yet
Development

No branches or pull requests

5 participants