keycloak-aplcache

Changes

integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/AdapterDeploymentContextBean.java 64(+0 -64)

integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/AdapterDeploymentContextBeanTest.java 56(+0 -56)

Details

diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/client-registration.xml b/docbook/auth-server-docs/reference/en/en-US/modules/client-registration.xml
index fab7119..35f73a3 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/client-registration.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/client-registration.xml
@@ -193,8 +193,8 @@ String initialAccessToken = "eyJhbGciOiJSUzI1NiJ9.eyJqdGkiOiJmMjJmNzQyYy04ZjNlLT
 ClientRepresentation client = new ClientRepresentation();
 client.setClientId(CLIENT_ID);
 
-ClientRegistration reg = ClientRegistration.create().url("http://keycloak/auth/realms/myrealm").build();
-reg.auth(initialAccessToken);
+ClientRegistration reg = ClientRegistration.create().url("http://keycloak/auth/realms/myrealm/clients").build();
+reg.auth(Auth.token(initialAccessToken));
 
 client = reg.create(client);
 
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/spring-security-adapter.xml b/docbook/auth-server-docs/reference/en/en-US/modules/spring-security-adapter.xml
index 33c2aa2..dce3d3d 100644
--- a/docbook/auth-server-docs/reference/en/en-US/modules/spring-security-adapter.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/spring-security-adapter.xml
@@ -1,7 +1,7 @@
 <section id="spring-security-adapter">
     <title>Spring Security Adapter</title>
     <para>
-        To to secure an application with Spring Security and Keyloak, add this adapter as a dependency to your project.
+        To secure an application with Spring Security and Keycloak, add this adapter as a dependency to your project.
         You then have to provide some extra beans in your Spring Security configuration file and add the Keycloak security
         filter to your pipeline.
     </para>
@@ -115,7 +115,10 @@ public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter
         <security:authentication-provider ref="keycloakAuthenticationProvider" />
     </security:authentication-manager>
 
-    <bean id="adapterDeploymentContextBean" class="org.keycloak.adapters.springsecurity.AdapterDeploymentContextBean" />
+    <bean id="adapterDeploymentContext" class="org.keycloak.adapters.springsecurity.AdapterDeploymentContextFactoryBean">
+        <constructor-arg value="/WEB-INF/keycloak.json" />
+    </bean>
+
     <bean id="keycloakAuthenticationEntryPoint" class="org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationEntryPoint" />
     <bean id="keycloakAuthenticationProvider" class="org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider" />
     <bean id="keycloakPreAuthActionsFilter" class="org.keycloak.adapters.springsecurity.filter.KeycloakPreAuthActionsFilter" />
@@ -124,7 +127,7 @@ public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter
     </bean>
 
     <bean id="keycloakLogoutHandler" class="org.keycloak.adapters.springsecurity.authentication.KeycloakLogoutHandler">
-            <constructor-arg ref="adapterDeploymentContextBean" />
+        <constructor-arg ref="adapterDeploymentContext" />
     </bean>
 
     <bean id="logoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">
@@ -158,12 +161,29 @@ public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter
         </section>
     </section>
     <section>
+        <title>Multi Tenancy</title>
+        <para>
+            The Keycloak Spring Security adapter also supports multi tenancy. Instead of injecting
+            <literal>AdapterDeploymentContextFactoryBean</literal> with the path to <literal>keycloak.json</literal> you
+            can inject an implementation of the <literal>KeycloakConfigResolver</literal> interface. More details on how
+            to implement the <literal>KeycloakConfigResolver</literal> can be found in <xref linkend="multi_tenancy" />.
+        </para>
+    </section>
+    <section>
         <title>Naming Security Roles</title>
         <para>
             Spring Security, when using role-based authentication, requires that role names start with <code>ROLE_</code>.
             For example, an administrator role must be declared in Keycloak as <code>ROLE_ADMIN</code> or similar, not simply
             <code>ADMIN</code>.
         </para>
+        <para>
+            The class <code>org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider</code>
+            supports an optional <code>org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper</code>
+            which can be used to map roles coming from Keycloak to roles recognized by Spring Security. Use, for example,
+            <code>org.springframework.security.core.authority.mapping.SimpleAuthorityMapper</code> to insert the
+            <code>ROLE_</code> prefix and convert the role name to upper case. The class is part of Spring Security
+            Core module.
+        </para>
     </section>
     <section>
         <title>Client to Client Support</title>
diff --git a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_pt_BR.properties b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_pt_BR.properties
index 37c0106..161ae60 100644
--- a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_pt_BR.properties
+++ b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_pt_BR.properties
@@ -52,8 +52,10 @@ role_manage-events=Gerencia eventos
 role_view-profile=Visualiza perfil
 role_manage-account=Gerencia conta
 role_read-token=L\u00EA token
+role_offline-access=Acesso Offline
 client_account=Conta
 client_security-admin-console=Console de Administra\u00E7\u00E3o de Seguran\u00E7a
+client_admin-cli=Admin CLI
 client_realm-management=Gerenciamento de Realm
 client_broker=Broker
 
@@ -85,9 +87,11 @@ application=Aplicativo
 availablePermissions=Permiss\u00F5es Dispon\u00EDveis
 grantedPermissions=Permiss\u00F5es Concedidas
 grantedPersonalInfo=Informa\u00E7\u00F5es Pessoais Concedidas
