Search code examples
spring-bootspring-securityoauth-2.0spring-webfluxspring-cloud-gateway

Loop redirect when login OAuth2.0 Login + Webflux Security


I am developing authentication and authorization in an environment where I use Spring Cloud Gateway Webflux + OAuth 2.0 the structure to achieve is the following:

enter image description here

As Authorization Server I have my own OAuth server that contains the /login page where I perform the authentication and it is also in charge of generating JWT and as Resource Server I have a WebFlux module that is also in charge of being the Gateway.

The Resource Server configuration is as follows:

application.yml

spring:
  application:
    name: spring-boot-gateway
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: http://adp-auth-provider/auth/oauth/token
      client:
        registration:
          oauth:
            client-name: oauth
            client-id: first-client
            client-secret: xxxxxx
            provider: adp-auth-provider
            authorization-grant-type: authorization_code
            redirect-uri: /login
            scope: read
        provider:
          adp-auth-provider:
            authorization-uri: /auth/oauth/authorize
            token-uri: http://adp-auth-provider/auth/oauth/token
            user-info-uri: http://adp-auth-provider/userinfo
            jwt-set-uri: http://adp-auth-provider/token_keys

WebFluxSecurityConfig.java

    @Configuration(proxyBeanMethods = false)
    @EnableWebFluxSecurity
    @EnableReactiveMethodSecurity
    public class WebFluxSecurityConfig {
    
        @Bean
        public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
    
            return http
                    .httpBasic().disable()
                    .csrf().disable()
                    .authorizeExchange(exchanges -> exchanges
                            .pathMatchers(HttpMethod.GET, "/oauth2/authorization/**",
                                    "/actuator",
                                    "/actuator/**",
                                    "/auth/login",
                                    "/login")
                            .permitAll()
                            .anyExchange()
                                    .authenticated()
                    .oauth2Login()
                    .and()
                    .build();
    
        }
}

SpringGatewayApplication.java

@SpringBootApplication
public class SpringGatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringGatewayApplication.class, args);
    }

}

When I type http://localhost in the browser, it redirects perfectly to the OAuth /login page, but when I enter my credentials it redirects me to the next page:

enter image description here

The requests appear to have been the right ones:

enter image description here

enter image description here

Does anyone know why I am not redirected to the index once I have logged in correctly? It stays on that page and if I click on oauth it redirects me to the same page again.

**EDIT:

Setting redirect-uri to the default "{baseUrl}/login/oauth2/code/{registrationId}" displays the following error:

Error with default redirect-uri

2022-01-18 12:12:15.852 ERROR 2836 --- [ctor-http-nio-6] a.w.r.e.AbstractErrorWebExceptionHandler : [477242e5-1]  500 Server Error for HTTP GET "/login/oauth2/code/oauth?code=nTCRNi&state=Ub8jQjbp1baxhgsxcpNULMMHoV8z42bQsp62iL2jNV8%3D"

java.lang.IllegalStateException: No provider found for class org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken
    at org.springframework.security.web.server.authentication.AuthenticationWebFilter.lambda$authenticate$6(AuthenticationWebFilter.java:123) ~[spring-security-web-5.6.0.jar:5.6.0]
    Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
    *__checkpoint ⇢ org.springframework.security.oauth2.client.web.server.authentication.OAuth2LoginAuthenticationWebFilter [DefaultWebFilterChain]
    *__checkpoint ⇢ org.springframework.security.oauth2.client.web.server.OAuth2AuthorizationRequestRedirectWebFilter [DefaultWebFilterChain]
    *__checkpoint ⇢ org.springframework.security.web.server.context.ReactorContextWebFilter [DefaultWebFilterChain]
    *__checkpoint ⇢ org.springframework.security.web.server.header.HttpHeaderWriterWebFilter [DefaultWebFilterChain]
    *__checkpoint ⇢ org.springframework.security.config.web.server.ServerHttpSecurity$ServerWebExchangeReactorContextWebFilter [DefaultWebFilterChain]
    *__checkpoint ⇢ org.springframework.security.web.server.WebFilterChainProxy [DefaultWebFilterChain]
    *__checkpoint ⇢ org.springframework.boot.actuate.metrics.web.reactive.server.MetricsWebFilter [DefaultWebFilterChain]
    *__checkpoint ⇢ HTTP GET "/login/oauth2/code/oauth?code=nTCRNi&state=Ub8jQjbp1baxhgsxcpNULMMHoV8z42bQsp62iL2jNV8%3D" [ExceptionHandlingWebHandler]
