I am trying to implement jwt authentication with spring webflux and i am adding filter on SecurityWebFiltersOrder.AUTHENTICATION but the problem is its getting called on permitted urls too.

@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(
            ServerHttpSecurity http,
            ReactiveAuthenticationManager jwtAuthenticationManager,
            ServerAuthenticationConverter jwtAuthenticationConverter
    ) {

        AuthenticationWebFilter authenticationWebFilter = new AuthenticationWebFilter(jwtAuthenticationManager);
        authenticationWebFilter.setServerAuthenticationConverter(jwtAuthenticationConverter);

        return http
                .addFilterAt(authenticationWebFilter, SecurityWebFiltersOrder.AUTHENTICATION)
                .httpBasic()
                .disable()
                .csrf()
                .disable()
                .formLogin()
                .disable()
                .logout()
                .disable()
                .exceptionHandling()
                .authenticationEntryPoint((exchange, e) -> {
                    if (e.getCause() instanceof AccessDeniedException) {
                        throw new ForbiddenException(e);
                    }
                    throw new UnauthorizedException(e);
                })
                .and()
                .authorizeExchange()
                .pathMatchers(
                        "/api/auth/login"
                ).permitAll()
                .pathMatchers("/api/user/**").hasRole(RolesList.SUPER_ADMIN.name())
                .pathMatchers("/api/auth/password").hasAnyRole(RolesList.SUPER_ADMIN.name(), RolesList.ADMIN.name())
                .pathMatchers("/api/**").authenticated() //entire server side app will be kept here
                .anyExchange().permitAll() // every else path should be permitted
                .and()
                .build();
    }
}
@Component
public class JwtServerAuthenticationConverter implements ServerAuthenticationConverter {

    private static final String AUTH_HEADER_VALUE_PREFIX = "Bearer ";

    @Override
    public Mono<Authentication> convert(ServerWebExchange exchange) {
        System.out.println("convert called");
        return Mono.justOrEmpty(exchange)
                .flatMap(serverWebExchange -> Mono.justOrEmpty(
                        serverWebExchange
                                .getRequest()
                                .getHeaders()
                                .getFirst(HttpHeaders.AUTHORIZATION)
                        )
                )
                .filter(header -> !header.trim().isEmpty() && header.trim().startsWith(AUTH_HEADER_VALUE_PREFIX))
                .map(header -> header.substring(AUTH_HEADER_VALUE_PREFIX.length()))
                .map(token -> new UsernamePasswordAuthenticationToken(token, token))
                ;
    }
}
@Component
public class JwtAuthenticationManager implements ReactiveAuthenticationManager {

    private final JWTProperties jwtProperties;
    private final ObjectMapper objectMapper;

    public JwtAuthenticationManager(JWTProperties jwtProperties, ObjectMapper objectMapper) {
        this.jwtProperties = jwtProperties;
        this.objectMapper = objectMapper;
    }

    @Override
    public Mono<Authentication> authenticate(Authentication authentication) {
        return Mono.just(authentication)
                .map(auth -> JWTHelper.loadAllClaimsFromToken(auth.getCredentials().toString(), jwtProperties.getSecret()))
                .onErrorResume(throwable -> Mono.error(new UnauthorizedException()))
                .map(claims -> objectMapper.convertValue(claims, JWTUserDetails.class))
                .map(jwtUserDetails ->
                        new UsernamePasswordAuthenticationToken(
                                jwtUserDetails,
                                authentication.getCredentials(),
                                jwtUserDetails.getGrantedAuthorities()
                        )
                )
                ;
    }
}

As you can see /api/auth/login path should not check the jwt token but when i hit it i can see convert called on the console

while i am assuming that is should only get called for authenticated urls.

Comment From: mychaint

Hi @ilyas2016, authentication procedure involves all requests as you declared above at . addFilterAt, those filters help to generate Authentication, meanwhile permitAll is part of authorisation procedure, which is to make decision according to authentication you got from authentication filters.

In a word, permitAll doesn't mean requests are not filtered by your authenticationFilter, instead it indicates that allows all requests without checking the authentication.

Comment From: ilyasdotdev

Hi @ilyas2016, authentication procedure involves all requests as you declared above at . addFilterAt, those filters help to generate Authentication, meanwhile permitAll is part of authorisation procedure, which is to make decision according to authentication you got from authentication filters.

In a word, permitAll doesn't mean requests are not filtered by your authenticationFilter, instead it indicates that allows all requests without checking the authentication.

Okay, So is there any way to run filter on authenticated paths only and not the permitted paths?

Comment From: mychaint