+additionalGrants=Concess\u00F5es Adicionais
 action=A\u00E7\u00E3o
 inResource=em
 fullAccess=Acesso Completo
+offlineToken=Offline Token
 revoke=Revogar Concess\u00F5es
 
 configureAuthenticators=Autenticadores Configurados
@@ -130,6 +134,7 @@ federatedIdentityLinkNotActiveMessage=Esta identidade n\u00E3o est\u00E1 mais em
 federatedIdentityRemovingLastProviderMessage=Voc\u00EA n\u00E3o pode remover a \u00FAltima identidade federada como voc\u00EA n\u00E3o tem senha
 identityProviderRedirectErrorMessage=Falha ao redirecionar para o provedor de identidade
 identityProviderRemovedMessage=Provedor de identidade removido com sucesso
+identityProviderAlreadyLinkedMessage=Identidade federada retornado por {0} j\u00E1 est\u00E1 ligado a outro usu\u00E1rio.
 
 accountDisabledMessage=Conta desativada, contate o administrador
 
@@ -147,4 +152,4 @@ locale_de=Deutsch
 locale_en=English
 locale_it=Italian
 locale_pt-BR=Portugu\u00EAs (BR)
-locale_fr=Fran\u00e7ais
\ No newline at end of file
+locale_fr=Fran\u00E7ais
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index 1fc4e75..0cd4acc 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -492,7 +492,9 @@ client.description.tooltip=Specifies description of the client. For example 'My 
 
 expires=Expires
 expiration=Expiration
+expiration.tooltip=Specifies how long the token should be valid
 count=Count
+count.tooltip=Specifies how many clients can be created using the token
 remainingCount=Remaining count
 created=Created
 back=Back
diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties
index a144120..58c91c8 100644
--- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties
+++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties
@@ -15,9 +15,9 @@ kerberosNotConfiguredTitle=Kerberos N\u00E3o Configurado
 bypassKerberosDetail=Ou voc\u00EA n\u00E3o est\u00E1 logado via Kerberos ou o seu navegador n\u00E3o est\u00E1 configurado para login Kerberos. Por favor, clique em continuar para fazer o login no atrav\u00E9s de outros meios
 kerberosNotSetUp=Kerberos n\u00E3o est\u00E1 configurado. Voc\u00EA n\u00E3o pode acessar.
 registerWithTitle=Registre-se com {0}
-registerWithTitleHtml={0}
+registerWithTitleHtml=Registre-se com <strong>{0}</strong>
 loginTitle=Entrar em {0}
-loginTitleHtml={0}
+loginTitleHtml=Entrar em <strong>{0}</strong>
 impersonateTitle={0} Impersonate User
 impersonateTitleHtml=<strong>{0}</strong> Impersonate User</strong>
 realmChoice=Realm
@@ -26,7 +26,7 @@ loginTotpTitle=Configura\u00E7\u00E3o do autenticador mobile
 loginProfileTitle=Atualiza\u00E7\u00E3o das Informa\u00E7\u00F5es da Conta
 loginTimeout=Voc\u00EA demorou muito para entrar. Por favor, refa\u00E7a o processo de login a partir do in\u00EDcio.
 oauthGrantTitle=Concess\u00E3o OAuth
-oauthGrantTitleHtml={0}
+oauthGrantTitleHtml=Acesso tempor\u00E1rio para <strong>{0}</strong> solicitado pela
 errorTitle=N\u00F3s lamentamos...
 errorTitleHtml=N\u00F3s <strong>lamentamos</strong> ...
 emailVerifyTitle=Verifica\u00E7\u00E3o de e-mail
@@ -39,9 +39,9 @@ termsTitle=Termos e Condi\u00E7\u00F5es
 termsTitleHtml=Termos e Condi\u00E7\u00F5es
 termsText=<p>Termos e Condi\u00E7\u00F5es a ser definido</p>
 
-recaptchaFailed=Invalid Recaptcha
-recaptchaNotConfigured=Recaptcha is required, but not configured
-consentDenied=Consent denied.
+recaptchaFailed=Recaptcha inv\u00E1lido
+recaptchaNotConfigured=Recaptcha \u00E9 requerido, mas n\u00E3o foi configurado
+consentDenied=Consentimento negado.
 
 noAccount=Novo usu\u00E1rio?
 username=Nome de usu\u00E1rio
@@ -79,6 +79,11 @@ emailVerifyInstruction1=Um e-mail com instru\u00E7\u00F5es para verificar o seu 
 emailVerifyInstruction2=Voc\u00EA n\u00E3o recebeu um c\u00F3digo de verifica\u00E7\u00E3o em seu e-mail?
 emailVerifyInstruction3=para reenviar o e-mail.
 
+emailLinkIdpTitle=Vincular {0}
+emailLinkIdp1=Um email com instru\u00E7\u00F5es para vincular a conta {0} {1} com sua conta {2} foi enviado para voc\u00EA.
+emailLinkIdp2=N\u00E3o recebeu um c\u00F3digo de verifica\u00E7\u00E3o no e-mail?
+emailLinkIdp3=para reenviar o email.
+
 backToLogin=&laquo; Voltar
 
 emailInstruction=Digite seu nome de usu\u00E1rio ou endere\u00E7o de email e n\u00F3s lhe enviaremos instru\u00E7\u00F5es sobre como criar uma nova senha.