Original Stack Trace:
        at org.springframework.security.web.server.authentication.AuthenticationWebFilter.lambda$authenticate$6(AuthenticationWebFilter.java:123) ~[spring-security-web-5.6.0.jar:5.6.0]
        at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:44) ~[reactor-core-3.4.12.jar:3.4.12]
        at reactor.core.publisher.Mono.subscribe(Mono.java:4400) ~[reactor-core-3.4.12.jar:3.4.12]
        at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onComplete(FluxSwitchIfEmpty.java:82) ~[reactor-core-3.4.12.jar:3.4.12]
        at reactor.core.publisher.FluxHide$SuppressFuseableSubscriber.onComplete(FluxHide.java:147) ~[reactor-core-3.4.12.jar:3.4.12]
        at reactor.core.publisher.MonoNext$NextSubscriber.onComplete(MonoNext.java:102) ~[reactor-core-3.4.12.jar:3.4.12]
        at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.drain(FluxConcatMap.java:367) ~[reactor-core-3.4.12.jar:3.4.12]
        at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.innerComplete(FluxConcatMap.java:296) ~[reactor-core-3.4.12.jar:3.4.12]
        at reactor.core.publisher.FluxConcatMap$ConcatMapInner.onComplete(FluxConcatMap.java:885) ~[reactor-core-3.4.12.jar:3.4.12]
        at reactor.core.publisher.MonoFlatMap$FlatMapMain.secondComplete(MonoFlatMap.java:196) ~[reactor-core-3.4.12.jar:3.4.12]
        at reactor.core.publisher.MonoFlatMap$FlatMapInner.onComplete(MonoFlatMap.java:268) ~[reactor-core-3.4.12.jar:3.4.12]
        at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:142) ~[reactor-core-3.4.12.jar:3.4.12]
        at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onComplete(Operators.java:2058) ~[reactor-core-3.4.12.jar:3.4.12]
        at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onComplete(Operators.java:2058) ~[reactor-core-3.4.12.jar:3.4.12]
        at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onComplete(FluxMapFuseable.java:150) ~[reactor-core-3.4.12.jar:3.4.12]
        at reactor.core.publisher.MonoFlatMap$FlatMapMain.secondComplete(MonoFlatMap.java:196) ~[reactor-core-3.4.12.jar:3.4.12]
        at reactor.core.publisher.MonoFlatMap$FlatMapInner.onComplete(MonoFlatMap.java:268) ~[reactor-core-3.4.12.jar:3.4.12]
        at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onComplete(Operators.java:2058) ~[reactor-core-3.4.12.jar:3.4.12]
        at reactor.core.publisher.MonoNext$NextSubscriber.onComplete(MonoNext.java:102) ~[reactor-core-3.4.12.jar:3.4.12]
        at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:142) ~[reactor-core-3.4.12.jar:3.4.12]
        at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:142) ~[reactor-core-3.4.12.jar:3.4.12]
        at reactor.core.publisher.FluxPeek$PeekSubscriber.onComplete(FluxPeek.java:260) ~[reactor-core-3.4.12.jar:3.4.12]
        at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:142) ~[reactor-core-3.4.12.jar:3.4.12]
        at reactor.netty.channel.FluxReceive.onInboundComplete(FluxReceive.java:400) ~[reactor-netty-core-1.0.13.jar:1.0.13]
        at reactor.netty.channel.ChannelOperations.onInboundComplete(ChannelOperations.java:419) ~[reactor-netty-core-1.0.13.jar:1.0.13]
        at reactor.netty.channel.ChannelOperations.terminate(ChannelOperations.java:473) ~[reactor-netty-core-1.0.13.jar:1.0.13]
        at reactor.netty.http.client.HttpClientOperations.onInboundNext(HttpClientOperations.java:702) ~[reactor-netty-http-1.0.13.jar:1.0.13]
        at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:93) ~[reactor-netty-core-1.0.13.jar:1.0.13]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
        at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:103) ~[netty-codec-4.1.70.Final.jar:4.1.70.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
        at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
        at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:324) ~[netty-codec-4.1.70.Final.jar:4.1.70.Final]
        at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:296) ~[netty-codec-4.1.70.Final.jar:4.1.70.Final]
        at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
        at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
        at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
        at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
        at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:719) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
        at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:655) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
        at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:581) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
        at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
        at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:986) ~[netty-common-4.1.70.Final.jar:4.1.70.Final]
        at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.70.Final.jar:4.1.70.Final]
        at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.70.Final.jar:4.1.70.Final]
        at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]

The application.yml now looks like this:

spring:
  application:
    name: spring-boot-gateway
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: http://localhost/auth/oauth/token
      client:
        registration:
          oauth:
            client-name: oauth
            client-id: first-client
            client-secret: xxxx
            provider: adp-auth-provider
            authorization-grant-type: authorization_code
            redirect-uri: '{baseUrl}/login/oauth2/code/{registrationId}'
            scope: read
        provider:
          adp-auth-provider:
            authorization-uri: http://localhost/auth/oauth/authorize
            token-uri: http://localhost/auth/oauth/token
            user-info-uri: http://localhost/auth/me
            user-name-attribute: sub

Solution

  • The problem was occurring because the default authentication manager wasn't working for me, I had to implement one specifically for my problem.

    
    @Configuration(proxyBeanMethods = false)
        @EnableWebFluxSecurity
        @EnableReactiveMethodSecurity
        public class WebFluxSecurityConfig {
        
            @Bean
            public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http, AuthenticationManager authenticationManager) {
        
                return http
                        .httpBasic().disable()
                        .csrf().disable()
                        .authorizeExchange(exchanges -> exchanges
                                .pathMatchers(HttpMethod.GET, "/oauth2/authorization/**",
                                        "/actuator",
                                        "/actuator/**",
                                        "/auth/login",
                                        "/login/**")
                                .permitAll()
                                .anyExchange()
                                        .authenticated()
                        .oauth2Login()
                        .authenticationManager(authenticationManager)
                        .and()
                        .build();
        
            }
    }
    

    I also had to modify the redirect-uri and leave it as '/login/oauth2/code/{registrationId}'.

    spring:
      application:
        name: spring-boot-gateway
      security:
        oauth2:
          resourceserver:
            jwt:
              issuer-uri: http://127.0.0.1/auth/oauth/token
          client:
            registration:
              oauth:
                client-name: oauth
                client-id: first-client
                client-secret: xxxxx
                provider: adp-auth-provider
                authorization-grant-type: authorization_code
                redirect-uri: '/login/oauth2/code/{registrationId}'
                scope: read
            provider:
              adp-auth-provider:
                authorization-uri: /auth/oauth/authorize
                token-uri: /auth/oauth/token
                user-info-uri: /auth/me