Multitenancy next level

52 Views Asked by At

This question is about Spring-Security and Multitenancy.

I'm facing a challenge to implement a multitenancy oAuth2-Security for customers who use JWT tokens with realm roles and other without.

So far we have our own Keycloak server and clients have their tokens with realm-roles. This makes it easy to manage which role has access to which endpoint.

Now we will get new clients who need to use their own IDPs and will call some of our endpoints with their own JWT tokens. There are no roles in there and we additionally need to use their user-info or introspection endpoints to indetify the users.

I need help, how to implement such a monster. The one thing is multitenancy - well described in the docs - different issuers... I know. But how can I secure the endpoints - using still the roles for clients who calls them with our tokens (with realm roles) and for the new clients with their own tokens (without roles)?

1

There are 1 best solutions below

4
ch4mp On

I see different options:

Multi-tenant OAuth2 clients

With this option, this are the clients which are multi-tenant: it fetch tokens from your authorization server before sending a request to your resource server.

This brings with the best flexibility on user roles: all users and roles assignements are managed in your authorization server. The downside being that each user must be declared in your authorization server and will have to login again before he can access your protected resources (if he doesn't have an active session on your authorization server already).

An option to ease this login on your side it to setup "social login" with their external identity provider ("login with PartnerX" button). It can even be transparent if this partners OAuth2 clients send their authorization request with the kc_idp_hint parameter (specific to Keycloak, but as this is what you are using ...).

When you use the identity providers from above, you could add an "Hardcoded Role" mapper to each of the "Identity Provider" to set a different default role to users from the different identity providers.

Multi-tenant OAuth2 resource server

This solution has the advantage for partners OAuth2 clients to use the token they already have from their own authorization server.

You'll have to provide with your own authentication manager that supports a list of trusted issuers.

An option to have distinct authorities (spring naming for "role") for users from each partner would be to do it in the JWT authentication converter (read the iss claim and use the host part of this claim or a static mapping or whatever).

Unfortunately, what is described in the doc for static multi-tenancy is incompatible with what is described for authorities mapping and you'll have to implement the AuthenticationManagerResolver<HttpServletRequest> interface yourself (along with a custom AuthenticationManager using you custom JWT authentication converter).

A simple alternative to writing AuthenticationManagerResolver, AuthenticationManager and Converter<Jwt, ? extends AbstractAuthenticationToken> yourself is using my starter:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>com.c4-soft.springaddons</groupId>
    <artifactId>spring-addons-starter-oidc</artifactId>
    <version>7.3.1</version>
</dependency>
@Configuration
public class SecurityConf {

    @Bean
    ClaimSetAuthoritiesConverter authoritiesConverter() {
        // This is a stupid mapper returning a collection with a single authority corresponding to the issuer host
        // you'll probably need something smarter in production (like using such an authority only for issuers that are not your authorization server and using realm_access.roles for your issuer)
        return claimsMap -> {
            final var claims = new OpenidClaimSet(claimsMap);
            final var host = URI.create(claims.getAsString(JwtClaimNames.ISS)).getHost();
            return List.of(new SimpleGrantedAuthority(host));
        };
    }
}
com:
  c4-soft:
    springaddons:
      oidc:
        ops:
        # this an array, you can add as many trusted issuer as you need
        - iss: https://localhost:8443/realms/master
        - iss: https://oidc.c4-soft.com/auth/realms/quiz