Summary

Improve org.springframework.security.config.web.server.ServerHttpSecurity.OAuth2LoginSpec for more configuration options.

Actual Behavior

  1. Chaining of OAuth2LoginSpec not available, missing public ServerHttpSecurity and() { return ServerHttpSecurity.this; }
  2. Static routes for > /oauth2/authorization/{registrationId} > /login/oauth2/code/{registrationId}

Expected Behavior

  1. allow chaining

    1. allow to override/configure oauthRedirectFilter (or authorizationRequestBaseUri in particular)
    2. allow to override/configure AuthenticationWebFilter.requiresAuthenticationMatcher (or PathPatternParserServerWebExchangeMatcher -> pattern => requestBaseUri in particular)

Configuration

http.oauth2Login();

Version

Boot 2.1.0.SNAPSHOT Spring Security 5.1.0.M2

Comment From: rwinch

The first part was easy so I fixed it in a separate issue. The next pieces are going to need to wait until we get #5610 resolved

Comment From: VijaySidhu

I am writing Service with WebFlux Framework Below are Technologies i am trying to use Spring Boot 2.1.0.M3 Spring Security 5.1.0.RC2 In Spring Security It's trying to match /oauth2/authorization/{registrationId} with Path i configured which is different and it's not matching and throwing 401. Is there any way to override /oauth2/authorization/{registrationId} this with value i am using

Comment From: nickbr23

Have the static routes:

/oauth2/authorization/{registrationId} /login/oauth2/code/{registrationId}

Been made overridable or configurable yet?

Comment From: jgrandja

@nickbr23 @VijaySidhu The configuration points for overriding the authorization request and authorization response base uri's is not available as of yet. Is anyone interested in submitting a PR for this?

At the moment I'm think we need the following:

OAuth2LoginSpec.authenticationMatcher(ServerWebExchangeMatcher matcher)

OAuth2LoginSpec.authorizationRequestResolver(ServerOAuth2AuthorizationRequestResolver resolver)

Comment From: hartmut-co-uk

I succeeded getting it working, see following snippet:

    private fun oauth2Login(
            http: ServerHttpSecurity,
            authBaseUri: String = "/api/auth/oauth2/authorization",
            loginBaseUri: String = "/api/auth/oauth2/code") {
        val clientRegistrationRepository = getClientRegistrationRepository()
        val oauthRedirectFilter = OAuth2AuthorizationRequestRedirectWebFilter(
                DefaultServerOAuth2AuthorizationRequestResolver(
                        clientRegistrationRepository,
                        PathPatternParserServerWebExchangeMatcher("$authBaseUri/{registrationId}")))

        val client = WebClientReactiveAuthorizationCodeTokenResponseClient()
        val userService = DefaultReactiveOAuth2UserService()
        var manager: ReactiveAuthenticationManager = MyOAuth2LoginReactiveAuthenticationManager(client, userService,
                accountService)

        val oidcAuthenticationProviderEnabled = ClassUtils.isPresent(
                "org.springframework.security.oauth2.jwt.JwtDecoder", this.javaClass.classLoader)
        if (oidcAuthenticationProviderEnabled) {
            val oidc = OidcAuthorizationCodeReactiveAuthenticationManager(client, OidcReactiveOAuth2UserService())
            manager = DelegatingReactiveAuthenticationManager(oidc, manager)
        }

        val authenticationFilter = AuthenticationWebFilter(manager)
        authenticationFilter.setRequiresAuthenticationMatcher(PathPatternParserServerWebExchangeMatcher("$loginBaseUri/{registrationId}"))
        authenticationFilter.setServerAuthenticationConverter(ServerOAuth2AuthorizationCodeAuthenticationTokenConverter(clientRegistrationRepository))

        authenticationFilter.setAuthenticationSuccessHandler(OAuth2CodeAuthenticationSuccessHandler())
        authenticationFilter.setAuthenticationFailureHandler { _, exception -> Mono.error(exception) } // TODO: metrics
        authenticationFilter.setSecurityContextRepository(WebSessionServerSecurityContextRepository())

        http.addFilterAt(oauthRedirectFilter, SecurityWebFiltersOrder.HTTP_BASIC)
        http.addFilterAt(authenticationFilter, SecurityWebFiltersOrder.AUTHENTICATION)
    }

