Spring Boot Keycloak多租户配置

5

我有一个Keycloak实例,并为每个领域创建了两个领域和一个用户。

Realm1 (Tenant1) -> User 1
Realm2 (Tenant2) -> User 2

我有一个Spring Boot应用程序.yml文件(资源服务器-API),针对一个特定的领域并在我的代码中进行了固定。

keycloak:
  realm: Realm1
  auth-server-url: https://localhost:8443/auth
  ssl-required: external
  resource: app
  bearer-only: true
  use-resource-role-mappings: true

它适用于 Realm1 并且已通过验证。

但现在我可以从用户2(租户2)接收请求,令牌将无效,因为公钥(realm1)对于签名请求 JWT 令牌(realm2)无效。

允许多租户和多个领域的最佳方法是什么?如何实现动态配置?

谢谢。

2个回答

5

有一个完整的章节涵盖了这个主题:2.1.18:多租户

不要在Spring应用程序.yaml中定义Keycloak配置,而是保留多个keycloak.json配置文件,并使用自定义的KeycloakConfigResolver:

public class PathBasedKeycloakConfigResolver implements KeycloakConfigResolver {

    @Override
    public KeycloakDeployment resolve(OIDCHttpFacade.Request request) {
        if (request.getPath().startsWith("alternative")) { // or some other criteria 
            InputStream is = getClass().getResourceAsStream("/tenant1-keycloak.json");
            return KeycloakDeploymentBuilder.build(is); //TODO: cache result
        } else {
            InputStream is = getClass().getResourceAsStream("/default-keycloak.json");
            return KeycloakDeploymentBuilder.build(is); //TODO: cache result
        }
    }    
}

我不确定这个方法是否能够与keycloak-spring-boot-starter兼容,但是在KeycloakWebSecurityConfigurerAdapter中引入你的自定义KeycloakConfigResolver可能已经足够了:

@Configuration
@EnableWebSecurity
class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {

    @Bean
    public KeycloakConfigResolver keycloakConfigResolver() {
        return new PathBasedKeycloakConfigResolver();
    }

    [...]
}

1
感谢您的快速回复@GeerPt,我有另一个问题/疑问。 假设我有10个要保护的微服务。我是否需要在每个微服务中定义相同的多个keycloak.json文件?谢谢。 - Lucho82
1
所有的10个微服务都面向用户吗?那是的。否则,这取决于你如何设置后端到后端的安全性。 - GeertPt
想象一下这种情况...前端应用程序(app1)--> API(app2)--> API(app3)。但是,app1可以从realm_1和realm_2进行登录(请求访问令牌)。 对于这个例子,app1应该是公共的,而app2和app3则应该是仅限持有人? - Lucho82
如果您在app1和app2之间使用OAuth2访问令牌,那么app2还需要能够验证来自realm1和realm2的令牌。我没有尝试过这个,但请尝试一下,如果遇到问题,请提出新的问题。 - GeertPt
@Geertpt 亲爱的,我已经在我的应用程序中进行了配置,链接如下:https://stackoverflow.com/questions/76687139/config-issuer-url-for-jwt-when-using-mutli-tenant-config-of-keycloak。现在我面临的问题是,我还配置了jwt转换器来获取角色并使用它们进行身份验证,但是jwt解码器要求提供jwt发行者的配置。因此,我在application.yml中添加了jwt发行者的配置,并且由于每个领域都有多个发行者URL,所以我可以从其他领域验证其他用户。 - kikicoder
显示剩余2条评论

0

import org.keycloak.adapters.springsecurity.KeycloakConfiguration;
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.DependsOn;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;

@DependsOn("keycloakConfigResolver")
@KeycloakConfiguration
@EnableGlobalMethodSecurity(jsr250Enabled = true)
@ConditionalOnProperty(name = "keycloak.enabled", havingValue = "true", matchIfMissing = true)
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
    /**
     * Registers the KeycloakAuthenticationProvider with the authentication manager.
     */
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        KeycloakAuthenticationProvider authenticationProvider = new KeycloakAuthenticationProvider();
        authenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
        auth.authenticationProvider(authenticationProvider);
    }

    /**
     * Defines the session authentication strategy.
     */
    @Bean
    @Override
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        http
                .cors()
                .and()
                .authorizeRequests().antMatchers(HttpMethod.OPTIONS)
                .permitAll()
                .antMatchers("/api-docs/**", "/configuration/ui",
                        "/swagger-resources/**", "/configuration/**", "/v2/api-docs",
                        "/swagger-ui.html/**", "/webjars/**", "/swagger-ui/**")
                .permitAll()
                .anyRequest().authenticated();
    }
}


import org.keycloak.adapters.KeycloakConfigResolver;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.adapters.OIDCHttpFacade;
import java.io.InputStream;
import java.util.concurrent.ConcurrentHashMap;


public class PathBasedConfigResolver implements KeycloakConfigResolver {

    private final ConcurrentHashMap<String, KeycloakDeployment> cache = new ConcurrentHashMap<>();

    @Override
    public KeycloakDeployment resolve(OIDCHttpFacade.Request request) {

        String path = request.getURI();
        String realm = "realmName";
        if (!cache.containsKey(realm)) {
            InputStream is = getClass().getResourceAsStream("/" + realm + "-keycloak.json");
            cache.put(realm, KeycloakDeploymentBuilder.build(is));
        }
        return cache.get(realm);
    }

}



import org.keycloak.adapters.KeycloakConfigResolver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.context.annotation.Bean;

@SpringBootApplication()
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(DIVMasterApplication.class, args);
    }

    @Bean
    @ConditionalOnMissingBean(PathBasedConfigResolver.class)
    public KeycloakConfigResolver keycloakConfigResolver() {
        return new PathBasedConfigResolver();
    }

}


你的回答可以通过提供更多支持信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认你的答案是正确的。您可以在帮助中心中找到有关如何编写良好答案的更多信息。 - Community

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接