@@ -89,6 +94,7 @@ personalInfo=Informa\u00E7\u00F5es Pessoais:
 role_admin=Admininstrador
 role_realm-admin=Administra Realm
 role_create-realm=Cria realm
+role_create-client=Cria cliente
 role_view-realm=Visualiza realm
 role_view-users=Visualiza usu\u00E1rios
 role_view-applications=Visualiza aplicativos
@@ -104,8 +110,10 @@ role_manage-events=Gerencia eventos
 role_view-profile=Visualiza perfil
 role_manage-account=Gerencia contas
 role_read-token=L\u00EA token
+role_offline-access=Acesso offline
 client_account=Conta
 client_security-admin-console=Console de Administra\u00E7\u00E3o de Seguran\u00E7a
+client_admin-cli=Admin CLI
 client_realm-management=Gerenciamento de Realm
 client_broker=Broker
 
@@ -130,13 +138,19 @@ invalidTotpMessage=C\u00F3digo autenticador inv\u00E1lido.
 usernameExistsMessage=Nome de usu\u00E1rio j\u00E1 existe.
 emailExistsMessage=Email j\u00E1 existe.
 
-federatedIdentityEmailExistsMessage=J\u00E1 existe usu\u00E1rio com este email. Por favor acesse sua conta de gest\u00E3o para vincular a conta.
-federatedIdentityUsernameExistsMessage=J\u00E1 existe usu\u00E1rio com este nome de usu\u00E1rio. Por favor acessar sua conta de gest\u00E3o para vincular a conta.
+federatedIdentityExistsMessage=Usu\u00E1rio com {0} {1} j\u00E1 existe. Por favor, entre em gerenciamento de contas para vincular a conta.
+
+confirmLinkIdpTitle=Conta j\u00E1 existente
+federatedIdentityConfirmLinkMessage=Usu\u00E1rio com {0} {1} j\u00E1 existe. Como voc\u00EA quer continuar?
+federatedIdentityConfirmReauthenticateMessage=Autenticar como {0} para vincular sua conta com {1}
+confirmLinkIdpReviewProfile=Revisar informa\u00E7\u00F5es do perfil
+confirmLinkIdpContinue=Vincular {0} com uma conta existente
 
 configureTotpMessage=Voc\u00EA precisa configurar seu celular com o autenticador Mobile para ativar sua conta.
 updateProfileMessage=Voc\u00EA precisa atualizar o seu perfil de usu\u00E1rio para ativar sua conta.
 updatePasswordMessage=Voc\u00EA precisa mudar sua senha para ativar sua conta.
 verifyEmailMessage=Voc\u00EA precisa verificar o seu endere\u00E7o de e-mail para ativar sua conta.
+linkIdpMessage=Voc\u00EA precisa confirmar o seu endere\u00E7o de e-mail para vincular sua conta com {0}.
 
 emailSentMessage=Voc\u00EA dever\u00E1 receber um e-mail em breve com mais instru\u00E7\u00F5es.
 emailSendErrorMessage=Falha ao enviar e-mail, por favor, tente novamente mais tarde
@@ -163,22 +177,23 @@ failedLogout=Falha ao sair
 unknownLoginRequesterMessage=Solicitante de login desconhecido
 loginRequesterNotEnabledMessage=Solicitante de login desativado
 bearerOnlyMessage=Aplicativos somente ao portador n\u00E3o tem permiss\u00E3o para iniciar o login pelo navegador
-directGrantsOnlyMessage=Clientes de concess\u00E3o direta n\u00E3o tem permiss\u00E3o para iniciar o login pelo navegador
+standardFlowDisabledMessage=Cliente n\u00E3o tem permiss\u00E3o para iniciar o login com response_type informado. O fluxo padr\u00E3o est\u00E1 desabilitado para o cliente.
+implicitFlowDisabledMessage=Cliente n\u00E3o tem permiss\u00E3o para iniciar o login com response_type informado. O fluxo padr\u00E3o est\u00E1 desabilitado para o cliente.
 invalidRedirectUriMessage=URI de redirecionamento inv\u00E1lido
 unsupportedNameIdFormatMessage=NameIDFormat n\u00E3o suportado
 invlidRequesterMessage=Solicitante inv\u00E1lido
 registrationNotAllowedMessage=Registro n\u00E3o permitido.
-resetCredentialNotAllowedMessage=Reset Credential not allowed
+resetCredentialNotAllowedMessage=N\u00E3o \u00E9 permitido redefinir credencial.
 
 permissionNotApprovedMessage=Permiss\u00E3o n\u00E3o aprovada.
 noRelayStateInResponseMessage=Sem estado de retransmiss\u00E3o na resposta do provedor de identidade.
