spring-projects / spring-security

Spring Security
http://spring.io/projects/spring-security
Apache License 2.0
8.84k stars 5.91k forks source link

OAuth2 Authorization Code redirect not working when using webflux.base-path #8967

Open juliuskrah opened 4 years ago

juliuskrah commented 4 years ago

Describe the bug I am running spring-boot 2.3.1 with spring-boot-starter-oauth2-client, after adding a context-path, everything breaks

To Reproduce I have the following configuration

@Bean
SecurityWebFilterChain securityFilter(ServerHttpSecurity http) {
    var logoutHandler = new OidcClientInitiatedServerLogoutSuccessHandler(repository);
    logoutHandler.setPostLogoutRedirectUri("{baseUrl}");
    var authenticationEntryPoint = new RedirectServerAuthenticationEntryPoint("/oauth2/authorization/keycloak");
    http.authorizeExchange(exchange 
           -> exchange.matchers(PathRequest.toStaticResources().atCommonLocations()).permitAll() //
        .anyExchange().authenticated()) //
        .logout(logout -> logout.logoutSuccessHandler(logoutHandler) //
        .requiresLogout(new PathPatternParserServerWebExchangeMatcher("/logout"))) //
        .oauth2Login(Customizer.withDefaults()) //
        .oauth2ResourceServer(oauth2ResourceServer -> oauth2ResourceServer.jwt())
        .exceptionHandling(exception -> exception.authenticationEntryPoint(authenticationEntryPoint));
    return http.build();
}

And my yaml has the following

spring:
  webflux:
    base-path: /path  # Breaks once this is added
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: http://keycloak/auth/realms/app
      client:
        registration:
          keycloak:
            client-id: client
            client-secret: ea898a70-84f2-408f-9d79-ab6e9eab0aa4
        provider:
          keycloak:
            issuer-uri: http://keycloak/auth/realms/app

Looking at the browser network tab, the final request looks like this

Request URL: http://localhost:8082/path/login/oauth2/code/keycloak?state=KCmpLGwuYQnwEc-mufrJ15LJX8D7LVQsesdDOuDO4Aw%3D&session_state=4a79ae1f-d587-4131-b972-9c740e94cd29&code=03ddb325-11ac-48ff-80b4-5fa97dd72e45.4a79ae1f-d587-4131-b972-9c740e94cd29.6145d626-fbb2-4a78-a04b-977fc60223f2
Request Method: GET
Status Code: 302 Found
Remote Address: 127.0.0.1:8082
Referrer Policy: no-referrer-when-downgrade

HTTP/1.1 302 Found
Location:  # location is blank
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1 ; mode=block
Referrer-Policy: no-referrer
Set-Cookie: SESSION=74b437e4-a788-481c-918e-faab6e3da29d; Path=/path/; HttpOnly; SameSite=Lax
content-length: 0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
Cookie: SESSION=055f12b8-3f4c-4be6-95c3-b8126f733086; SESSION=200328fd-c13e-4887-91c1-d6846a5eae92
Host: localhost:8082
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36

Expected behavior Application should redirect me to initially requested page

ssejinkim commented 4 years ago

@juliuskrah Did you solve this problem?

juliuskrah commented 4 years ago

@codetalkr I took out the context path

ssejinkim commented 4 years ago

me too.

libantema commented 3 years ago

I added an explicit redirect URI to the client registration:

...

spring:
  webflux:
    base-path: /path
  security:
    oauth2:
      client:
        registration:
          keycloak:
            client-id: my-client
            client-secret: my-secret
            redirect-uri: https://host.domain.com/path/login/oauth2/code/{registrationId}
...

Unfortunately, I'm still having issues with logging out when specifying a base-path.

jgrandja commented 3 years ago

@juliuskrah The reason you are seeing

Location: # location is blank

is because the SPRING_SECURITY_SAVED_REQUEST in the session is blank. The value should be http://localhost:8080/path/. The root of this issue is in RedirectServerAuthenticationSuccessHandler which uses WebSessionServerRequestCache.

webflux-context-path

If you try the oauth2Login Servlet sample, you will see that SavedRequestAwareAuthenticationSuccessHandler obtains a DefaultSavedRequest from HttpSessionRequestCache with the value http://localhost:8080/path/ and it works as expected.

Would you be interested in submitting a PR for this fix?

parikshitdutta commented 3 years ago

Hi @jgrandja, you can assign me the task. May I?

jgrandja commented 3 years ago

Thank you @parikshitdutta ! The issue is yours.

libantema commented 3 years ago

Is there a workaround for this bug until it is fixed?

The most obvious workaround to me would be to extend RedirectServerAuthenticationSuccessHandler and just override onAuthenticationSuccess() to be something like this?

@Override
public Mono<Void> onAuthenticationSuccess(WebFilterExchange webFilterExchange, Authentication authentication) {
    ServerWebExchange exchange = webFilterExchange.getExchange();
    return this.requestCache.getRedirectUri(exchange).defaultIfEmpty(this.location).flatMap((location) -> {
        if (location.toASCIIString().isBlank() && this.location.toASCIIString().isBlank()) {
            location = URI.create("/");
        } else if (location.toASCIIString().isBlank()) {
            location = this.location;
        }
        return this.redirectStrategy.sendRedirect(exchange, location);
    });
}

It seems like a huge pain to just copy an entire class almost word-for-word, so I'm hoping someone else is aware of a quicker/simpler workaround.

BrandonSchmitt commented 1 year ago

I ran into the same problem today but what I found out is that it does not work iif the request path equals the base path and does not end in a forward slash. Whether the base path ends in a slash is irrelevant.

So, if you have set spring.webflux.base-path: /path and you request /path, you will end up with /path/login?error and "Invalid credentials" because of the reason described above. However, if you request /path/ or e.g. /path/foo, it works.

As this issue is a bit older and things may have changed in the meantime, here some context about the version, I'm using:

HeneryHawk commented 3 days ago

I came also over this issue and the above workaround by @libantema worked for me. I just had to copy the class RedirectServerAuthenticationSuccessHandler and override the method onAuthenticationSuccess.

@Override
public Mono<Void> onAuthenticationSuccess(WebFilterExchange webFilterExchange, Authentication authentication) {
  ServerWebExchange exchange = webFilterExchange.getExchange();
  return this.requestCache.getRedirectUri(exchange).defaultIfEmpty(this.location).flatMap((location) -> {
      if (location.toASCIIString().isBlank() && this.location.toASCIIString().isBlank()) {
          location = URI.create("/");
      } else if (location.toASCIIString().isBlank()) {
          location = this.location;
      }
      return this.redirectStrategy.sendRedirect(exchange, location);
  });
}

Can this be fixed in the spring security core?

If anybody uses the Spring Cloud Gatway, also the following Issue could be interesting: https://github.com/spring-cloud/spring-cloud-gateway/issues/1935