Please note MyOAuth2LoginReactiveAuthenticationManager and accountService are custom..

var manager: ReactiveAuthenticationManager = MyOAuth2LoginReactiveAuthenticationManager(client, userService,
                accountService)

PS: that's on spring boot version 2.1.1.RELEASE, not SNAPSHOT as above in the original issue description.

Comment From: nickbr23

At the moment I'm think we need the following:

OAuth2LoginSpec.authenticationMatcher(ServerWebExchangeMatcher matcher)

OAuth2LoginSpec.authorizationRequestResolver(ServerOAuth2AuthorizationRequestResolver resolver)

That would work.

For just overriding the uri's, it should be as simple as providing:

OAuth2LoginSpec.authorizationRequestBaseUri(String authorizationRequestBaseUri) OAuth2LoginSpec.loginProcessingUrl(String loginProcessingUrl)

So that it mirrors the servlets builder.

If you're happy with just providing the uri's then feel free to assign it to me and I'll handle the PR.

Comment From: jgrandja

Thanks for the feedback @nickbr23

So that it mirrors the servlets builder.

OAuth2LoginSpec.authorizationRequestBaseUri(String authorizationRequestBaseUri)
OAuth2LoginSpec.loginProcessingUrl(String loginProcessingUrl)

This does not actually mirror the DSL in OAuth2LoginConfigurer. Check out the full DSL here

The preference is to use ServerOAuth2AuthorizationRequestResolver over just configuring the authorization request URI as it provides more flexibility. This is how http.oauth2Client().authorizationCodeGrant().authorizationRequestResolver() has been designed as well. Also, we are considering deprecating authorizationEndpoint.baseUri().

The new configuration API's we would want to introduce are as follows:

OAuth2LoginSpec.authorizationRequestResolver(ServerOAuth2AuthorizationRequestResolver authorizationRequestResolver)

OAuth2LoginSpec.authenticationMatcher(ServerWebExchangeMatcher authenticationMatcher)

Let me know if you're still interested in submitting a PR for this improvement.

Comment From: nickbr23

Let me know if you're still interested in submitting a PR for this improvement.

Sure, I'm happy to do it.

If we introduce OAuth2LoginSpec.authorizationRequestResolver(ServerOAuth2AuthorizationRequestResolver authorizationRequestResolver) won't there still be an issue with changing the path from method OAuth2LoginSpec.getLinks as this should match the path in the new request resolver?

Comment From: jgrandja

OAuth2LoginSpec.getLinks is used for the default login page. If the user provides a customAuthorizationRequestResolver, it is also their responsibility to provide a custom login page that uses the custom authorization request base uri used by customAuthorizationRequestResolver. So this is not an issue as OAuth2LoginSpec.getLinks does not need to be updated as part of the changes required for this PR. Also keep in mind that the default login page is generally used for development/testing/samples as a convenience. Production applications typically provide a custom login page.

The PR is yours @nickbr23. Let me know if you have any other questions. Thanks for taking this on!

Comment From: nickbr23

PR has been created: https://github.com/spring-projects/spring-security/pull/6462

Comment From: deeplights

OAuth2LoginSpec.getLinks is used for the default login page. If the user provides a customAuthorizationRequestResolver, it is also their responsibility to provide a custom login page that uses the custom authorization request base uri used by customAuthorizationRequestResolver. So this is not an issue as OAuth2LoginSpec.getLinks does not need to be updated as part of the changes required for this PR. Also keep in mind that the default login page is generally used for development/testing/samples as a convenience. Production applications typically provide a custom login page.