-identityProviderAlreadyLinkedMessage=A identidade retornado pelo provedor de identidade j\u00E1 est\u00E1 vinculado a outro usu\u00E1rio.
 insufficientPermissionMessage=Permiss\u00F5es insuficientes para vincular identidades.
 couldNotProceedWithAuthenticationRequestMessage=N\u00E3o foi poss\u00EDvel proceder \u00E0 solicita\u00E7\u00E3o de autentica\u00E7\u00E3o para provedor de identidade.
 couldNotObtainTokenMessage=N\u00E3o foi poss\u00EDvel obter token do provedor de identidade.
 unexpectedErrorRetrievingTokenMessage=Erro inesperado ao recuperar token do provedor de identidade.
 unexpectedErrorHandlingResponseMessage=Erro inesperado ao manusear resposta do provedor de identidade.
 identityProviderAuthenticationFailedMessage=Falha na autentica\u00E7\u00E3o. N\u00E3o foi poss\u00EDvel autenticar com o provedor de identidade.
+identityProviderDifferentUserMessage=Autenticado como {0}, mas era esperado ser autenticado como {1}
 couldNotSendAuthenticationRequestMessage=N\u00E3o foi poss\u00EDvel enviar solicita\u00E7\u00E3o de autentica\u00E7\u00E3o para o provedor de identidade.
 unexpectedErrorHandlingRequestMessage=Erro inesperado ao manusear pedido de autentica\u00E7\u00E3o para provedor de identidade.
 invalidAccessCodeMessage=C\u00F3digo de acesso inv\u00E1lido.
@@ -186,6 +201,7 @@ sessionNotActiveMessage=Sess\u00E3o inativa.
 invalidCodeMessage=C\u00F3digo inv\u00E1lido, por favor fa\u00E7a login novamente atrav\u00E9s de sua aplica\u00E7\u00E3o.
 identityProviderUnexpectedErrorMessage=Erro inesperado durante a autentica\u00E7\u00E3o com o provedor de identidade
 identityProviderNotFoundMessage=N\u00E3o foi poss\u00EDvel encontrar um provedor de identidade com o identificador.
+identityProviderLinkSuccess=Sua conta foi vinculada com sucesso com {0} conta {1} .
 realmSupportsNoCredentialsMessage=O realm n\u00E3o suporta qualquer tipo de credencial.
 identityProviderNotUniqueMessage=O realm suporta m\u00FAltiplos provedores de identidade. N\u00E3o foi poss\u00EDvel determinar qual o provedor de identidade deve ser usado para se autenticar.
 emailVerifiedMessage=O seu endere\u00E7o de e-mail foi confirmado.
@@ -194,7 +210,7 @@ locale_de=Deutsch
 locale_en=English
 locale_it=Italian
 locale_pt-BR=Portugu\u00EAs (BR)
-locale_fr=Fran\u00e7ais
+locale_fr=Fran\u00E7ais
 locale_es=Espa\u00F1ol
 
 backToApplication=&laquo; Voltar para o aplicativo
diff --git a/forms/common-themes/src/main/resources/theme/keycloak/email/messages/messages_pt_BR.properties b/forms/common-themes/src/main/resources/theme/keycloak/email/messages/messages_pt_BR.properties
index 06b4647..6596fba 100755
--- a/forms/common-themes/src/main/resources/theme/keycloak/email/messages/messages_pt_BR.properties
+++ b/forms/common-themes/src/main/resources/theme/keycloak/email/messages/messages_pt_BR.properties
@@ -1,6 +1,9 @@
 emailVerificationSubject=Verifica\u00E7\u00E3o de e-mail
 emailVerificationBody=Algu\u00E9m criou uma conta {2} com este endere\u00E7o de e-mail. Se foi voc\u00EA, clique no link abaixo para verificar o seu endere\u00E7o de email\n\n{0}\n\nEste link ir\u00E1 expirar dentro de {1} minutos.\n\nSe n\u00E3o foi voc\u00EA que criou esta conta, basta ignorar esta mensagem.
 emailVerificationBodyHtml=<p>Algu\u00E9m criou uma conta {2} com este endere\u00E7o de e-mail. Se foi voc\u00EA, clique no link abaixo para verificar o seu endere\u00E7o de email</p><p><a href="{0}">{0}</a></p><p>Este link ir\u00E1 expirar dentro de {1} minutos.</p><p>Se n\u00E3o foi voc\u00EA que criou esta conta, basta ignorar esta mensagem.</p>
+identityProviderLinkSubject=Vincular {0}
+identityProviderLinkBody=Algu\u00E9m quer vincular sua conta "{1}" com a conta "{0}" do usu\u00E1rio {2} . Se foi voc\u00EA, clique no link abaixo para vincular as contas.\n\n{3}\n\nEste link ir\u00E1 expirar em {4} minutos.\n\nSe voc\u00EA n\u00E3o quer vincular a conta, apenas ignore esta mensagem. Se voc\u00EA vincular as contas, voc\u00EA ser\u00E1 capaz de logar em {1} atr\u00E1v\u00E9s de {0}.
+identityProviderLinkBodyHtml=<p>Algu\u00E9m quer vincular sua conta <b>{1}</b> com a conta <b>{0}</b> do usu\u00E1rio {2} . Se foi voc\u00EA, clique no link abaixo para vincular as contas.</p><p><a href="{3}">{3}</a></p><p>Este link ir\u00E1 expirar em {4} minutos.</p><p>Se voc\u00EA n\u00E3o quer vincular a conta, apenas ignore esta mensagem. Se voc\u00EA vincular as contas, voc\u00EA ser\u00E1 capaz de logar em {1} atr\u00E1v\u00E9s de {0}.</p>
 passwordResetSubject=Redefini\u00E7\u00E3o de senha
 passwordResetBody=Someone just requested to change your {2} account''s credentials. If this was you, click on the link below to reset them.\n\n{0}\n\nThis link and code will expire within {1} minutes.\n\nIf you don''t want to reset your credentials, just ignore this message and nothing will be changed.
 passwordResetBodyHtml=<p>Someone just requested to change your {2} account''s credentials. If this was you, click on the link below to reset them.</p><p><a href="{0}">{0}</a></p><p>This link will expire within {1} minutes.</p><p>If you don''t want to reset your credentials, just ignore this message and nothing will be changed.</p>