I would suggest to configure those routes specifically in the authentication filter. If you refer to UsernamePasswordAuthenticationFilter implementation, it uses constructor below:

    /**
     * Creates a new instance with a {@link RequestMatcher} and an
     * {@link AuthenticationManager}
     * @param requiresAuthenticationRequestMatcher the {@link RequestMatcher} used to
     * determine if authentication is required. Cannot be null.
     * @param authenticationManager the {@link AuthenticationManager} used to authenticate
     * an {@link Authentication} object. Cannot be null.
     */
    protected AbstractAuthenticationProcessingFilter(RequestMatcher requiresAuthenticationRequestMatcher,
            AuthenticationManager authenticationManager) {
        setRequiresAuthenticationRequestMatcher(requiresAuthenticationRequestMatcher);
        setAuthenticationManager(authenticationManager);

But if you would like to use something simpler, there is another constructor in AbstractAuthenticationProcessingFilter

    /**
     * @param defaultFilterProcessesUrl the default value for <tt>filterProcessesUrl</tt>.
     */
    protected AbstractAuthenticationProcessingFilter(String defaultFilterProcessesUrl) {
        setFilterProcessesUrl(defaultFilterProcessesUrl);
    }

If anyone knows other way, nice to hear about it.

Comment From: ilyasdotdev

I would suggest to configure those routes specifically in the authentication filter. If you refer to UsernamePasswordAuthenticationFilter implementation, it uses constructor below:

java /** * Creates a new instance with a {@link RequestMatcher} and an * {@link AuthenticationManager} * @param requiresAuthenticationRequestMatcher the {@link RequestMatcher} used to * determine if authentication is required. Cannot be null. * @param authenticationManager the {@link AuthenticationManager} used to authenticate * an {@link Authentication} object. Cannot be null. */ protected AbstractAuthenticationProcessingFilter(RequestMatcher requiresAuthenticationRequestMatcher, AuthenticationManager authenticationManager) { setRequiresAuthenticationRequestMatcher(requiresAuthenticationRequestMatcher); setAuthenticationManager(authenticationManager);

But if you would like to use something simpler, there is another constructor in AbstractAuthenticationProcessingFilter

java /** * @param defaultFilterProcessesUrl the default value for <tt>filterProcessesUrl</tt>. */ protected AbstractAuthenticationProcessingFilter(String defaultFilterProcessesUrl) { setFilterProcessesUrl(defaultFilterProcessesUrl); }

If anyone knows other way, nice to hear about it.

I have found an easiest way which can ignore authentication on specific urls so no converter get called :)

return http
                .addFilterAt(authenticationWebFilter, SecurityWebFiltersOrder.AUTHENTICATION)
                .securityMatcher(
                        new NegatedServerWebExchangeMatcher(
                                ServerWebExchangeMatchers.pathMatchers("/api/auth/login")
                        )
                )

Comment From: mychaint

I would suggest to configure those routes specifically in the authentication filter. If you refer to UsernamePasswordAuthenticationFilter implementation, it uses constructor below: java /** * Creates a new instance with a {@link RequestMatcher} and an * {@link AuthenticationManager} * @param requiresAuthenticationRequestMatcher the {@link RequestMatcher} used to * determine if authentication is required. Cannot be null. * @param authenticationManager the {@link AuthenticationManager} used to authenticate * an {@link Authentication} object. Cannot be null. */ protected AbstractAuthenticationProcessingFilter(RequestMatcher requiresAuthenticationRequestMatcher, AuthenticationManager authenticationManager) { setRequiresAuthenticationRequestMatcher(requiresAuthenticationRequestMatcher); setAuthenticationManager(authenticationManager);

But if you would like to use something simpler, there is another constructor in AbstractAuthenticationProcessingFilter java /** * @param defaultFilterProcessesUrl the default value for <tt>filterProcessesUrl</tt>. */ protected AbstractAuthenticationProcessingFilter(String defaultFilterProcessesUrl) { setFilterProcessesUrl(defaultFilterProcessesUrl); }

If anyone knows other way, nice to hear about it.

I have found an easiest way which can ignore authentication on specific urls so no converter get called :)

return http .addFilterAt(authenticationWebFilter, SecurityWebFiltersOrder.AUTHENTICATION) .securityMatcher( new NegatedServerWebExchangeMatcher( ServerWebExchangeMatchers.pathMatchers("/api/auth/login") ) )

.securityMatcher is to specify what url pattern the whole configuration is applied to. if this is what you need, it is a good workaround.

Multiple HttpSecurity is also an option for the case i think. Kotlin-Configuration.

Comment From: eleftherias

Thanks for getting in touch, but it feels like this is a question that would be better suited to Stack Overflow. We prefer to use GitHub issues only for bugs and enhancements. Feel free to update this issue with a link to the re-posted question (so that other people can find it) or add a minimal sample that reproduces this issue if you feel this is a genuine bug.

Comment From: josephcass-awin

I also bumped into this issue, with a slightly different context.

I had the following SecurityWebFilterChain configuration:

http
    .authorizeExchange()
    .pathMatchers(getPermittedUrls()).permitAll()
    .anyExchange().authenticated()
    .and()
    .oauth2ResourceServer()

where getPermittedUrls() returns a String[] of URLs I'd like to bypass security configuration for.

This worked fine for permitting requests to the specified URLs which don't include Authorization headers, but still applies the security configuration to requests that do.

I thought, before reading this thread, that the permitted endpoints should bypass security regardless of whether an Authorization header is passed. Is this a bug in the way permitAll() works? Or a miss-understanding on my half?

Regardless, I was able to achieve what I was looking for by using .securityMatcher(), but I don't think this is initially obvious, and I only came across this solution en route to creating an issue for my problem.

Thanks :)

Note: I am using Spring Security 5.4.5

Comment From: jzheaux

I thought, before reading this thread, that the permitted endpoints should bypass security regardless of whether an Authorization header is passed. Is this a bug in the way permitAll() works?

When credentials are presented, Spring Security will authenticate the request using those credentials. If a request cannot be authenticated, then the request is rejected. This is by design.

Note that securityMatcher means that you are describing the endpoints that Spring Security will participate in. This means not only authentication and authorization, but also web application security defense. Generally speaking, securityMatcher is for when you need multiple filter chains.

As @eleftherias explained, please follow up on StackOverflow, though, as that is the preferred forum for questions.