我正在使用Sping Boot Keycloak 17将单片应用程序现代化为基于微服务的应用程序,支持多租户,配置是Keycloak配置文件,具体取决于指向此example的路径
对我来说,它工作正常,可以从json加载部署,登录,下面是应用程序的url,我正在解析“tenant”后的branch 1,没有问题
http://localhost:8100/租户/分支机构1/
主要问题是呈现css和JS文件,这些文件包含租户名称,知道我在多个领域中使用sing WAR
http://localhost:8100/tenant/branch1/resources/bootstrap/js/bootstrap.min.js
--〉返回不存在404
- 包含静态内容的实际代码 *
在jsp文件中,我像以前一样阅读css/js文件<link rel="stylesheet" href="resources/bootstrap/css/bootstrap.min.css">
- keycloal json文件示例 *
{"realm": "branch1",
"auth-server-url": "http://localhost:8181/",
"ssl-required": "external",
"resource": "app",
"public-client": true,
"confidential-port": 0,
"principal-attribute": "preferred_username"}
请告知
1.呈现静态内容
1.在身份验证后返回一个不带tenant/branch 1的URL是否有任何指导,特别是我在应用程序中使用CurrentTenantIdentifierResolver
@ConditionalOnProperty(前缀=“密钥隐藏.配置”,名称=“解析器”,值=“路径”)公共类PathBasedConfigResolver实现密钥隐藏配置解析器{
private final ConcurrentHashMap<String, KeycloakDeployment> cache = new ConcurrentHashMap<>();
@SuppressWarnings("unused")
private static AdapterConfig adapterConfig;
@Override
public KeycloakDeployment resolve(OIDCHttpFacade.Request request) {
System.out.println("inside resolve :: ");
String realm = SubdomainUtils.obtainTenantFromSubdomain(request.getURI());
if (realm.contains("?")) {
realm = realm.split("\\?")[0];
}
if (!cache.containsKey(realm)) {
InputStream is = this.getClass().getResourceAsStream("/" + realm + "-keycloak.json");
cache.put(realm, KeycloakDeploymentBuilder.build(is));
}
return cache.get(realm);
}
static void setAdapterConfig(AdapterConfig adapterConfig) {
PathBasedConfigResolver.adapterConfig = adapterConfig;
}
}
public class SpringKeycloakSecurityConfiguration {
@DependsOn("keycloakConfigResolver")
@KeycloakConfiguration
@ConditionalOnProperty(name = "keycloak.enabled", havingValue = "true", matchIfMissing = true)
public static class KeycloakConfigurationAdapter extends KeycloakWebSecurityConfigurerAdapter {
/**
* Registers the KeycloakAuthenticationProvider with the authentication manager.
*/
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
SimpleAuthorityMapper soa = new SimpleAuthorityMapper();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(soa);
auth.authenticationProvider(keycloakAuthenticationProvider);
}
/**
* Defines the session authentication strategy.
*/
@Bean
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
// required for bearer-only applications.
// return new NullAuthenticatedSessionStrategy();
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
@Override
protected AuthenticationEntryPoint authenticationEntryPoint() throws Exception {
return new MultitenantKeycloakAuthenticationEntryPoint(adapterDeploymentContext());
}
@Override
protected KeycloakAuthenticationProcessingFilter keycloakAuthenticationProcessingFilter() throws Exception {
KeycloakAuthenticationProcessingFilter filter = new KeycloakAuthenticationProcessingFilter(
authenticationManager(), new AntPathRequestMatcher("/tenant/*/sso/login"));
filter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy());
return filter;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Bean
public FilterRegistrationBean keycloakAuthenticationProcessingFilterRegistrationBean(
KeycloakAuthenticationProcessingFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
registrationBean.setEnabled(false);
return registrationBean;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Bean
public FilterRegistrationBean keycloakPreAuthActionsFilterRegistrationBean(
KeycloakPreAuthActionsFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
registrationBean.setEnabled(false);
return registrationBean;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Bean
public FilterRegistrationBean keycloakAuthenticatedActionsFilterBean(
KeycloakAuthenticatedActionsFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
registrationBean.setEnabled(false);
return registrationBean;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Bean
public FilterRegistrationBean keycloakSecurityContextRequestFilterBean(
KeycloakSecurityContextRequestFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
registrationBean.setEnabled(false);
return registrationBean;
}
@Bean
@Override
@ConditionalOnMissingBean(HttpSessionManager.class)
protected HttpSessionManager httpSessionManager() {
return new HttpSessionManager();
}
/**
* Configuration spécifique à keycloak (ajouts de filtres, etc)
*
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
// use previously declared bean
.sessionAuthenticationStrategy(sessionAuthenticationStrategy())
// keycloak filters for securisation
.and().addFilterBefore(keycloakPreAuthActionsFilter(), LogoutFilter.class)
.addFilterBefore(keycloakAuthenticationProcessingFilter(), X509AuthenticationFilter.class)
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint())
.and().logout().addLogoutHandler(keycloakLogoutHandler()).logoutUrl("/tenant/*/logout")
.logoutSuccessHandler(
// logout handler for API
(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) -> response.setStatus(HttpServletResponse.SC_OK))
.and().authorizeRequests().antMatchers("mobileservlet/**").permitAll().antMatchers("**/favicon.ico")
.permitAll().antMatchers("/error").permitAll().antMatchers("/login.go").permitAll()
.antMatchers("/resources/*").permitAll().anyRequest().authenticated();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList(HttpMethod.OPTIONS.name(), "GET", "POST"));
configuration.setAllowedHeaders(
Arrays.asList("Access-Control-Allow-Headers", "Access-Control-Allow-Origin", "Authorization"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
}
public class MultitenantKeycloakAuthenticationEntryPoint extends KeycloakAuthenticationEntryPoint {
public MultitenantKeycloakAuthenticationEntryPoint(AdapterDeploymentContext adapterDeploymentContext) {
super(adapterDeploymentContext);
}
public MultitenantKeycloakAuthenticationEntryPoint(AdapterDeploymentContext adapterDeploymentContext, RequestMatcher apiRequestMatcher) {
super(adapterDeploymentContext, apiRequestMatcher);
}
@Override
protected void commenceLoginRedirect(HttpServletRequest request, HttpServletResponse response) throws IOException {
System.out.println("inside commenceLoginRedirect :: ");
String path = request.getRequestURI();
int multitenantIndex = path.indexOf("tenant/");
if (multitenantIndex == -1) {
throw new IllegalStateException("Not able to resolve the realm from the request path!");
}
String realm = path.substring(path.indexOf("tenant/")).split("/")[1];
if (realm.contains("?")) {
realm = realm.split("\\?")[0];
}
String contextAwareLoginUri = request.getContextPath() + "/tenant/" + realm + DEFAULT_LOGIN_URI;
response.sendRedirect(contextAwareLoginUri);
}
}
1条答案
按热度按时间g52tjvyc1#
坏消息是,你正在使用的Spring的Keycloak适配器非常deprecated。不要使用它。
更好的消息是,我托管了支持多租户的spring-boot starters for resource-servers:接受由多个颁发者颁发的身份(根据您的情况需要多个领域),并使用您想要的Map(控制大小写和前缀)从领域和客户端检索“角色”。它还允许您从属性文件配置“公共”路由和CORS配置(以及其他一些内容)。
两个客户端(
some client
和other-client
)使用的realm1
和other-realm
的配置非常简单:第一个