@@ -18,4 +21,4 @@ eventUpdatePasswordBody=Sua senha foi alterada em {0} de {1}. Se n\u00E3o foi vo
 eventUpdatePasswordBodyHtml=<p>Sua senha foi alterada em {0} de {1}. Se n\u00E3o foi voc\u00EA, por favor, entre em contato com um administrador.</p>
 eventUpdateTotpSubject=Atualiza\u00E7\u00E3o TOTP
 eventUpdateTotpBody=TOTP foi atualizado para a sua conta em {0} de {1}. Se n\u00E3o foi voc\u00EA, por favor, entre em contato com um administrador.
-eventUpdateTotpBodyHtml=<p>TOTP foi atualizado para a sua conta em {0} de {1}. Se n\u00E3o foi voc\u00EA, por favor, entre em contato com um administrador.</p>
+eventUpdateTotpBodyHtml=<p>TOTP foi atualizado para a sua conta em {0} de {1}. Se n\u00E3o foi voc\u00EA, por favor, entre em contato com um administrador.</p>
\ No newline at end of file
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/AdapterDeploymentContextFactoryBean.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/AdapterDeploymentContextFactoryBean.java
new file mode 100644
index 0000000..d089ded
--- /dev/null
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/AdapterDeploymentContextFactoryBean.java
@@ -0,0 +1,79 @@
+package org.keycloak.adapters.springsecurity;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Objects;
+
+import org.keycloak.adapters.AdapterDeploymentContext;
+import org.keycloak.adapters.KeycloakConfigResolver;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.KeycloakDeploymentBuilder;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.core.io.Resource;
+
+/**
+ * {@link FactoryBean} that creates an {@link AdapterDeploymentContext} given a {@link Resource} defining the Keycloak
+ * client configuration or a {@link KeycloakConfigResolver} for multi-tenant environments.
+ *
+ * @author <a href="mailto:thomas.raehalme@aitiofinland.com">Thomas Raehalme</a>
+ */
+public class AdapterDeploymentContextFactoryBean
+        implements FactoryBean<AdapterDeploymentContext>, InitializingBean {
+    private static final Logger log =
+        LoggerFactory.getLogger(AdapterDeploymentContextFactoryBean.class);
+    private final Resource keycloakConfigFileResource;
+    private final KeycloakConfigResolver keycloakConfigResolver;
+    private AdapterDeploymentContext adapterDeploymentContext;
+
+    public AdapterDeploymentContextFactoryBean(Resource keycloakConfigFileResource) {
+        this.keycloakConfigFileResource = Objects.requireNonNull(keycloakConfigFileResource);
+        this.keycloakConfigResolver = null;
+    }
+
+    public AdapterDeploymentContextFactoryBean(KeycloakConfigResolver keycloakConfigResolver) {
+        this.keycloakConfigResolver = Objects.requireNonNull(keycloakConfigResolver);
+        this.keycloakConfigFileResource = null;
+    }
+
+    @Override
+    public Class<?> getObjectType() {
+        return AdapterDeploymentContext.class;
+    }
+
+    @Override
+    public boolean isSingleton() {
+        return true;
+    }
+
+    @Override
+    public void afterPropertiesSet() throws Exception {
+        if (keycloakConfigResolver != null) {
+            adapterDeploymentContext = new AdapterDeploymentContext(keycloakConfigResolver);
+        }
+        else {
+            log.info("Loading Keycloak deployment from configuration file: {}", keycloakConfigFileResource);
+
+            KeycloakDeployment deployment = loadKeycloakDeployment();
+            adapterDeploymentContext = new AdapterDeploymentContext(deployment);
+        }
+    }
+
+    private KeycloakDeployment loadKeycloakDeployment() throws IOException {
+        if (!keycloakConfigFileResource.isReadable()) {
+            throw new FileNotFoundException(String.format("Unable to locate Keycloak configuration file: %s",
+                    keycloakConfigFileResource.getFilename()));
+        }
+
+        return KeycloakDeploymentBuilder.build(keycloakConfigFileResource.getInputStream());
+    }
+
+    @Override
+    public AdapterDeploymentContext getObject() throws Exception {
+        return adapterDeploymentContext;
+    }
+}
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/authentication/KeycloakLogoutHandler.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/authentication/KeycloakLogoutHandler.java
index 27178ca..c17dca1 100644
--- a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/authentication/KeycloakLogoutHandler.java
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/authentication/KeycloakLogoutHandler.java
@@ -1,8 +1,10 @@
 package org.keycloak.adapters.springsecurity.authentication;
 
