alex90bar
alex90bar

Reputation: 111

Spring Boot OAuth2 - Keycloak - multiple realms for different endpoints

I have a problem with multiple realms usage with Spring Boot and Keycloak.

I configure OAuth2 in my application with 2 realms like this:

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: http://localhost:8080/auth/realms/realmuser
      client:
        provider:
          realmuser:
            props: ...

          realmadmin:
            props: ...

        registration:
          realmuser:
            props: ...

          realmadmin:
            props: ...

I have several endpoints in my application covered by Spring Security.

When I make a request to any of them I get Spring OAuth2 login page where I'm proposed to choose "realmuser" or "realmadmin" to login. After that everything works OK.

But my goal is to divide endpoints by realms.

For example, I'd like endpoints "/v1/admin/etc.." to be redirected to "realmadmin" and endpoints "/v1/user/etc.." to be redirected to "realmuser" immediately, i.e. without Spring OAuth2 login page.

Is there any way to achiveve this?

My SecurityConfig:

  @Bean
  public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.authorizeRequests()
        .antMatchers("/v1/*")
        .hasRole("USER")
        .anyRequest()
        .authenticated()
        .and()
        .cors()
        .and()
        .csrf()
        .disable();
    http.oauth2Login()
        .and()
        .logout()
        .addLogoutHandler(keycloakLogoutHandler)
        .logoutSuccessUrl("/");
    http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
    return http.build();
  }

What I tried to do:

    http.oauth2Login(oauth2Login ->
        oauth2Login
            .authorizationEndpoint(authorizationEndpoint ->
                authorizationEndpoint
                    .baseUri("/oauth2/authorization")
                    .authorizationRequestResolver(new CustomAuthorizationRequestResolver("/oauth2/authorization/realmuser",
                        "/oauth2/authorization/realmadmin", clientRegistrationRepository()))
            )
    )

and then creating class

{

CustomAuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver

//...


    @Override
    public OAuth2AuthorizationRequest resolve(HttpServletRequest request) {
      String authorizationUri;
      if (request.getServletPath().startsWith("/v1/admin")) {
        authorizationUri = adminAuthorizationUri;
      } else if (request.getServletPath().startsWith("/v1/user")) {
        authorizationUri = userAuthorizationUri;
      } else {
        authorizationUri = "/oauth2/authorize";
      }
      OAuth2AuthorizationRequest authorizationRequest = defaultAuthorizationRequestResolver.resolve(request);
      return authorizationRequest != null ? OAuth2AuthorizationRequest.from(authorizationRequest)
          .authorizationUri(authorizationUri)
          .build() : null;
    }

    @Override
    public OAuth2AuthorizationRequest resolve(HttpServletRequest request, String clientRegistrationId) {
      return defaultAuthorizationRequestResolver.resolve(request, clientRegistrationId);
    }

}

None of them helped me to solve my problem.

Upvotes: 0

Views: 1629

Answers (1)

ch4mp
ch4mp

Reputation: 12835

Client and resource server configurations are very different: requests to OAuth2 clients are secured with sessions, when those to resource server are secured with access tokens (which makes it possible to get rid of sessions).

oauth2Login, logout and all spring.security.oauth2.client properties are client features. In the configuration above, session are enabled and required for client configuration. Disabling CSRF protection on an OAuth2 client is very unsafe and should not be done.

If you inspect the requests sent by your browser, you won't find any access token. You'll just find session cookies. You'd have to use a Javascript based framework (Angular, Vue, React, NextJS, ...) and configure it as a public OAuth2 client (with the help of a library) for browser requests to be authorized with access tokens. Another (lately prefered) way to secure requests from Javascript application to resource servers is to insert a middleware on the server (a Backend For Frontend) to hide OAuth2 tokens from Javascript code. In this architecture, the BFF is the OAuth2 client and is responsible for replacing session cookie with an access token before forwarding requests from the browser to the REST API.

Resource server don't care about login, logout or how tokens were obtained or are kept. All that matter to it is if a request is authorized with an access token, if this access token is valid, if it was emitted by an authorization server it trusts and if he should grant access to the requested resources based on token claims.

The resource server configuration above is mostly inoperant and would accept only tokens emitted by the realmuser (provided that you write a client which sends such tokens because, as exposed above, your browser won't if it uses Spring's oauth2Login). Whatever you'll try with JwtIssuerAuthenticationManagerResolver will have no effect on requests secured with sessions (it applies to requests to resource servers, secured with JWT access tokens).

So, the first questions you should answer are those:

  • Are you exposing a REST API you want to be secured with access tokens? If yes, those endpoints should be configured with OAuth2 resource server security
  • Do you expose a templated UI (Thymeleaf or whatever)? If yes, those endpoints should be configured with OAuth2 client security
  • Do you expose both a REST API and a UI to query it? In which case I strongly advise you split client and resource server configurations in distinct security filter-chains (one with client security based on sessions and the other with resource server security based on access tokens)

Once you get a clearer idea of your needs about OAuth2 clients and resource servers (and understood how different the security for the two are), I suggest you refer to the tutorials I wrote, all support multiple authorization servers (or realms).

Upvotes: 1

Related Questions