Search code examples
spring-boothttp-redirectkeycloakreverse-proxy

External keycloak server and Spring boot app behind reverse proxy - redirect to root context after success login


My spring boot service is working behind reverse proxy and secured by external keycloak server.

After successful login at Keycloak server it redirects me to my service and then I get redirect to root of context path instead of initial url.

So request chain is looks like:

initial url: http://~HOSTNAME~/~SERVICE-NAME~/rest/info/654321

and redirects:

http://~HOSTNAME~/~SERVICE-NAME~/rest/sso/login

https://ext-keycloak.server/auth/realms/test/protocol/openid-connect/auth?response_type=code&client_id=dev&redirect_uri=http%3A%2F%2F~HOSTNAME~%2F~SERVICE-NAME~%2Frest%2Fsso%2Flogin&state=60ebad0d-8c68-43cd-9461&login=true&scope=openid

http://~HOSTNAME~/~SERVICE-NAME~/rest/sso/login?state=60ebad0d-8c68-43cd-9461&session_state=074aaa0d-4f72-440e&code=a8c92c50-70f8-438c-4fe311f0b3b6.074aaa0d-440e-8726.8166b689-bbdd-493a-8b8f

http://~HOSTNAME~/~SERVICE-NAME~/rest/ - I have no handlers here and getting error.

First problem was that application generated wrong redirect uri for keycloak. All services are in kubernetes cluster and have urls like: http://~HOSTNAME~/~SERVICE-NAME~/rest (where '/rest' is context path).

~SERVICE-NAME~ part is used to locate service in cluster and application gets request without this prefix. But proxy adds header X-Original-Request with original url and I decided to use it (unfortunately I can't change configuration of proxy and keycloak servers). I made filter to use header value to generate correct redirect uri by copy-pasting from Spring's org.springframework.web.filter.ForwardedHeaderFilter. Now it generates correct redirect_uri but I'm getting wrong redirect at the end as described above.

How can I get redirect to initial page in this case?

Spring security config:

@EnableWebSecurity
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {

    private final PermissionConfig permissionConfig;

    @Autowired
    public SecurityConfig(PermissionConfig permissionConfig) {
        this.permissionConfig = permissionConfig;
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
        keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new NullAuthoritiesMapper());
        auth.authenticationProvider(keycloakAuthenticationProvider);
    }

    @Bean
    public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
        return new KeycloakSpringBootConfigResolver();
    }

    @Bean
    @Override
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        var urlRegistry = http.authorizeRequests()
                .antMatchers("/actuator/**")
                .permitAll()
                .antMatchers("/info/**")
                .hasAnyAuthority(permissionConfig.getRoles().toArray(new String[0]));
    }

    @Bean
    public FilterRegistrationBean<OriginalUriHeaderFilter> originalUriHeaderFilter() {
        OriginalUriHeaderFilter filter = new OriginalUriHeaderFilter();
        FilterRegistrationBean<OriginalUriHeaderFilter> registration = new FilterRegistrationBean<>(filter);
        registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC, DispatcherType.ERROR);
        registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return registration;
    }
}

spring keycloak config (yaml)

keycloak:
  auth-server-url: 'https://ext-keycloak.server/auth/'
  realm: test
  ssl-required: NONE
  resource: dev
  credentials:
    secret: 'hex-value'
  confidential-port: 0
  disable-trust-manager: true

Solution

  • Uh, the problem was with service prefix not with Keycloak.

    When I tryed to get page Spring set JSESSIONID cookie with path=/rest, stored request to session and redirected me to Keycloak. After login Spring couldn't find session and redirected me to root context because browser didn't provide JSESSIONID cookie for path /~SERVICE-NAME~/rest !!

    By default Spring sets cookie path=server.servlet.contextPath. All I've done just added cookie path to application.yaml:

    server:
      port: 8080
      servlet:
        contextPath: /rest
        session:
          cookie:
            path: /
    

    Spring Server Properties

    Spring DebugFilter is quite useful thing