+import org.keycloak.adapters.AdapterDeploymentContext;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
-import org.keycloak.adapters.springsecurity.AdapterDeploymentContextBean;
+import org.keycloak.adapters.spi.HttpFacade;
+import org.keycloak.adapters.springsecurity.facade.SimpleHttpFacade;
 import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -23,11 +25,11 @@ public class KeycloakLogoutHandler implements LogoutHandler {
 
     private static final Logger log = LoggerFactory.getLogger(KeycloakLogoutHandler.class);
 
-    private AdapterDeploymentContextBean deploymentContextBean;
+    private AdapterDeploymentContext adapterDeploymentContext;
 
-    public KeycloakLogoutHandler(AdapterDeploymentContextBean deploymentContextBean) {
-        Assert.notNull(deploymentContextBean);
-        this.deploymentContextBean = deploymentContextBean;
+    public KeycloakLogoutHandler(AdapterDeploymentContext adapterDeploymentContext) {
+        Assert.notNull(adapterDeploymentContext);
+        this.adapterDeploymentContext = adapterDeploymentContext;
     }
 
     @Override
@@ -45,7 +47,8 @@ public class KeycloakLogoutHandler implements LogoutHandler {
     }
 
     protected void handleSingleSignOut(HttpServletRequest request, HttpServletResponse response, KeycloakAuthenticationToken authenticationToken) {
-        KeycloakDeployment deployment = deploymentContextBean.getDeployment();
+        HttpFacade facade = new SimpleHttpFacade(request, response);
+        KeycloakDeployment deployment = adapterDeploymentContext.resolveDeployment(facade);
         RefreshableKeycloakSecurityContext session = (RefreshableKeycloakSecurityContext) authenticationToken.getAccount().getKeycloakSecurityContext();
         session.logout(deployment);
     }
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/config/KeycloakWebSecurityConfigurerAdapter.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/config/KeycloakWebSecurityConfigurerAdapter.java
index b5ef665..55c6b3c 100644
--- a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/config/KeycloakWebSecurityConfigurerAdapter.java
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/config/KeycloakWebSecurityConfigurerAdapter.java
@@ -1,6 +1,8 @@
 package org.keycloak.adapters.springsecurity.config;
 
-import org.keycloak.adapters.springsecurity.AdapterDeploymentContextBean;
+import org.keycloak.adapters.AdapterDeploymentContext;
+import org.keycloak.adapters.KeycloakConfigResolver;
+import org.keycloak.adapters.springsecurity.AdapterDeploymentContextFactoryBean;
 import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationEntryPoint;
 import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
 import org.keycloak.adapters.springsecurity.authentication.KeycloakLogoutHandler;
@@ -8,6 +10,7 @@ import org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcess
 import org.keycloak.adapters.springsecurity.filter.KeycloakCsrfRequestMatcher;
 import org.keycloak.adapters.springsecurity.filter.KeycloakPreAuthActionsFilter;
 import org.keycloak.adapters.springsecurity.management.HttpSessionManager;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Bean;
 import org.springframework.core.io.Resource;
@@ -35,10 +38,20 @@ public abstract class KeycloakWebSecurityConfigurerAdapter extends WebSecurityCo
 
     @Value("${keycloak.configurationFile:WEB-INF/keycloak.json}")
     private Resource keycloakConfigFileResource;
+    @Autowired(required = false)
+    private KeycloakConfigResolver keycloakConfigResolver;
 
     @Bean
-    protected AdapterDeploymentContextBean adapterDeploymentContextBean() {
-        return new AdapterDeploymentContextBean(keycloakConfigFileResource);
+    protected AdapterDeploymentContext adapterDeploymentContext() throws Exception {
+        AdapterDeploymentContextFactoryBean factoryBean;
+        if (keycloakConfigResolver != null) {
+             factoryBean = new AdapterDeploymentContextFactoryBean(keycloakConfigResolver);
+        }
+        else {
+            factoryBean = new AdapterDeploymentContextFactoryBean(keycloakConfigFileResource);
+        }
+        factoryBean.afterPropertiesSet();
+        return factoryBean.getObject();
     }
 
     protected AuthenticationEntryPoint authenticationEntryPoint() {
@@ -70,8 +83,8 @@ public abstract class KeycloakWebSecurityConfigurerAdapter extends WebSecurityCo
         return new HttpSessionManager();
     }
 
-    protected KeycloakLogoutHandler keycloakLogoutHandler() {
-        return new KeycloakLogoutHandler(adapterDeploymentContextBean());
+    protected KeycloakLogoutHandler keycloakLogoutHandler() throws Exception {
+        return new KeycloakLogoutHandler(adapterDeploymentContext());
     }
 
     protected abstract SessionAuthenticationStrategy sessionAuthenticationStrategy();
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilter.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilter.java
index 965c162..04c6ed3 100644
--- a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilter.java
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilter.java
@@ -1,11 +1,12 @@
 package org.keycloak.adapters.springsecurity.filter;
 
+import org.keycloak.adapters.AdapterDeploymentContext;
 import org.keycloak.adapters.AdapterTokenStore;
 import org.keycloak.adapters.spi.AuthChallenge;
 import org.keycloak.adapters.spi.AuthOutcome;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.RequestAuthenticator;
-import org.keycloak.adapters.springsecurity.AdapterDeploymentContextBean;
+import org.keycloak.adapters.spi.HttpFacade;
 import org.keycloak.adapters.springsecurity.KeycloakAuthenticationException;
 import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationEntryPoint;
 import org.keycloak.adapters.springsecurity.authentication.SpringSecurityRequestAuthenticator;
@@ -56,7 +57,7 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
     private static final Logger log = LoggerFactory.getLogger(KeycloakAuthenticationProcessingFilter.class);
 
     private ApplicationContext applicationContext;
-    private AdapterDeploymentContextBean adapterDeploymentContextBean;
+    private AdapterDeploymentContext adapterDeploymentContext;
     private AdapterTokenStoreFactory adapterTokenStoreFactory = new SpringSecurityAdapterTokenStoreFactory();
     private AuthenticationManager authenticationManager;
 
@@ -100,7 +101,7 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
 
     @Override
     public void afterPropertiesSet() {
-        adapterDeploymentContextBean = applicationContext.getBean(AdapterDeploymentContextBean.class);
+        adapterDeploymentContext = applicationContext.getBean(AdapterDeploymentContext.class);
         super.afterPropertiesSet();
     }
 
@@ -110,8 +111,8 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
 
         log.debug("Attempting Keycloak authentication");
 
-        KeycloakDeployment deployment = adapterDeploymentContextBean.getDeployment();
-        SimpleHttpFacade facade = new SimpleHttpFacade(request, response);
+        HttpFacade facade = new SimpleHttpFacade(request, response);
+        KeycloakDeployment deployment = adapterDeploymentContext.resolveDeployment(facade);
         AdapterTokenStore tokenStore = adapterTokenStoreFactory.createAdapterTokenStore(deployment, request);
         RequestAuthenticator authenticator
                 = new SpringSecurityRequestAuthenticator(facade, request, deployment, tokenStore, -1);
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/KeycloakPreAuthActionsFilter.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/KeycloakPreAuthActionsFilter.java
index 2363b3f..565ae62 100755
--- a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/KeycloakPreAuthActionsFilter.java
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/KeycloakPreAuthActionsFilter.java
@@ -5,7 +5,6 @@ import org.keycloak.adapters.spi.HttpFacade;
 import org.keycloak.adapters.NodesRegistrationManagement;
 import org.keycloak.adapters.PreAuthActionsHandler;
 import org.keycloak.adapters.spi.UserSessionManagement;
-import org.keycloak.adapters.springsecurity.AdapterDeploymentContextBean;
 import org.keycloak.adapters.springsecurity.facade.SimpleHttpFacade;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -47,9 +46,7 @@ public class KeycloakPreAuthActionsFilter extends GenericFilterBean implements A
 
     @Override
     protected void initFilterBean() throws ServletException {
-        AdapterDeploymentContextBean contextBean = applicationContext.getBean(AdapterDeploymentContextBean.class);
-        deploymentContext = contextBean.getDeploymentContext();
-        management.tryRegister(contextBean.getDeployment());
+        deploymentContext = applicationContext.getBean(AdapterDeploymentContext.class);
     }
 
     @Override
diff --git a/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/AdapterDeploymentContextFactoryBeanTest.java b/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/AdapterDeploymentContextFactoryBeanTest.java
new file mode 100644
index 0000000..6a3b39f
--- /dev/null
+++ b/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/AdapterDeploymentContextFactoryBeanTest.java
@@ -0,0 +1,77 @@
+package org.keycloak.adapters.springsecurity;
+
+import java.io.FileNotFoundException;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import org.keycloak.adapters.KeycloakConfigResolver;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.spi.HttpFacade;
+
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+
+import static org.junit.Assert.assertNotNull;
+
+public class AdapterDeploymentContextFactoryBeanTest {
+    @Rule
+    public ExpectedException expectedException = ExpectedException.none();
+
+    private AdapterDeploymentContextFactoryBean adapterDeploymentContextFactoryBean;
+
+    @Test
+    public void should_create_adapter_deployment_context_from_configuration_file() throws Exception {
+        // given:
+        adapterDeploymentContextFactoryBean = new AdapterDeploymentContextFactoryBean(getCorrectResource());
+
+        // when:
+        adapterDeploymentContextFactoryBean.afterPropertiesSet();
+
+        // then
+        assertNotNull(adapterDeploymentContextFactoryBean.getObject());
+    }
+
+    private Resource getCorrectResource() {
+        return new ClassPathResource("keycloak.json");
+    }
+
+    @Test
+    public void should_throw_exception_when_configuration_file_was_not_found() throws Exception {
+        // given:
+        adapterDeploymentContextFactoryBean = new AdapterDeploymentContextFactoryBean(getEmptyResource());
+
+        // then:
+        expectedException.expect(FileNotFoundException.class);
+        expectedException.expectMessage("Unable to locate Keycloak configuration file: no-file.json");
+
+        // when:
+        adapterDeploymentContextFactoryBean.afterPropertiesSet();
+    }
+
+    private Resource getEmptyResource() {
+        return new ClassPathResource("no-file.json");
+    }
+
+    @Test
+    public void should_create_adapter_deployment_context_from_keycloak_config_resolver() throws Exception {
+        // given:
+        adapterDeploymentContextFactoryBean = new AdapterDeploymentContextFactoryBean(getKeycloakConfigResolver());
+
+        // when:
+        adapterDeploymentContextFactoryBean.afterPropertiesSet();
+
+        // then:
+        assertNotNull(adapterDeploymentContextFactoryBean.getObject());
+    }
+
+    private KeycloakConfigResolver getKeycloakConfigResolver() {
+        return new KeycloakConfigResolver() {
+            @Override
+            public KeycloakDeployment resolve(HttpFacade.Request facade) {
+                return null;
+            }
+        };
+    }
+}
diff --git a/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/authentication/KeycloakLogoutHandlerTest.java b/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/authentication/KeycloakLogoutHandlerTest.java
index cc751c4..2f44107 100755
--- a/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/authentication/KeycloakLogoutHandlerTest.java
+++ b/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/authentication/KeycloakLogoutHandlerTest.java
@@ -2,10 +2,11 @@ package org.keycloak.adapters.springsecurity.authentication;
 
 import org.junit.Before;
 import org.junit.Test;
+import org.keycloak.adapters.AdapterDeploymentContext;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.OidcKeycloakAccount;
 import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
-import org.keycloak.adapters.springsecurity.AdapterDeploymentContextBean;
+import org.keycloak.adapters.spi.HttpFacade;
 import org.keycloak.adapters.springsecurity.account.KeycloakRole;
 import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
 import org.mockito.Mock;
@@ -35,7 +36,7 @@ public class KeycloakLogoutHandlerTest {
     private MockHttpServletResponse response;
 
     @Mock
-    private AdapterDeploymentContextBean adapterDeploymentContextBean;
+    private AdapterDeploymentContext adapterDeploymentContext;
 
     @Mock
     private OidcKeycloakAccount keycloakAccount;
@@ -52,11 +53,11 @@ public class KeycloakLogoutHandlerTest {
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         keycloakAuthenticationToken = mock(KeycloakAuthenticationToken.class);
-        keycloakLogoutHandler = new KeycloakLogoutHandler(adapterDeploymentContextBean);
+        keycloakLogoutHandler = new KeycloakLogoutHandler(adapterDeploymentContext);
         request = new MockHttpServletRequest();
         response = new MockHttpServletResponse();
 
-        when(adapterDeploymentContextBean.getDeployment()).thenReturn(keycloakDeployment);
+        when(adapterDeploymentContext.resolveDeployment(any(HttpFacade.class))).thenReturn(keycloakDeployment);
         when(keycloakAuthenticationToken.getAccount()).thenReturn(keycloakAccount);
         when(keycloakAccount.getKeycloakSecurityContext()).thenReturn(session);
     }
diff --git a/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilterTest.java b/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilterTest.java
index ab4c032..1ccc367 100755
--- a/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilterTest.java
+++ b/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilterTest.java
@@ -4,9 +4,10 @@ import org.junit.Before;
 import org.junit.Test;
 import org.keycloak.KeycloakPrincipal;
 import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.AdapterDeploymentContext;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.OidcKeycloakAccount;
-import org.keycloak.adapters.springsecurity.AdapterDeploymentContextBean;
+import org.keycloak.adapters.spi.HttpFacade;
 import org.keycloak.adapters.springsecurity.KeycloakAuthenticationException;
 import org.keycloak.adapters.springsecurity.account.KeycloakRole;
 import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
@@ -45,7 +46,7 @@ public class KeycloakAuthenticationProcessingFilterTest {
     private AuthenticationManager authenticationManager;
 
     @Mock
-    private AdapterDeploymentContextBean adapterDeploymentContextBean;
+    private AdapterDeploymentContext adapterDeploymentContext;
 
     @Mock
     private FilterChain chain;
@@ -85,8 +86,8 @@ public class KeycloakAuthenticationProcessingFilterTest {
         filter.setAuthenticationSuccessHandler(successHandler);
         filter.setAuthenticationFailureHandler(failureHandler);
 
-        when(applicationContext.getBean(eq(AdapterDeploymentContextBean.class))).thenReturn(adapterDeploymentContextBean);
-        when(adapterDeploymentContextBean.getDeployment()).thenReturn(keycloakDeployment);
+        when(applicationContext.getBean(eq(AdapterDeploymentContext.class))).thenReturn(adapterDeploymentContext);
+        when(adapterDeploymentContext.resolveDeployment(any(HttpFacade.class))).thenReturn(keycloakDeployment);
         when(keycloakAccount.getPrincipal()).thenReturn(
                 new KeycloakPrincipal<KeycloakSecurityContext>(UUID.randomUUID().toString(), keycloakSecurityContext));
 
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java
index bf452ff..84bbdad 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java
@@ -170,7 +170,7 @@ public class IdentityProvidersResource {
             adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, identityProvider.getInternalId())
                     .representation(representation).success();
             
-            return Response.created(uriInfo.getAbsolutePathBuilder().path(representation.getProviderId()).build()).build();
+            return Response.created(uriInfo.getAbsolutePathBuilder().path(representation.getAlias()).build()).build();
         } catch (ModelDuplicateException e) {
             return ErrorResponse.exists("Identity Provider " + representation.getAlias() + " already exists");
         }