@jgrandja Is there a way to customize the login page and override the behavior OAuth2LoginSpec.getLinks provides?

Comment From: saurabhgour

@nickbr23 @jgrandja

I still could'nt figure out how to customize the baseUri in the reactive stack. For the servlet stack I can customize it as follows:

@Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests(authorize -> authorize .anyRequest().authenticated() ) .oauth2Login() .redirectionEndpoint() .baseUri("/auth/custom/sso"); } Could you please provide a sample of the equivalent code for the reactive stack ?

Comment From: luqmanulkhair

@nickbr23 @jgrandja can you please provide a documentation link, thanks!

Comment From: jgrandja

@saurabhgour @luqmanulkhair Here is a sample configuration:

@EnableWebFluxSecurity
public class SecurityConfig {

    @Autowired
    private ReactiveClientRegistrationRepository clientRegistrationRepository;

    @Bean
    SecurityWebFilterChain configure(ServerHttpSecurity http) {
        http
            .authorizeExchange(exchanges ->
                exchanges
                    .anyExchange().authenticated()
            )
            .oauth2Login(oauth2Login ->
                oauth2Login
                    .authorizationRequestResolver(getAuthorizationRequestResolver()));
        return http.build();
    }

    private ServerOAuth2AuthorizationRequestResolver getAuthorizationRequestResolver() {
        return new DefaultServerOAuth2AuthorizationRequestResolver(
                this.clientRegistrationRepository,
                new PathPatternParserServerWebExchangeMatcher(
                        "/auth/custom/sso/{registrationId}"));

    }
}

Given the above configuration, the URL http://localhost:8080/auth/custom/sso/google would trigger the authentication for Google.

Comment From: luqmanulkhair

@jgrandja thanks for your response, Is this code for .authoriationEndpoint().baseurl() or for .redirectionEndpoint() .baseUri(""), my problem is to handle the redirect from authorization-server after login with code, I think I need to use authenticationMatcher(), I have implemented my custom logic but it not standardized.

Example: redirect_url: custom/login instead of {baseUrl}/login/oauth2/code/{registrationId} (does not work) if i use {baseUrl}/login/oauth2/code/{registrationId} as a redirect uri everything works.

Comment From: jgrandja

@luqmanulkhair The sample code provided is for .authoriationEndpoint().baseurl().

For redirectionEndpoint() .baseUri(), that is correct, you need to configure authenticationMatcher(). The default authenticationMatcher is:

new PathPatternParserServerWebExchangeMatcher("/login/oauth2/code/{registrationId}")

Configure this, with whatever path you require.

Comment From: SMakhrov

@jgrandja solution doesn't work for me. After copy/paste your Java class I have redirect loop to /auth/custom/sso/{registrationId} Spring Security Improve OAuth2LoginSpec with more configuration options Could I ask you for help, please? My application.yaml in Spring Cloud Gateway (extremely simple - only this yaml and your SecurityConfig):

server:
  port: 80
spring:
  cloud:
    gateway:
      default-filters:
        - TokenRelay
      routes:
        - id: root
          uri: http://origin
          predicates:
            - Path=/**
          filters:
            - RemoveRequestHeader=Cookie

  security:
    oauth2:
      client:
        registration:
          smart_hub_client:
            provider: wso2is
            client-id: clientid
            client-secret: clientsecret
            authorization-grant-type: authorization_code
            redirect-uri: "{baseUrl}/redirect_uri"
            scope: sso,openid
        provider:
          wso2is:
            authorization-uri: http://localhost:8080/oauth2/authorize?loginPage=login.jsp
            token-uri: http://localhost:8080/oauth2/token
            user-info-uri: http://localhost:8080/oauth2/userinfo
            user-name-attribute: sub
            jwk-set-uri: http://localhost:8080/oauth2/jwks