commit
4d18812768
76 changed files with 697 additions and 64 deletions
|
@ -433,6 +433,16 @@ public class AdapterDeploymentContext {
|
|||
public void setPrincipalAttribute(String principalAttribute) {
|
||||
delegate.setPrincipalAttribute(principalAttribute);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTurnOffChangeSessionIdOnLogin() {
|
||||
return delegate.isTurnOffChangeSessionIdOnLogin();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTurnOffChangeSessionIdOnLogin(boolean turnOffChangeSessionIdOnLogin) {
|
||||
delegate.setTurnOffChangeSessionIdOnLogin(turnOffChangeSessionIdOnLogin);
|
||||
}
|
||||
}
|
||||
|
||||
protected KeycloakUriBuilder getBaseBuilder(HttpFacade facade, String base) {
|
||||
|
|
|
@ -57,6 +57,8 @@ public class KeycloakDeployment {
|
|||
protected boolean alwaysRefreshToken;
|
||||
protected boolean registerNodeAtStartup;
|
||||
protected int registerNodePeriod;
|
||||
protected boolean turnOffChangeSessionIdOnLogin;
|
||||
|
||||
protected volatile int notBefore;
|
||||
|
||||
public KeycloakDeployment() {
|
||||
|
@ -361,4 +363,12 @@ public class KeycloakDeployment {
|
|||
public void setPrincipalAttribute(String principalAttribute) {
|
||||
this.principalAttribute = principalAttribute;
|
||||
}
|
||||
|
||||
public boolean isTurnOffChangeSessionIdOnLogin() {
|
||||
return turnOffChangeSessionIdOnLogin;
|
||||
}
|
||||
|
||||
public void setTurnOffChangeSessionIdOnLogin(boolean turnOffChangeSessionIdOnLogin) {
|
||||
this.turnOffChangeSessionIdOnLogin = turnOffChangeSessionIdOnLogin;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -88,6 +88,9 @@ public class KeycloakDeploymentBuilder {
|
|||
throw new RuntimeException("You must specify auth-url");
|
||||
}
|
||||
deployment.setAuthServerBaseUrl(adapterConfig);
|
||||
if (adapterConfig.getTurnOffChangeSessionIdOnLogin() != null) {
|
||||
deployment.setTurnOffChangeSessionIdOnLogin(adapterConfig.getTurnOffChangeSessionIdOnLogin());
|
||||
}
|
||||
|
||||
log.debug("Use authServerUrl: " + deployment.getAuthServerBaseUrl() + ", tokenUrl: " + deployment.getTokenUrl() + ", relativeUrls: " + deployment.getRelativeUrls());
|
||||
return deployment;
|
||||
|
|
|
@ -293,7 +293,7 @@ public class OAuthRequestAuthenticator {
|
|||
strippedOauthParametersRequestUri = stripOauthParametersFromRedirect();
|
||||
try {
|
||||
// For COOKIE store we don't have httpSessionId and single sign-out won't be available
|
||||
String httpSessionId = deployment.getTokenStore() == TokenStore.SESSION ? reqAuthenticator.getHttpSessionId(true) : null;
|
||||
String httpSessionId = deployment.getTokenStore() == TokenStore.SESSION ? reqAuthenticator.changeHttpSessionId(true) : null;
|
||||
tokenResponse = ServerRequest.invokeAccessCodeToToken(deployment, code, strippedOauthParametersRequestUri, httpSessionId);
|
||||
} catch (ServerRequest.HttpFailure failure) {
|
||||
log.error("failed to turn code into token");
|
||||
|
|
|
@ -141,7 +141,13 @@ public abstract class RequestAuthenticator {
|
|||
|
||||
protected abstract void completeOAuthAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal);
|
||||
protected abstract void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal, String method);
|
||||
protected abstract String getHttpSessionId(boolean create);
|
||||
|
||||
/**
|
||||
* After code is received, we change the session id if possible to guard against https://www.owasp.org/index.php/Session_Fixation
|
||||
* @param create
|
||||
* @return
|
||||
*/
|
||||
protected abstract String changeHttpSessionId(boolean create);
|
||||
|
||||
protected void completeAuthentication(BearerTokenRequestAuthenticator bearer, String method) {
|
||||
RefreshableKeycloakSecurityContext session = new RefreshableKeycloakSecurityContext(deployment, null, bearer.getTokenString(), bearer.getToken(), null, null, null);
|
||||
|
|
|
@ -87,6 +87,7 @@
|
|||
<xs:element name="token-store" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="principal-attribute" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="enable-basic-auth" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="turn-off-change-session-id-on-login" type="xs:boolean" minOccurs="0" maxOccurs="1" />
|
||||
</xs:all>
|
||||
<xs:attribute name="name" type="xs:string" use="required">
|
||||
<xs:annotation>
|
||||
|
|
|
@ -76,7 +76,7 @@ public class JettyRequestAuthenticator extends RequestAuthenticator {
|
|||
|
||||
|
||||
@Override
|
||||
protected String getHttpSessionId(boolean create) {
|
||||
protected String changeHttpSessionId(boolean create) {
|
||||
HttpSession session = request.getSession(create);
|
||||
return session != null ? session.getId() : null;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
package org.keycloak.adapters.jetty;
|
||||
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.keycloak.adapters.AdapterTokenStore;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.jetty.core.JettyRequestAuthenticator;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class Jetty91RequestAuthenticator extends JettyRequestAuthenticator {
|
||||
public Jetty91RequestAuthenticator(HttpFacade facade, KeycloakDeployment deployment, AdapterTokenStore tokenStore, int sslRedirectPort, Request request) {
|
||||
super(facade, deployment, tokenStore, sslRedirectPort, request);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String changeHttpSessionId(boolean create) {
|
||||
Request request = this.request;
|
||||
HttpSession session = request.getSession(false);
|
||||
if (session == null) {
|
||||
return request.getSession(true).getId();
|
||||
}
|
||||
if (deployment.isTurnOffChangeSessionIdOnLogin() == false) return request.changeSessionId();
|
||||
else return session.getId();
|
||||
}
|
||||
}
|
|
@ -7,7 +7,9 @@ import org.eclipse.jetty.server.UserIdentity;
|
|||
import org.keycloak.adapters.AdapterTokenStore;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.jetty.core.AbstractKeycloakJettyAuthenticator;
|
||||
import org.keycloak.adapters.jetty.core.JettyRequestAuthenticator;
|
||||
import org.keycloak.adapters.jetty.core.JettySessionTokenStore;
|
||||
import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
|
||||
|
@ -42,5 +44,12 @@ public class KeycloakJettyAuthenticator extends AbstractKeycloakJettyAuthenticat
|
|||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JettyRequestAuthenticator createRequestAuthenticator(Request request, JettyHttpFacade facade,
|
||||
KeycloakDeployment deployment, AdapterTokenStore tokenStore) {
|
||||
return new Jetty91RequestAuthenticator(facade, deployment, tokenStore, -1, request);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
package org.keycloak.adapters.jetty;
|
||||
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.keycloak.adapters.AdapterTokenStore;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.jetty.core.JettyRequestAuthenticator;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class Jetty92RequestAuthenticator extends JettyRequestAuthenticator {
|
||||
public Jetty92RequestAuthenticator(HttpFacade facade, KeycloakDeployment deployment, AdapterTokenStore tokenStore, int sslRedirectPort, Request request) {
|
||||
super(facade, deployment, tokenStore, sslRedirectPort, request);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String changeHttpSessionId(boolean create) {
|
||||
Request request = this.request;
|
||||
HttpSession session = request.getSession(false);
|
||||
if (session == null) {
|
||||
return request.getSession(true).getId();
|
||||
}
|
||||
if (deployment.isTurnOffChangeSessionIdOnLogin() == false) return request.changeSessionId();
|
||||
else return session.getId();
|
||||
}
|
||||
}
|
|
@ -7,7 +7,9 @@ import org.eclipse.jetty.server.UserIdentity;
|
|||
import org.keycloak.adapters.AdapterTokenStore;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.jetty.core.AbstractKeycloakJettyAuthenticator;
|
||||
import org.keycloak.adapters.jetty.core.JettyRequestAuthenticator;
|
||||
import org.keycloak.adapters.jetty.core.JettySessionTokenStore;
|
||||
import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
|
||||
|
@ -41,4 +43,11 @@ public class KeycloakJettyAuthenticator extends AbstractKeycloakJettyAuthenticat
|
|||
public AdapterTokenStore createSessionTokenStore(Request request, KeycloakDeployment resolvedDeployment) {
|
||||
return new JettySessionTokenStore(request, resolvedDeployment, new JettyAdapterSessionStore(request));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JettyRequestAuthenticator createRequestAuthenticator(Request request, JettyHttpFacade facade,
|
||||
KeycloakDeployment deployment, AdapterTokenStore tokenStore) {
|
||||
return new Jetty92RequestAuthenticator(facade, deployment, tokenStore, -1, request);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
<module>jetty-adapter-spi</module>
|
||||
<module>jetty-core</module>
|
||||
<module>jetty8.1</module>
|
||||
<module>jetty9.2</module>
|
||||
|
|
|
@ -78,7 +78,7 @@ public class FilterRequestAuthenticator extends RequestAuthenticator {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected String getHttpSessionId(boolean create) {
|
||||
protected String changeHttpSessionId(boolean create) {
|
||||
HttpSession session = request.getSession(create);
|
||||
return session != null ? session.getId() : null;
|
||||
}
|
||||
|
|
|
@ -82,7 +82,7 @@ public class SpringSecurityRequestAuthenticator extends RequestAuthenticator {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected String getHttpSessionId(boolean create) {
|
||||
protected String changeHttpSessionId(boolean create) {
|
||||
HttpSession session = request.getSession(create);
|
||||
return session != null ? session.getId() : null;
|
||||
}
|
||||
|
|
|
@ -95,13 +95,13 @@ public class SpringSecurityRequestAuthenticatorTest {
|
|||
|
||||
@Test
|
||||
public void testGetHttpSessionIdTrue() throws Exception {
|
||||
String sessionId = authenticator.getHttpSessionId(true);
|
||||
String sessionId = authenticator.changeHttpSessionId(true);
|
||||
assertNotNull(sessionId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetHttpSessionIdFalse() throws Exception {
|
||||
String sessionId = authenticator.getHttpSessionId(false);
|
||||
String sessionId = authenticator.changeHttpSessionId(false);
|
||||
assertNull(sessionId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -185,7 +185,7 @@ public abstract class AbstractKeycloakAuthenticatorValve extends FormAuthenticat
|
|||
|
||||
nodesRegistrationManagement.tryRegister(deployment);
|
||||
|
||||
CatalinaRequestAuthenticator authenticator = new CatalinaRequestAuthenticator(deployment, tokenStore, facade, request, createPrincipalFactory());
|
||||
CatalinaRequestAuthenticator authenticator = createRequestAuthenticator(request, facade, deployment, tokenStore);
|
||||
AuthOutcome outcome = authenticator.authenticate();
|
||||
if (outcome == AuthOutcome.AUTHENTICATED) {
|
||||
if (facade.isEnded()) {
|
||||
|
@ -200,6 +200,10 @@ public abstract class AbstractKeycloakAuthenticatorValve extends FormAuthenticat
|
|||
return false;
|
||||
}
|
||||
|
||||
protected CatalinaRequestAuthenticator createRequestAuthenticator(Request request, CatalinaHttpFacade facade, KeycloakDeployment deployment, AdapterTokenStore tokenStore) {
|
||||
return new CatalinaRequestAuthenticator(deployment, tokenStore, facade, request, createPrincipalFactory());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that access token is still valid. Will attempt refresh of token if it is not.
|
||||
*
|
||||
|
@ -230,7 +234,7 @@ public abstract class AbstractKeycloakAuthenticatorValve extends FormAuthenticat
|
|||
}
|
||||
|
||||
if (resolvedDeployment.getTokenStore() == TokenStore.SESSION) {
|
||||
store = new CatalinaSessionTokenStore(request, resolvedDeployment, userSessionManagement, createPrincipalFactory(), this);
|
||||
store = createSessionTokenStore(request, resolvedDeployment);
|
||||
} else {
|
||||
store = new CatalinaCookieTokenStore(request, facade, resolvedDeployment, createPrincipalFactory());
|
||||
}
|
||||
|
@ -239,4 +243,10 @@ public abstract class AbstractKeycloakAuthenticatorValve extends FormAuthenticat
|
|||
return store;
|
||||
}
|
||||
|
||||
private AdapterTokenStore createSessionTokenStore(Request request, KeycloakDeployment resolvedDeployment) {
|
||||
AdapterTokenStore store;
|
||||
store = new CatalinaSessionTokenStore(request, resolvedDeployment, userSessionManagement, createPrincipalFactory(), this);
|
||||
return store;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -83,7 +83,7 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected String getHttpSessionId(boolean create) {
|
||||
protected String changeHttpSessionId(boolean create) {
|
||||
HttpSession session = request.getSession(create);
|
||||
return session != null ? session.getId() : null;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@ import org.apache.catalina.connector.Response;
|
|||
import org.apache.catalina.core.StandardContext;
|
||||
import org.apache.catalina.deploy.LoginConfig;
|
||||
import org.apache.catalina.realm.GenericPrincipal;
|
||||
import org.keycloak.adapters.AdapterTokenStore;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
@ -55,4 +57,5 @@ public class KeycloakAuthenticatorValve extends AbstractKeycloakAuthenticatorVal
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ import org.apache.catalina.connector.Response;
|
|||
import org.apache.catalina.core.StandardContext;
|
||||
import org.apache.catalina.deploy.LoginConfig;
|
||||
import org.apache.catalina.realm.GenericPrincipal;
|
||||
import org.keycloak.adapters.AdapterTokenStore;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
@ -51,4 +53,5 @@ public class KeycloakAuthenticatorValve extends AbstractKeycloakAuthenticatorVal
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,6 +7,9 @@ import org.apache.catalina.core.StandardContext;
|
|||
import org.apache.catalina.realm.GenericPrincipal;
|
||||
import org.apache.tomcat.util.ExceptionUtils;
|
||||
import org.apache.tomcat.util.descriptor.web.LoginConfig;
|
||||
import org.keycloak.adapters.AdapterTokenStore;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
|
||||
import javax.servlet.RequestDispatcher;
|
||||
import javax.servlet.ServletException;
|
||||
|
@ -71,4 +74,9 @@ public class KeycloakAuthenticatorValve extends AbstractKeycloakAuthenticatorVal
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AdapterTokenStore getTokenStore(Request request, HttpFacade facade, KeycloakDeployment resolvedDeployment) {
|
||||
return super.getTokenStore(request, facade, resolvedDeployment);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
package org.keycloak.adapters.tomcat;
|
||||
|
||||
import org.apache.catalina.connector.Request;
|
||||
import org.keycloak.adapters.AdapterTokenStore;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class Tomcat8RequestAuthenticator extends CatalinaRequestAuthenticator {
|
||||
public Tomcat8RequestAuthenticator(KeycloakDeployment deployment, AdapterTokenStore tokenStore, CatalinaHttpFacade facade, Request request, GenericPrincipalFactory principalFactory) {
|
||||
super(deployment, tokenStore, facade, request, principalFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String changeHttpSessionId(boolean create) {
|
||||
Request request = this.request;
|
||||
HttpSession session = request.getSession(false);
|
||||
if (session == null) {
|
||||
return request.getSession(true).getId();
|
||||
}
|
||||
if (deployment.isTurnOffChangeSessionIdOnLogin() == false) return request.changeSessionId();
|
||||
else return session.getId();
|
||||
}
|
||||
}
|
|
@ -72,7 +72,7 @@ public abstract class AbstractUndertowRequestAuthenticator extends RequestAuthen
|
|||
}
|
||||
|
||||
@Override
|
||||
protected String getHttpSessionId(boolean create) {
|
||||
protected String changeHttpSessionId(boolean create) {
|
||||
if (create) {
|
||||
Session session = Sessions.getOrCreateSession(exchange);
|
||||
return session.getId();
|
||||
|
|
|
@ -177,7 +177,7 @@ public class KeycloakServletExtension implements ServletExtension {
|
|||
ServletSessionConfig cookieConfig = new ServletSessionConfig();
|
||||
cookieConfig.setPath(deploymentInfo.getContextPath());
|
||||
deploymentInfo.setServletSessionConfig(cookieConfig);
|
||||
ChangeSessionIdOnLogin.turnOffChangeSessionIdOnLogin(deploymentInfo);
|
||||
ChangeSessionId.turnOffChangeSessionIdOnLogin(deploymentInfo);
|
||||
deploymentInfo.addListener(new ListenerInfo(UndertowNodesRegistrationManagementWrapper.class, new InstanceFactory<UndertowNodesRegistrationManagementWrapper>() {
|
||||
|
||||
@Override
|
||||
|
|
|
@ -63,6 +63,11 @@ public class ServletRequestAuthenticator extends AbstractUndertowRequestAuthenti
|
|||
}
|
||||
|
||||
@Override
|
||||
protected String changeHttpSessionId(boolean create) {
|
||||
if (deployment.isTurnOffChangeSessionIdOnLogin() == false) return ChangeSessionId.changeSessionId(exchange, create);
|
||||
else return getHttpSessionId(create);
|
||||
}
|
||||
|
||||
protected String getHttpSessionId(boolean create) {
|
||||
HttpSession session = getSession(create);
|
||||
return session != null ? session.getId() : null;
|
||||
|
|
|
@ -77,6 +77,12 @@ public class SecureDeploymentDefinition extends SimpleResourceDefinition {
|
|||
.setAllowExpression(true)
|
||||
.setDefaultValue(new ModelNode(false))
|
||||
.build();
|
||||
protected static final SimpleAttributeDefinition TURN_OFF_CHANGE_SESSION =
|
||||
new SimpleAttributeDefinitionBuilder("turn-off-change-session-id-on-login", ModelType.BOOLEAN, true)
|
||||
.setXmlName("turn-off-change-session-id-on-login")
|
||||
.setAllowExpression(true)
|
||||
.setDefaultValue(new ModelNode(false))
|
||||
.build();
|
||||
|
||||
protected static final List<SimpleAttributeDefinition> DEPLOYMENT_ONLY_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
|
||||
static {
|
||||
|
@ -86,6 +92,7 @@ public class SecureDeploymentDefinition extends SimpleResourceDefinition {
|
|||
DEPLOYMENT_ONLY_ATTRIBUTES.add(BEARER_ONLY);
|
||||
DEPLOYMENT_ONLY_ATTRIBUTES.add(ENABLE_BASIC_AUTH);
|
||||
DEPLOYMENT_ONLY_ATTRIBUTES.add(PUBLIC_CLIENT);
|
||||
DEPLOYMENT_ONLY_ATTRIBUTES.add(TURN_OFF_CHANGE_SESSION);
|
||||
}
|
||||
|
||||
protected static final List<SimpleAttributeDefinition> ALL_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
|
||||
|
|
|
@ -63,6 +63,7 @@ keycloak.secure-deployment.register-node-at-startup=Cluster setting
|
|||
keycloak.secure-deployment.register-node-period=how often to re-register node
|
||||
keycloak.secure-deployment.token-store=cookie or session storage for auth session data
|
||||
keycloak.secure-deployment.principal-attribute=token attribute to use to set Principal name
|
||||
keycloak.secure-deployment.turn-off-change-session-id-on-login=The session id is changed by default on a successful login. Change this to true if you want to turn this off
|
||||
|
||||
keycloak.secure-deployment.credential=Credential value
|
||||
|
||||
|
|
|
@ -87,6 +87,7 @@
|
|||
<xs:element name="token-store" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="principal-attribute" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="enable-basic-auth" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="turn-off-change-session-id-on-login" type="xs:boolean" minOccurs="0" maxOccurs="1" />
|
||||
</xs:all>
|
||||
<xs:attribute name="name" type="xs:string" use="required">
|
||||
<xs:annotation>
|
||||
|
|
1
adapters/oidc/wildfly/wf8-subsystem/src/test/resources/org/keycloak/subsystem/wf8/extension/keycloak-1.1.xml
Normal file → Executable file
1
adapters/oidc/wildfly/wf8-subsystem/src/test/resources/org/keycloak/subsystem/wf8/extension/keycloak-1.1.xml
Normal file → Executable file
|
@ -3,6 +3,7 @@
|
|||
<realm>master</realm>
|
||||
<resource>web-console</resource>
|
||||
<use-resource-role-mappings>true</use-resource-role-mappings>
|
||||
<turn-off-change-session-id-on-login>false</turn-off-change-session-id-on-login>
|
||||
<realm-public-key>
|
||||
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC4siLKUew0WYxdtq6/rwk4Uj/4amGFFnE/yzIxQVU0PUqz3QBRVkUWpDj0K6ZnS5nzJV/y6DHLEy7hjZTdRDphyF1sq09aDOYnVpzu8o2sIlMM8q5RnUyEfIyUZqwo8pSZDJ90fS0s+IDUJNCSIrAKO3w1lqZDHL6E/YFHXyzkvQIDAQAB
|
||||
</realm-public-key>
|
||||
|
|
|
@ -77,6 +77,12 @@ public class SecureDeploymentDefinition extends SimpleResourceDefinition {
|
|||
.setAllowExpression(true)
|
||||
.setDefaultValue(new ModelNode(false))
|
||||
.build();
|
||||
protected static final SimpleAttributeDefinition TURN_OFF_CHANGE_SESSION =
|
||||
new SimpleAttributeDefinitionBuilder("turn-off-change-session-id-on-login", ModelType.BOOLEAN, true)
|
||||
.setXmlName("turn-off-change-session-id-on-login")
|
||||
.setAllowExpression(true)
|
||||
.setDefaultValue(new ModelNode(false))
|
||||
.build();
|
||||
|
||||
protected static final List<SimpleAttributeDefinition> DEPLOYMENT_ONLY_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
|
||||
static {
|
||||
|
@ -86,6 +92,7 @@ public class SecureDeploymentDefinition extends SimpleResourceDefinition {
|
|||
DEPLOYMENT_ONLY_ATTRIBUTES.add(BEARER_ONLY);
|
||||
DEPLOYMENT_ONLY_ATTRIBUTES.add(ENABLE_BASIC_AUTH);
|
||||
DEPLOYMENT_ONLY_ATTRIBUTES.add(PUBLIC_CLIENT);
|
||||
DEPLOYMENT_ONLY_ATTRIBUTES.add(TURN_OFF_CHANGE_SESSION);
|
||||
}
|
||||
|
||||
protected static final List<SimpleAttributeDefinition> ALL_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
|
||||
|
|
|
@ -63,6 +63,7 @@ keycloak.secure-deployment.register-node-at-startup=Cluster setting
|
|||
keycloak.secure-deployment.register-node-period=how often to re-register node
|
||||
keycloak.secure-deployment.token-store=cookie or session storage for auth session data
|
||||
keycloak.secure-deployment.principal-attribute=token attribute to use to set Principal name
|
||||
keycloak.secure-deployment.turn-off-change-session-id-on-login=The session id is changed by default on a successful login. Change this to true if you want to turn this off
|
||||
|
||||
keycloak.secure-deployment.credential=Credential value
|
||||
|
||||
|
|
|
@ -87,6 +87,7 @@
|
|||
<xs:element name="token-store" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="principal-attribute" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="enable-basic-auth" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="turn-off-change-session-id-on-login" type="xs:boolean" minOccurs="0" maxOccurs="1" />
|
||||
</xs:all>
|
||||
<xs:attribute name="name" type="xs:string" use="required">
|
||||
<xs:annotation>
|
||||
|
|
1
adapters/oidc/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/extension/keycloak-1.1.xml
Normal file → Executable file
1
adapters/oidc/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/extension/keycloak-1.1.xml
Normal file → Executable file
|
@ -3,6 +3,7 @@
|
|||
<realm>master</realm>
|
||||
<resource>web-console</resource>
|
||||
<use-resource-role-mappings>true</use-resource-role-mappings>
|
||||
<turn-off-change-session-id-on-login>false</turn-off-change-session-id-on-login>
|
||||
<realm-public-key>
|
||||
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC4siLKUew0WYxdtq6/rwk4Uj/4amGFFnE/yzIxQVU0PUqz3QBRVkUWpDj0K6ZnS5nzJV/y6DHLEy7hjZTdRDphyF1sq09aDOYnVpzu8o2sIlMM8q5RnUyEfIyUZqwo8pSZDJ90fS0s+IDUJNCSIrAKO3w1lqZDHL6E/YFHXyzkvQIDAQAB
|
||||
</realm-public-key>
|
||||
|
|
|
@ -201,6 +201,7 @@ public class DefaultSamlDeployment implements SamlDeployment {
|
|||
private String nameIDPolicyFormat;
|
||||
private boolean forceAuthentication;
|
||||
private boolean isPassive;
|
||||
private boolean turnOffChangeSessionIdOnLogin;
|
||||
private PrivateKey decryptionKey;
|
||||
private KeyPair signingKeyPair;
|
||||
private String assertionConsumerServiceUrl;
|
||||
|
@ -211,6 +212,16 @@ public class DefaultSamlDeployment implements SamlDeployment {
|
|||
private SignatureAlgorithm signatureAlgorithm;
|
||||
private String signatureCanonicalizationMethod;
|
||||
|
||||
@Override
|
||||
public boolean turnOffChangeSessionIdOnLogin() {
|
||||
return turnOffChangeSessionIdOnLogin;
|
||||
}
|
||||
|
||||
public void setTurnOffChangeSessionIdOnLogin(boolean turnOffChangeSessionIdOnLogin) {
|
||||
this.turnOffChangeSessionIdOnLogin = turnOffChangeSessionIdOnLogin;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public IDP getIDP() {
|
||||
return idp;
|
||||
|
|
|
@ -57,6 +57,7 @@ public interface SamlDeployment {
|
|||
String getNameIDPolicyFormat();
|
||||
boolean isForceAuthentication();
|
||||
boolean isIsPassive();
|
||||
boolean turnOffChangeSessionIdOnLogin();
|
||||
PrivateKey getDecryptionKey();
|
||||
KeyPair getSigningKeyPair();
|
||||
String getSignatureCanonicalizationMethod();
|
||||
|
|
|
@ -34,6 +34,7 @@ public class SP implements Serializable {
|
|||
private String sslPolicy;
|
||||
private boolean forceAuthentication;
|
||||
private boolean isPassive;
|
||||
private boolean turnOffChangeSessionIdOnLogin;
|
||||
private String logoutPage;
|
||||
private List<Key> keys;
|
||||
private String nameIDPolicyFormat;
|
||||
|
@ -73,6 +74,14 @@ public class SP implements Serializable {
|
|||
this.isPassive = isPassive;
|
||||
}
|
||||
|
||||
public boolean isTurnOffChangeSessionIdOnLogin() {
|
||||
return turnOffChangeSessionIdOnLogin;
|
||||
}
|
||||
|
||||
public void setTurnOffChangeSessionIdOnLogin(boolean turnOffChangeSessionIdOnLogin) {
|
||||
this.turnOffChangeSessionIdOnLogin = turnOffChangeSessionIdOnLogin;
|
||||
}
|
||||
|
||||
public List<Key> getKeys() {
|
||||
return keys;
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ public class ConfigXmlConstants {
|
|||
public static final String NAME_ID_POLICY_FORMAT_ATTR = "nameIDPolicyFormat";
|
||||
public static final String FORCE_AUTHENTICATION_ATTR = "forceAuthentication";
|
||||
public static final String IS_PASSIVE_ATTR = "isPassive";
|
||||
public static final String TURN_OFF_CHANGE_SESSSION_ID_ON_LOGIN_ATTR = "turnOffChangeSessionIdOnLogin";
|
||||
public static final String SIGNATURE_ALGORITHM_ATTR = "signatureAlgorithm";
|
||||
public static final String SIGNATURE_CANONICALIZATION_METHOD_ATTR = "signatureCanonicalizationMethod";
|
||||
public static final String LOGOUT_PAGE_ATTR = "logoutPage";
|
||||
|
|
|
@ -66,6 +66,7 @@ public class SPXmlParser extends AbstractParser {
|
|||
sp.setNameIDPolicyFormat(getAttributeValue(startElement, ConfigXmlConstants.NAME_ID_POLICY_FORMAT_ATTR));
|
||||
sp.setForceAuthentication(getBooleanAttributeValue(startElement, ConfigXmlConstants.FORCE_AUTHENTICATION_ATTR));
|
||||
sp.setIsPassive(getBooleanAttributeValue(startElement, ConfigXmlConstants.IS_PASSIVE_ATTR));
|
||||
sp.setTurnOffChangeSessionIdOnLogin(getBooleanAttributeValue(startElement, ConfigXmlConstants.TURN_OFF_CHANGE_SESSSION_ID_ON_LOGIN_ATTR));
|
||||
while (xmlEventReader.hasNext()) {
|
||||
XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
|
||||
if (xmlEvent == null)
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
<xs:attribute name="logoutPage" type="xs:string" use="optional"/>
|
||||
<xs:attribute name="forceAuthentication" type="xs:boolean" use="optional"/>
|
||||
<xs:attribute name="isPassive" type="xs:boolean" use="optional"/>
|
||||
<xs:attribute name="turnOffChangeSessionIdOnLogin" type="xs:boolean" use="optional"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="keys-type">
|
||||
|
|
|
@ -75,12 +75,18 @@ public abstract class AbstractSamlAuthenticator extends LoginAuthenticator {
|
|||
if (store != null) {
|
||||
return store;
|
||||
}
|
||||
store = new JettySamlSessionStore(request, createSessionTokenStore(request, resolvedDeployment), facade, idMapper, new JettyUserSessionManagement(request.getSessionManager()));
|
||||
store = createJettySamlSessionStore(request, facade, resolvedDeployment);
|
||||
|
||||
request.setAttribute(TOKEN_STORE_NOTE, store);
|
||||
return store;
|
||||
}
|
||||
|
||||
protected JettySamlSessionStore createJettySamlSessionStore(Request request, HttpFacade facade, SamlDeployment resolvedDeployment) {
|
||||
JettySamlSessionStore store;
|
||||
store = new JettySamlSessionStore(request, createSessionTokenStore(request, resolvedDeployment), facade, idMapper, new JettyUserSessionManagement(request.getSessionManager()), resolvedDeployment);
|
||||
return store;
|
||||
}
|
||||
|
||||
public abstract AdapterSessionStore createSessionTokenStore(Request request, SamlDeployment resolvedDeployment);
|
||||
|
||||
public void logoutCurrent(Request request) {
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.keycloak.adapters.saml.jetty;
|
|||
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.adapters.saml.SamlDeployment;
|
||||
import org.keycloak.adapters.spi.AdapterSessionStore;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.adapters.spi.SessionIdMapper;
|
||||
|
@ -23,19 +24,21 @@ import java.util.Set;
|
|||
public class JettySamlSessionStore implements SamlSessionStore {
|
||||
public static final String SAML_REDIRECT_URI = "SAML_REDIRECT_URI";
|
||||
private static final Logger log = Logger.getLogger(JettySamlSessionStore.class);
|
||||
private Request request;
|
||||
protected Request request;
|
||||
protected AdapterSessionStore sessionStore;
|
||||
protected HttpFacade facade;
|
||||
protected SessionIdMapper idMapper;
|
||||
protected JettyUserSessionManagement sessionManagement;
|
||||
protected final SamlDeployment deployment;
|
||||
|
||||
public JettySamlSessionStore(Request request, AdapterSessionStore sessionStore, HttpFacade facade,
|
||||
SessionIdMapper idMapper, JettyUserSessionManagement sessionManagement) {
|
||||
SessionIdMapper idMapper, JettyUserSessionManagement sessionManagement, SamlDeployment deployment) {
|
||||
this.request = request;
|
||||
this.sessionStore = sessionStore;
|
||||
this.facade = facade;
|
||||
this.idMapper = idMapper;
|
||||
this.sessionManagement = sessionManagement;
|
||||
this.deployment = deployment;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -132,10 +135,14 @@ public class JettySamlSessionStore implements SamlSessionStore {
|
|||
HttpSession session = request.getSession(true);
|
||||
session.setAttribute(SamlSession.class.getName(), account);
|
||||
|
||||
idMapper.map(account.getSessionIndex(), account.getPrincipal().getSamlSubject(), session.getId());
|
||||
idMapper.map(account.getSessionIndex(), account.getPrincipal().getSamlSubject(), changeSessionId(session));
|
||||
|
||||
}
|
||||
|
||||
protected String changeSessionId(HttpSession session) {
|
||||
return session.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SamlSession getAccount() {
|
||||
HttpSession session = request.getSession(true);
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
package org.keycloak.adapters.saml.jetty;
|
||||
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.keycloak.adapters.jetty.spi.JettyUserSessionManagement;
|
||||
import org.keycloak.adapters.saml.SamlDeployment;
|
||||
import org.keycloak.adapters.spi.AdapterSessionStore;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.adapters.spi.SessionIdMapper;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class Jetty9SamlSessionStore extends JettySamlSessionStore {
|
||||
public Jetty9SamlSessionStore(Request request, AdapterSessionStore sessionStore, HttpFacade facade, SessionIdMapper idMapper, JettyUserSessionManagement sessionManagement, SamlDeployment deployment) {
|
||||
super(request, sessionStore, facade, idMapper, sessionManagement, deployment);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String changeSessionId(HttpSession session) {
|
||||
Request request = this.request;
|
||||
if (deployment.turnOffChangeSessionIdOnLogin() == false) return request.changeSessionId();
|
||||
else return session.getId();
|
||||
}
|
||||
}
|
|
@ -4,8 +4,10 @@ import org.eclipse.jetty.server.Authentication;
|
|||
import org.eclipse.jetty.server.HttpChannel;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.UserIdentity;
|
||||
import org.keycloak.adapters.jetty.spi.JettyUserSessionManagement;
|
||||
import org.keycloak.adapters.spi.AdapterSessionStore;
|
||||
import org.keycloak.adapters.saml.SamlDeployment;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
|
||||
|
@ -40,5 +42,13 @@ public class KeycloakSamlAuthenticator extends AbstractSamlAuthenticator {
|
|||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JettySamlSessionStore createJettySamlSessionStore(Request request, HttpFacade facade, SamlDeployment resolvedDeployment) {
|
||||
JettySamlSessionStore store;
|
||||
store = new Jetty9SamlSessionStore(request, createSessionTokenStore(request, resolvedDeployment), facade, idMapper, new JettyUserSessionManagement(request.getSessionManager()), resolvedDeployment);
|
||||
return store;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
package org.keycloak.adapters.saml.jetty;
|
||||
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.keycloak.adapters.jetty.spi.JettyUserSessionManagement;
|
||||
import org.keycloak.adapters.saml.SamlDeployment;
|
||||
import org.keycloak.adapters.spi.AdapterSessionStore;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.adapters.spi.SessionIdMapper;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class Jetty9SamlSessionStore extends JettySamlSessionStore {
|
||||
public Jetty9SamlSessionStore(Request request, AdapterSessionStore sessionStore, HttpFacade facade, SessionIdMapper idMapper, JettyUserSessionManagement sessionManagement, SamlDeployment deployment) {
|
||||
super(request, sessionStore, facade, idMapper, sessionManagement, deployment);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String changeSessionId(HttpSession session) {
|
||||
Request request = this.request;
|
||||
if (deployment.turnOffChangeSessionIdOnLogin() == false) return request.changeSessionId();
|
||||
else return session.getId();
|
||||
}
|
||||
}
|
|
@ -4,8 +4,10 @@ import org.eclipse.jetty.server.Authentication;
|
|||
import org.eclipse.jetty.server.HttpChannel;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.UserIdentity;
|
||||
import org.keycloak.adapters.jetty.spi.JettyUserSessionManagement;
|
||||
import org.keycloak.adapters.spi.AdapterSessionStore;
|
||||
import org.keycloak.adapters.saml.SamlDeployment;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
|
||||
|
@ -39,4 +41,11 @@ public class KeycloakSamlAuthenticator extends AbstractSamlAuthenticator {
|
|||
public AdapterSessionStore createSessionTokenStore(Request request, SamlDeployment resolvedDeployment) {
|
||||
return new JettyAdapterSessionStore(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JettySamlSessionStore createJettySamlSessionStore(Request request, HttpFacade facade, SamlDeployment resolvedDeployment) {
|
||||
JettySamlSessionStore store;
|
||||
store = new Jetty9SamlSessionStore(request, createSessionTokenStore(request, resolvedDeployment), facade, idMapper, new JettyUserSessionManagement(request.getSessionManager()), resolvedDeployment);
|
||||
return store;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ public abstract class AbstractSamlAuthenticatorValve extends FormAuthenticator i
|
|||
protected void logoutInternal(Request request) {
|
||||
CatalinaHttpFacade facade = new CatalinaHttpFacade(null, request);
|
||||
SamlDeployment deployment = deploymentContext.resolveDeployment(facade);
|
||||
SamlSessionStore tokenStore = getTokenStore(request, facade, deployment);
|
||||
SamlSessionStore tokenStore = getSessionStore(request, facade, deployment);
|
||||
tokenStore.logoutAccount();
|
||||
request.setUserPrincipal(null);
|
||||
}
|
||||
|
@ -184,7 +184,7 @@ public abstract class AbstractSamlAuthenticatorValve extends FormAuthenticator i
|
|||
log.fine("deployment not configured");
|
||||
return false;
|
||||
}
|
||||
SamlSessionStore tokenStore = getTokenStore(request, facade, deployment);
|
||||
SamlSessionStore tokenStore = getSessionStore(request, facade, deployment);
|
||||
|
||||
|
||||
CatalinaSamlAuthenticator authenticator = new CatalinaSamlAuthenticator(facade, deployment, tokenStore);
|
||||
|
@ -229,16 +229,22 @@ public abstract class AbstractSamlAuthenticatorValve extends FormAuthenticator i
|
|||
}
|
||||
}
|
||||
|
||||
protected SamlSessionStore getTokenStore(Request request, HttpFacade facade, SamlDeployment resolvedDeployment) {
|
||||
protected SamlSessionStore getSessionStore(Request request, HttpFacade facade, SamlDeployment resolvedDeployment) {
|
||||
SamlSessionStore store = (SamlSessionStore)request.getNote(TOKEN_STORE_NOTE);
|
||||
if (store != null) {
|
||||
return store;
|
||||
}
|
||||
|
||||
store = new CatalinaSamlSessionStore(userSessionManagement, createPrincipalFactory(), mapper, request, this, facade);
|
||||
store = createSessionStore(request, facade, resolvedDeployment);
|
||||
|
||||
request.setNote(TOKEN_STORE_NOTE, store);
|
||||
return store;
|
||||
}
|
||||
|
||||
protected SamlSessionStore createSessionStore(Request request, HttpFacade facade, SamlDeployment resolvedDeployment) {
|
||||
SamlSessionStore store;
|
||||
store = new CatalinaSamlSessionStore(userSessionManagement, createPrincipalFactory(), mapper, request, this, facade, resolvedDeployment);
|
||||
return store;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -32,15 +32,18 @@ public class CatalinaSamlSessionStore implements SamlSessionStore {
|
|||
protected final Request request;
|
||||
protected final AbstractSamlAuthenticatorValve valve;
|
||||
protected final HttpFacade facade;
|
||||
protected final SamlDeployment deployment;
|
||||
|
||||
public CatalinaSamlSessionStore(CatalinaUserSessionManagement sessionManagement, GenericPrincipalFactory principalFactory,
|
||||
SessionIdMapper idMapper, Request request, AbstractSamlAuthenticatorValve valve, HttpFacade facade) {
|
||||
SessionIdMapper idMapper, Request request, AbstractSamlAuthenticatorValve valve, HttpFacade facade,
|
||||
SamlDeployment deployment) {
|
||||
this.sessionManagement = sessionManagement;
|
||||
this.principalFactory = principalFactory;
|
||||
this.idMapper = idMapper;
|
||||
this.request = request;
|
||||
this.valve = valve;
|
||||
this.facade = facade;
|
||||
this.deployment = deployment;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -173,10 +176,15 @@ public class CatalinaSamlSessionStore implements SamlSessionStore {
|
|||
}
|
||||
request.setUserPrincipal(principal);
|
||||
request.setAuthType("KEYCLOAK-SAML");
|
||||
idMapper.map(account.getSessionIndex(), account.getPrincipal().getSamlSubject(), session.getId());
|
||||
String newId = changeSessionId(session);
|
||||
idMapper.map(account.getSessionIndex(), account.getPrincipal().getSamlSubject(), newId);
|
||||
|
||||
}
|
||||
|
||||
protected String changeSessionId(Session session) {
|
||||
return session.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SamlSession getAccount() {
|
||||
HttpSession session = getSession(true);
|
||||
|
|
|
@ -6,6 +6,10 @@ import org.apache.catalina.core.StandardContext;
|
|||
import org.apache.catalina.realm.GenericPrincipal;
|
||||
import org.apache.tomcat.util.descriptor.web.LoginConfig;
|
||||
import org.keycloak.adapters.saml.AbstractSamlAuthenticatorValve;
|
||||
import org.keycloak.adapters.saml.CatalinaSamlSessionStore;
|
||||
import org.keycloak.adapters.saml.SamlDeployment;
|
||||
import org.keycloak.adapters.saml.SamlSessionStore;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.adapters.tomcat.GenericPrincipalFactory;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
@ -68,4 +72,12 @@ public class SamlAuthenticatorValve extends AbstractSamlAuthenticatorValve {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SamlSessionStore createSessionStore(Request request, HttpFacade facade, SamlDeployment resolvedDeployment) {
|
||||
SamlSessionStore store;
|
||||
store = new Tomcat8SamlSessionStore(userSessionManagement, createPrincipalFactory(), mapper, request, this, facade, resolvedDeployment);
|
||||
return store;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
package org.keycloak.adapters.saml.tomcat;
|
||||
|
||||
import org.apache.catalina.Session;
|
||||
import org.apache.catalina.connector.Request;
|
||||
import org.keycloak.adapters.saml.AbstractSamlAuthenticatorValve;
|
||||
import org.keycloak.adapters.saml.CatalinaSamlSessionStore;
|
||||
import org.keycloak.adapters.saml.SamlDeployment;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.adapters.spi.SessionIdMapper;
|
||||
import org.keycloak.adapters.tomcat.CatalinaUserSessionManagement;
|
||||
import org.keycloak.adapters.tomcat.GenericPrincipalFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class Tomcat8SamlSessionStore extends CatalinaSamlSessionStore {
|
||||
public Tomcat8SamlSessionStore(CatalinaUserSessionManagement sessionManagement, GenericPrincipalFactory principalFactory, SessionIdMapper idMapper, Request request, AbstractSamlAuthenticatorValve valve, HttpFacade facade, SamlDeployment deployment) {
|
||||
super(sessionManagement, principalFactory, idMapper, request, valve, facade, deployment);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String changeSessionId(Session session) {
|
||||
Request request = this.request;
|
||||
if (deployment.turnOffChangeSessionIdOnLogin() == false) return request.changeSessionId();
|
||||
else return session.getId();
|
||||
}
|
||||
}
|
|
@ -35,7 +35,7 @@ import org.keycloak.adapters.saml.SamlDeployment;
|
|||
import org.keycloak.adapters.saml.SamlDeploymentContext;
|
||||
import org.keycloak.adapters.saml.config.parsers.DeploymentBuilder;
|
||||
import org.keycloak.adapters.saml.config.parsers.ResourceLoader;
|
||||
import org.keycloak.adapters.undertow.ChangeSessionIdOnLogin;
|
||||
import org.keycloak.adapters.undertow.ChangeSessionId;
|
||||
import org.keycloak.adapters.undertow.UndertowUserSessionManagement;
|
||||
import org.keycloak.saml.common.exceptions.ParsingException;
|
||||
|
||||
|
@ -184,7 +184,7 @@ public class SamlServletExtension implements ServletExtension {
|
|||
ServletSessionConfig cookieConfig = new ServletSessionConfig();
|
||||
cookieConfig.setPath(deploymentInfo.getContextPath());
|
||||
deploymentInfo.setServletSessionConfig(cookieConfig);
|
||||
ChangeSessionIdOnLogin.turnOffChangeSessionIdOnLogin(deploymentInfo);
|
||||
ChangeSessionId.turnOffChangeSessionIdOnLogin(deploymentInfo);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ public class ServletSamlAuthMech extends AbstractSamlAuthMech {
|
|||
|
||||
@Override
|
||||
protected SamlSessionStore getTokenStore(HttpServerExchange exchange, HttpFacade facade, SamlDeployment deployment, SecurityContext securityContext) {
|
||||
return new ServletSamlSessionStore(exchange, sessionManagement, securityContext, idMapper);
|
||||
return new ServletSamlSessionStore(exchange, sessionManagement, securityContext, idMapper, deployment);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -7,9 +7,11 @@ import io.undertow.server.session.SessionManager;
|
|||
import io.undertow.servlet.handlers.ServletRequestContext;
|
||||
import io.undertow.servlet.spec.HttpSessionImpl;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.adapters.saml.SamlDeployment;
|
||||
import org.keycloak.adapters.spi.SessionIdMapper;
|
||||
import org.keycloak.adapters.saml.SamlSession;
|
||||
import org.keycloak.adapters.saml.SamlSessionStore;
|
||||
import org.keycloak.adapters.undertow.ChangeSessionId;
|
||||
import org.keycloak.adapters.undertow.SavedRequest;
|
||||
import org.keycloak.adapters.undertow.UndertowUserSessionManagement;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
|
@ -36,15 +38,17 @@ public class ServletSamlSessionStore implements SamlSessionStore {
|
|||
private final UndertowUserSessionManagement sessionManagement;
|
||||
private final SecurityContext securityContext;
|
||||
private final SessionIdMapper idMapper;
|
||||
protected final SamlDeployment deployment;
|
||||
|
||||
|
||||
public ServletSamlSessionStore(HttpServerExchange exchange, UndertowUserSessionManagement sessionManagement,
|
||||
SecurityContext securityContext,
|
||||
SessionIdMapper idMapper) {
|
||||
SessionIdMapper idMapper, SamlDeployment deployment) {
|
||||
this.exchange = exchange;
|
||||
this.sessionManagement = sessionManagement;
|
||||
this.securityContext = securityContext;
|
||||
this.idMapper = idMapper;
|
||||
this.deployment = deployment;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -155,10 +159,16 @@ public class ServletSamlSessionStore implements SamlSessionStore {
|
|||
HttpSession session = getSession(true);
|
||||
session.setAttribute(SamlSession.class.getName(), account);
|
||||
sessionManagement.login(servletRequestContext.getDeployment().getSessionManager());
|
||||
idMapper.map(account.getSessionIndex(), account.getPrincipal().getSamlSubject(), session.getId());
|
||||
String sessionId = changeSessionId(session);
|
||||
idMapper.map(account.getSessionIndex(), account.getPrincipal().getSamlSubject(), sessionId);
|
||||
|
||||
}
|
||||
|
||||
protected String changeSessionId(HttpSession session) {
|
||||
if (deployment.turnOffChangeSessionIdOnLogin() == false) return ChangeSessionId.changeSessionId(exchange, false);
|
||||
else return session.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SamlSession getAccount() {
|
||||
HttpSession session = getSession(true);
|
||||
|
|
|
@ -20,6 +20,6 @@ public class WildflySamlAuthMech extends ServletSamlAuthMech {
|
|||
|
||||
@Override
|
||||
protected SamlSessionStore getTokenStore(HttpServerExchange exchange, HttpFacade facade, SamlDeployment deployment, SecurityContext securityContext) {
|
||||
return new WildflySamlSessionStore(exchange, sessionManagement, securityContext, idMapper);
|
||||
return new WildflySamlSessionStore(exchange, sessionManagement, securityContext, idMapper, deployment);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.keycloak.adapters.saml.wildfly;
|
|||
|
||||
import io.undertow.security.api.SecurityContext;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import org.keycloak.adapters.saml.SamlDeployment;
|
||||
import org.keycloak.adapters.spi.SessionIdMapper;
|
||||
import org.keycloak.adapters.saml.SamlSession;
|
||||
import org.keycloak.adapters.saml.undertow.ServletSamlSessionStore;
|
||||
|
@ -13,8 +14,8 @@ import org.keycloak.adapters.undertow.UndertowUserSessionManagement;
|
|||
*/
|
||||
public class WildflySamlSessionStore extends ServletSamlSessionStore {
|
||||
public WildflySamlSessionStore(HttpServerExchange exchange, UndertowUserSessionManagement sessionManagement,
|
||||
SecurityContext securityContext, SessionIdMapper idMapper) {
|
||||
super(exchange, sessionManagement, securityContext, idMapper);
|
||||
SecurityContext securityContext, SessionIdMapper idMapper, SamlDeployment resolvedDeployment) {
|
||||
super(exchange, sessionManagement, securityContext, idMapper, resolvedDeployment);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
4
adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/Constants.java
Normal file → Executable file
4
adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/Constants.java
Normal file → Executable file
|
@ -30,6 +30,8 @@ public class Constants {
|
|||
static final String NAME_ID_POLICY_FORMAT = "name-id-policy-format";
|
||||
static final String LOGOUT_PAGE = "logout-page";
|
||||
static final String FORCE_AUTHENTICATION = "force-authentication";
|
||||
static final String IS_PASSIVE = "isPassive";
|
||||
static final String TURN_OFF_CHANGE_SESSSION_ID_ON_LOGIN = "turnOffChangeSessionIdOnLogin";
|
||||
static final String ROLE_ATTRIBUTES = "role-attributes";
|
||||
static final String SIGNING = "signing";
|
||||
static final String ENCRYPTION = "encryption";
|
||||
|
@ -87,6 +89,8 @@ public class Constants {
|
|||
static final String KEY_STORE = "KeyStore";
|
||||
static final String PRIVATE_KEY = "PrivateKey";
|
||||
static final String CERTIFICATE = "Certificate";
|
||||
static final String IS_PASSIVE = "isPassive";
|
||||
static final String TURN_OFF_CHANGE_SESSSION_ID_ON_LOGIN = "turnOffChangeSessionIdOnLogin";
|
||||
|
||||
static final String PRIVATE_KEY_ALIAS = "alias";
|
||||
static final String PRIVATE_KEY_PASSWORD = "password";
|
||||
|
|
11
adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/ServiceProviderDefinition.java
Normal file → Executable file
11
adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/ServiceProviderDefinition.java
Normal file → Executable file
|
@ -59,6 +59,15 @@ public class ServiceProviderDefinition extends SimpleResourceDefinition {
|
|||
.setXmlName(Constants.XML.FORCE_AUTHENTICATION)
|
||||
.build();
|
||||
|
||||
static final SimpleAttributeDefinition IS_PASSIVE =
|
||||
new SimpleAttributeDefinitionBuilder(Constants.Model.IS_PASSIVE, ModelType.BOOLEAN, true)
|
||||
.setXmlName(Constants.XML.IS_PASSIVE)
|
||||
.build();
|
||||
static final SimpleAttributeDefinition TURN_OFF_CHANGE_SESSSION_ID_ON_LOGIN =
|
||||
new SimpleAttributeDefinitionBuilder(Constants.Model.TURN_OFF_CHANGE_SESSSION_ID_ON_LOGIN, ModelType.BOOLEAN, true)
|
||||
.setXmlName(Constants.XML.TURN_OFF_CHANGE_SESSSION_ID_ON_LOGIN)
|
||||
.build();
|
||||
|
||||
static final SimpleAttributeDefinition PRINCIPAL_NAME_MAPPING_POLICY =
|
||||
new SimpleAttributeDefinitionBuilder(Constants.Model.PRINCIPAL_NAME_MAPPING_POLICY, ModelType.STRING, true)
|
||||
.setXmlName(Constants.XML.PRINCIPAL_NAME_MAPPING_POLICY)
|
||||
|
@ -74,7 +83,7 @@ public class ServiceProviderDefinition extends SimpleResourceDefinition {
|
|||
.setAllowNull(false)
|
||||
.build();
|
||||
|
||||
static final SimpleAttributeDefinition[] ATTRIBUTES = {SSL_POLICY, NAME_ID_POLICY_FORMAT, LOGOUT_PAGE, FORCE_AUTHENTICATION};
|
||||
static final SimpleAttributeDefinition[] ATTRIBUTES = {SSL_POLICY, NAME_ID_POLICY_FORMAT, LOGOUT_PAGE, FORCE_AUTHENTICATION, IS_PASSIVE, TURN_OFF_CHANGE_SESSSION_ID_ON_LOGIN};
|
||||
static final AttributeDefinition[] ELEMENTS = {PRINCIPAL_NAME_MAPPING_POLICY, PRINCIPAL_NAME_MAPPING_ATTRIBUTE_NAME, ROLE_ATTRIBUTES};
|
||||
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@ keycloak-saml.service-provider.ssl-policy=SSL Policy to use
|
|||
keycloak-saml.service-provider.name-id-policy-format=Name ID policy format URN
|
||||
keycloak-saml.service-provider.logout-page=URI to a logout page
|
||||
keycloak-saml.service-provider.force-authentication=Redirected unauthenticated request to a login page
|
||||
keycloak-saml.service-provider.isPassive=If user isn't logged in just return with an error. Used to check if a user is already logged in or not
|
||||
keycloak-saml.service-provider.turnOffChangeSessionIdOnLogin=The session id is changed by default on a successful login. Change this to true if you want to turn this off
|
||||
keycloak-saml.service-provider.role-attributes=Role identifiers
|
||||
keycloak-saml.service-provider.principal-name-mapping-policy=Principal name mapping policy
|
||||
keycloak-saml.service-provider.principal-name-mapping-attribute-name=Principal name mapping attribute name
|
||||
|
|
|
@ -66,6 +66,16 @@
|
|||
<xs:documentation>Redirected unauthenticated request to a login page</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="isPassive" type="xs:boolean" use="required">
|
||||
<xs:annotation>
|
||||
<xs:documentation>If user isn't logged in just return with an error. Used to check if a user is already logged in or not</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="turnOffChangeSessionIdOnLogin" type="xs:boolean" use="required">
|
||||
<xs:annotation>
|
||||
<xs:documentation>The session id is changed by default on a successful login. Change this to true if you want to turn this off</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="identity-provider-type">
|
||||
<xs:all minOccurs="1" maxOccurs="1">
|
||||
|
|
4
adapters/saml/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/saml/extension/keycloak-saml-1.1.xml
Normal file → Executable file
4
adapters/saml/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/saml/extension/keycloak-saml-1.1.xml
Normal file → Executable file
|
@ -4,7 +4,9 @@
|
|||
sslPolicy="EXTERNAL"
|
||||
nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
|
||||
logoutPage="/logout.jsp"
|
||||
forceAuthentication="false">
|
||||
forceAuthentication="false"
|
||||
isPassive="true"
|
||||
turnOffChangeSessionIdOnLogin="true">
|
||||
|
||||
<Keys>
|
||||
<Key encryption="true" signing="true">
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.9.0.CR1-SNAPSHOT</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
|
@ -19,5 +19,6 @@
|
|||
<module>undertow-adapter-spi</module>
|
||||
<module>servlet-adapter-spi</module>
|
||||
<module>jboss-adapter-core</module>
|
||||
<module>jetty-adapter-spi</module>
|
||||
</modules>
|
||||
</project>
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
package org.keycloak.adapters.undertow;
|
||||
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import io.undertow.server.session.Session;
|
||||
import io.undertow.server.session.SessionConfig;
|
||||
import io.undertow.server.session.SessionManager;
|
||||
import io.undertow.servlet.api.DeploymentInfo;
|
||||
import io.undertow.servlet.handlers.ServletRequestContext;
|
||||
import io.undertow.servlet.spec.HttpSessionImpl;
|
||||
import io.undertow.servlet.spec.ServletContextImpl;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.security.AccessController;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class ChangeSessionId {
|
||||
/**
|
||||
* This is a hack to be backward compatible between Undertow 1.3+ and versions lower. In Undertow 1.3, a new
|
||||
* switch was added setChangeSessionIdOnLogin, this screws up session management for keycloak as after the session id
|
||||
* is uploaded to Keycloak, undertow changes the session id and it can't be invalidated.
|
||||
*
|
||||
* @param deploymentInfo
|
||||
*/
|
||||
public static void turnOffChangeSessionIdOnLogin(DeploymentInfo deploymentInfo) {
|
||||
try {
|
||||
Method method = DeploymentInfo.class.getMethod("setChangeSessionIdOnLogin", boolean.class);
|
||||
method.invoke(deploymentInfo, false);
|
||||
} catch (Exception ignore) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static String changeSessionId(HttpServerExchange exchange, boolean create) {
|
||||
final ServletRequestContext sc = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
|
||||
ServletContextImpl currentServletContext = sc.getCurrentServletContext();
|
||||
HttpSessionImpl session = currentServletContext.getSession(exchange, create);
|
||||
if (session == null) {
|
||||
return null;
|
||||
}
|
||||
Session underlyingSession;
|
||||
if(System.getSecurityManager() == null) {
|
||||
underlyingSession = session.getSession();
|
||||
} else {
|
||||
underlyingSession = AccessController.doPrivileged(new HttpSessionImpl.UnwrapSessionAction(session));
|
||||
}
|
||||
|
||||
|
||||
return underlyingSession.changeSessionId(exchange, currentServletContext.getSessionConfig());
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
package org.keycloak.adapters.undertow;
|
||||
|
||||
import io.undertow.servlet.api.DeploymentInfo;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class ChangeSessionIdOnLogin {
|
||||
/**
|
||||
* This is a hack to be backward compatible between Undertow 1.3+ and versions lower. In Undertow 1.3, a new
|
||||
* switch was added setChangeSessionIdOnLogin, this screws up session management for keycloak as after the session id
|
||||
* is uploaded to Keycloak, undertow changes the session id and it can't be invalidated.
|
||||
*
|
||||
* @param deploymentInfo
|
||||
*/
|
||||
public static void turnOffChangeSessionIdOnLogin(DeploymentInfo deploymentInfo) {
|
||||
try {
|
||||
Method method = DeploymentInfo.class.getMethod("setChangeSessionIdOnLogin", boolean.class);
|
||||
method.invoke(deploymentInfo, false);
|
||||
} catch (Exception ignore) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -50,6 +50,8 @@ public class AdapterConfig extends BaseAdapterConfig {
|
|||
protected String tokenStore;
|
||||
@JsonProperty("principal-attribute")
|
||||
protected String principalAttribute;
|
||||
@JsonProperty("turn-off-change-session-id-on-login")
|
||||
protected Boolean turnOffChangeSessionIdOnLogin;
|
||||
|
||||
public boolean isAllowAnyHostname() {
|
||||
return allowAnyHostname;
|
||||
|
@ -162,4 +164,12 @@ public class AdapterConfig extends BaseAdapterConfig {
|
|||
public void setPrincipalAttribute(String principalAttribute) {
|
||||
this.principalAttribute = principalAttribute;
|
||||
}
|
||||
|
||||
public Boolean getTurnOffChangeSessionIdOnLogin() {
|
||||
return turnOffChangeSessionIdOnLogin;
|
||||
}
|
||||
|
||||
public void setTurnOffChangeSessionIdOnLogin(Boolean turnOffChangeSessionIdOnLogin) {
|
||||
this.turnOffChangeSessionIdOnLogin = turnOffChangeSessionIdOnLogin;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -104,6 +104,14 @@
|
|||
with mongo and infinispan under modules keycloak-model-mongo and keycloak-model-infinispan.
|
||||
</para>
|
||||
</simplesect>
|
||||
<simplesect>
|
||||
<title>For adapters, session id changed after login</title>
|
||||
<para>
|
||||
To plug a security attack vector, for platforms that support it (Tomcat 8, Undertow/Wildfly, Jetty 9),
|
||||
the keycloak oidc and saml adapters will change the session id after login. You can turn off this behavior
|
||||
check adapter config switches.
|
||||
</para>
|
||||
</simplesect>
|
||||
</section>
|
||||
<section>
|
||||
<title>Migrating to 1.8.0</title>
|
||||
|
|
|
@ -379,6 +379,15 @@
|
|||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>turn-off-change-session-id-on-login</term>
|
||||
<listitem>
|
||||
<para>
|
||||
The session id is changed by default on a successful login on some platforms to plug a security attack vector (Tomcat 8, Jetty9, Undertow/Wildfly). Change this to true if you want to turn this off
|
||||
This is <emphasis>OPTIONAL</emphasis>. The default value is <emphasis>false</emphasis>.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</para>
|
||||
</section>
|
||||
|
|
|
@ -12,7 +12,8 @@
|
|||
nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
|
||||
logoutPage="/logout.jsp"
|
||||
forceAuthentication="false"
|
||||
isPassive="false">
|
||||
isPassive="false"
|
||||
turnOffChangeSessionIdOnLogin="false">
|
||||
<Keys>
|
||||
<Key signing="true" >
|
||||
<KeyStore resource="/WEB-INF/keystore.jks" password="store123">
|
||||
|
@ -125,6 +126,15 @@
|
|||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>turnOffChangeSessionIdOnLogin</term>
|
||||
<listitem>
|
||||
<para>
|
||||
The session id is changed by default on a successful login on some platforms to plug a security attack vector (Tomcat 8, Jetty9, Undertow/Wildfly). Change this to true if you want to turn this off
|
||||
This is <emphasis>OPTIONAL</emphasis>. The default value is <emphasis>false</emphasis>.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</para>
|
||||
</section>
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
package org.keycloak.testsuite.keycloaksaml;
|
||||
|
||||
import org.keycloak.testsuite.pages.AbstractPage;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class InputPage extends AbstractPage {
|
||||
@FindBy(id = "parameter")
|
||||
private WebElement parameter;
|
||||
|
||||
@FindBy(name = "submit")
|
||||
private WebElement submit;
|
||||
|
||||
public void execute(String param) {
|
||||
parameter.clear();
|
||||
parameter.sendKeys(param);
|
||||
|
||||
submit.click();
|
||||
}
|
||||
|
||||
|
||||
public boolean isCurrent() {
|
||||
return driver.getTitle().equals("Input Page");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open() {
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package org.keycloak.testsuite.keycloaksaml;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bburke@redhat.com">Bill Burke</a>
|
||||
*/
|
||||
public class InputServlet extends HttpServlet {
|
||||
|
||||
private static final String FORM_URLENCODED = "application/x-www-form-urlencoded";
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
String appBase = System.getProperty("app.server.base.url", "http://localhost:8081");
|
||||
String actionUrl = appBase + "/input-portal/secured/post";
|
||||
|
||||
|
||||
resp.setContentType("text/html");
|
||||
PrintWriter pw = resp.getWriter();
|
||||
pw.printf("<html><head><title>%s</title></head><body>", "Input Page");
|
||||
pw.printf("<form action=\"%s\" method=\"POST\">", actionUrl);
|
||||
pw.println("<input id=\"parameter\" type=\"text\" name=\"parameter\">");
|
||||
pw.println("<input name=\"submit\" type=\"submit\" value=\"Submit\"></form>");
|
||||
pw.print("</body></html>");
|
||||
pw.flush();
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
if (!FORM_URLENCODED.equals(req.getContentType())) {
|
||||
resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
|
||||
PrintWriter pw = resp.getWriter();
|
||||
resp.setContentType("text/plain");
|
||||
pw.printf("Expecting content type " + FORM_URLENCODED +
|
||||
", received " + req.getContentType() + " instead");
|
||||
pw.flush();
|
||||
return;
|
||||
}
|
||||
resp.setContentType("text/plain");
|
||||
PrintWriter pw = resp.getWriter();
|
||||
pw.printf("parameter="+req.getParameter("parameter"));
|
||||
pw.flush();
|
||||
}
|
||||
|
||||
}
|
|
@ -31,6 +31,8 @@ public class SamlAdapterTest {
|
|||
initializeSamlSecuredWar("/keycloak-saml/bad-client-signed-post", "/bad-client-sales-post-sig", "bad-client-post-sig.war", classLoader);
|
||||
initializeSamlSecuredWar("/keycloak-saml/bad-realm-signed-post", "/bad-realm-sales-post-sig", "bad-realm-post-sig.war", classLoader);
|
||||
initializeSamlSecuredWar("/keycloak-saml/encrypted-post", "/sales-post-enc", "post-enc.war", classLoader);
|
||||
System.setProperty("app.server.base.url", "http://localhost:8081");
|
||||
initializeSamlSecuredWar("/keycloak-saml/simple-input", "/input-portal", "input.war", classLoader, InputServlet.class, "/secured/*");
|
||||
SamlAdapterTestStrategy.uploadSP("http://localhost:8081/auth");
|
||||
server.getServer().deploy(createDeploymentInfo("employee.war", "/employee", SamlSPFacade.class));
|
||||
|
||||
|
@ -63,6 +65,11 @@ public class SamlAdapterTest {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
//@Test Doesn't work for Wildfly as the input stream is read by getParameter for SAML POST binding
|
||||
public void testSavedPostRequest() throws Exception {
|
||||
testStrategy.testSavedPostRequest();
|
||||
}
|
||||
@Test
|
||||
public void testErrorHandling() throws Exception {
|
||||
testStrategy.testErrorHandling();
|
||||
|
|
|
@ -3,6 +3,7 @@ package org.keycloak.testsuite.keycloaksaml;
|
|||
import org.apache.commons.io.IOUtils;
|
||||
import org.junit.Assert;
|
||||
import org.junit.rules.ExternalResource;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.adapters.saml.SamlAuthenticationError;
|
||||
import org.keycloak.adapters.saml.SamlPrincipal;
|
||||
import org.keycloak.admin.client.Keycloak;
|
||||
|
@ -12,6 +13,7 @@ import org.keycloak.models.Constants;
|
|||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
||||
import org.keycloak.protocol.saml.mappers.AttributeStatementHelper;
|
||||
import org.keycloak.protocol.saml.mappers.GroupMembershipMapper;
|
||||
import org.keycloak.protocol.saml.mappers.HardcodedAttributeMapper;
|
||||
|
@ -27,6 +29,7 @@ import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
|||
import org.keycloak.saml.processing.core.saml.v2.constants.X500SAMLProfileConstants;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.testsuite.KeycloakServer;
|
||||
import org.keycloak.testsuite.adapter.*;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
import org.keycloak.testsuite.rule.AbstractKeycloakRule;
|
||||
import org.keycloak.testsuite.rule.ErrorServlet;
|
||||
|
@ -38,7 +41,10 @@ import org.w3c.dom.Document;
|
|||
|
||||
import javax.ws.rs.client.Client;
|
||||
import javax.ws.rs.client.ClientBuilder;
|
||||
import javax.ws.rs.client.Entity;
|
||||
import javax.ws.rs.core.Form;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.HashSet;
|
||||
|
@ -70,6 +76,8 @@ public class SamlAdapterTestStrategy extends ExternalResource {
|
|||
protected WebDriver driver;
|
||||
@WebResource
|
||||
protected LoginPage loginPage;
|
||||
@WebResource
|
||||
protected InputPage inputPage;
|
||||
|
||||
@Override
|
||||
protected void before() throws Throwable {
|
||||
|
@ -101,6 +109,38 @@ public class SamlAdapterTestStrategy extends ExternalResource {
|
|||
Assert.assertTrue(driver.getCurrentUrl().startsWith(AUTH_SERVER_URL + "/realms/demo/protocol/saml"));
|
||||
}
|
||||
|
||||
public void testSavedPostRequest() throws Exception {
|
||||
// test login to customer-portal which does a bearer request to customer-db
|
||||
driver.navigate().to(APP_SERVER_BASE_URL + "/input-portal");
|
||||
System.out.println("Current url: " + driver.getCurrentUrl());
|
||||
Assert.assertTrue(driver.getCurrentUrl().startsWith(APP_SERVER_BASE_URL + "/input-portal"));
|
||||
inputPage.execute("hello");
|
||||
|
||||
assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
|
||||
loginPage.login("bburke@redhat.com", "password");
|
||||
System.out.println("Current url: " + driver.getCurrentUrl());
|
||||
Assert.assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/input-portal/secured/post");
|
||||
String pageSource = driver.getPageSource();
|
||||
System.out.println(pageSource);
|
||||
Assert.assertTrue(pageSource.contains("parameter=hello"));
|
||||
|
||||
// test logout
|
||||
|
||||
driver.navigate().to(APP_SERVER_BASE_URL + "/input-portal?GLO=true");
|
||||
|
||||
// test unsecured POST KEYCLOAK-901
|
||||
|
||||
Client client = ClientBuilder.newClient();
|
||||
Form form = new Form();
|
||||
form.param("parameter", "hello");
|
||||
String text = client.target(APP_SERVER_BASE_URL + "/input-portal/unsecured").request().post(Entity.form(form), String.class);
|
||||
Assert.assertTrue(text.contains("parameter=hello"));
|
||||
client.close();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void testErrorHandling() throws Exception {
|
||||
ErrorServlet.authError = null;
|
||||
Client client = ClientBuilder.newClient();
|
||||
|
|
|
@ -15,6 +15,7 @@ import io.undertow.servlet.api.WebResourceCollection;
|
|||
import org.keycloak.adapters.saml.undertow.SamlServletExtension;
|
||||
import org.keycloak.testsuite.rule.AbstractKeycloakRule;
|
||||
|
||||
import javax.servlet.Servlet;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
|
||||
|
@ -92,12 +93,19 @@ public abstract class SamlKeycloakRule extends AbstractKeycloakRule {
|
|||
|
||||
public void initializeSamlSecuredWar(String warResourcePath, String contextPath, String warDeploymentName, ClassLoader classLoader) {
|
||||
|
||||
ServletInfo regularServletInfo = new ServletInfo("servlet", SendUsernameServlet.class)
|
||||
Class<SendUsernameServlet> servletClass = SendUsernameServlet.class;
|
||||
String constraintUrl = "/*";
|
||||
|
||||
initializeSamlSecuredWar(warResourcePath, contextPath, warDeploymentName, classLoader, servletClass, constraintUrl);
|
||||
}
|
||||
|
||||
public void initializeSamlSecuredWar(String warResourcePath, String contextPath, String warDeploymentName, ClassLoader classLoader, Class<? extends Servlet> servletClass, String constraintUrl) {
|
||||
ServletInfo regularServletInfo = new ServletInfo("servlet", servletClass)
|
||||
.addMapping("/*");
|
||||
|
||||
SecurityConstraint constraint = new SecurityConstraint();
|
||||
WebResourceCollection collection = new WebResourceCollection();
|
||||
collection.addUrlPattern("/*");
|
||||
collection.addUrlPattern(constraintUrl);
|
||||
constraint.addWebResourceCollection(collection);
|
||||
constraint.addRoleAllowed("manager");
|
||||
constraint.addRoleAllowed("el-jefe");
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<keycloak-saml-adapter>
|
||||
<SP entityID="http://localhost:8081/input-portal/"
|
||||
sslPolicy="EXTERNAL"
|
||||
nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
|
||||
logoutPage="/logout.jsp"
|
||||
forceAuthentication="false">
|
||||
<PrincipalNameMapping policy="FROM_NAME_ID"/>
|
||||
<RoleIdentifiers>
|
||||
<Attribute name="Role"/>
|
||||
</RoleIdentifiers>
|
||||
<IDP entityID="idp">
|
||||
<SingleSignOnService requestBinding="POST"
|
||||
bindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
|
||||
/>
|
||||
|
||||
<SingleLogoutService
|
||||
requestBinding="POST"
|
||||
responseBinding="POST"
|
||||
postBindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
|
||||
redirectBindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
|
||||
/>
|
||||
</IDP>
|
||||
</SP>
|
||||
</keycloak-saml-adapter>
|
|
@ -85,6 +85,23 @@
|
|||
"saml_idp_initiated_sso_url_name": "sales-post"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "http://localhost:8081/input-portal/",
|
||||
"enabled": true,
|
||||
"fullScopeAllowed": true,
|
||||
"protocol": "saml",
|
||||
"baseUrl": "http://localhost:8081/input-portal/",
|
||||
"redirectUris": [
|
||||
"http://localhost:8081/input-portal/*"
|
||||
],
|
||||
"attributes": {
|
||||
"saml.authnstatement": "true",
|
||||
"saml_assertion_consumer_url_post": "http://localhost:8081/input-portal/",
|
||||
"saml_assertion_consumer_url_redirect": "http://localhost:8081/input-portal/",
|
||||
"saml_single_logout_service_url_post": "http://localhost:8081/input-portal/",
|
||||
"saml_single_logout_service_url_redirect": "http://localhost:8081/input-portal/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "http://localhost:8081/sales-post-passive/",
|
||||
"enabled": true,
|
||||
|
|
Loading…
Reference in a new issue