KEYCLOAK-10802 add support of SAMLv2 ForceAuthn
This commit is contained in:
parent
6283c7add3
commit
2fb507e170
4 changed files with 71 additions and 3 deletions
|
@ -118,6 +118,8 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
public static final String SAML_PERSISTENT_NAME_ID_FOR = "saml.persistent.name.id.for";
|
public static final String SAML_PERSISTENT_NAME_ID_FOR = "saml.persistent.name.id.for";
|
||||||
public static final String SAML_IDP_INITIATED_SSO_RELAY_STATE = "saml_idp_initiated_sso_relay_state";
|
public static final String SAML_IDP_INITIATED_SSO_RELAY_STATE = "saml_idp_initiated_sso_relay_state";
|
||||||
public static final String SAML_IDP_INITIATED_SSO_URL_NAME = "saml_idp_initiated_sso_url_name";
|
public static final String SAML_IDP_INITIATED_SSO_URL_NAME = "saml_idp_initiated_sso_url_name";
|
||||||
|
public static final String SAML_LOGIN_REQUEST_FORCEAUTHN = "SAML_LOGIN_REQUEST_FORCEAUTHN";
|
||||||
|
public static final String SAML_FORCEAUTHN_REQUIREMENT = "true";
|
||||||
|
|
||||||
protected KeycloakSession session;
|
protected KeycloakSession session;
|
||||||
|
|
||||||
|
@ -726,8 +728,8 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean requireReauthentication(UserSessionModel userSession, AuthenticationSessionModel authSession) {
|
public boolean requireReauthentication(UserSessionModel userSession, AuthenticationSessionModel authSession) {
|
||||||
// Not yet supported
|
String requireReauthentication = authSession.getAuthNote(SamlProtocol.SAML_LOGIN_REQUEST_FORCEAUTHN);
|
||||||
return false;
|
return Objects.equals(SamlProtocol.SAML_FORCEAUTHN_REQUIREMENT, requireReauthentication);
|
||||||
}
|
}
|
||||||
|
|
||||||
private JaxrsSAML2BindingBuilder createBindingBuilder(SamlClient samlClient) {
|
private JaxrsSAML2BindingBuilder createBindingBuilder(SamlClient samlClient) {
|
||||||
|
|
|
@ -352,6 +352,11 @@ public class SamlService extends AuthorizationEndpointBase {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (null != requestAbstractType.isForceAuthn()
|
||||||
|
&& requestAbstractType.isForceAuthn()) {
|
||||||
|
authSession.setAuthNote(SamlProtocol.SAML_LOGIN_REQUEST_FORCEAUTHN, SamlProtocol.SAML_FORCEAUTHN_REQUIREMENT);
|
||||||
|
}
|
||||||
//If unset we fall back to default "false"
|
//If unset we fall back to default "false"
|
||||||
final boolean isPassive = (null == requestAbstractType.isIsPassive() ?
|
final boolean isPassive = (null == requestAbstractType.isIsPassive() ?
|
||||||
false : requestAbstractType.isIsPassive().booleanValue());
|
false : requestAbstractType.isIsPassive().booleanValue());
|
||||||
|
|
|
@ -199,7 +199,7 @@ public class KcSamlBrokerConfiguration implements BrokerConfiguration {
|
||||||
config.put(SINGLE_SIGN_ON_SERVICE_URL, getAuthRoot(suiteContext) + "/auth/realms/" + REALM_PROV_NAME + "/protocol/saml");
|
config.put(SINGLE_SIGN_ON_SERVICE_URL, getAuthRoot(suiteContext) + "/auth/realms/" + REALM_PROV_NAME + "/protocol/saml");
|
||||||
config.put(SINGLE_LOGOUT_SERVICE_URL, getAuthRoot(suiteContext) + "/auth/realms/" + REALM_PROV_NAME + "/protocol/saml");
|
config.put(SINGLE_LOGOUT_SERVICE_URL, getAuthRoot(suiteContext) + "/auth/realms/" + REALM_PROV_NAME + "/protocol/saml");
|
||||||
config.put(NAME_ID_POLICY_FORMAT, "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress");
|
config.put(NAME_ID_POLICY_FORMAT, "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress");
|
||||||
config.put(FORCE_AUTHN, "true");
|
config.put(FORCE_AUTHN, "false");
|
||||||
config.put(POST_BINDING_RESPONSE, "true");
|
config.put(POST_BINDING_RESPONSE, "true");
|
||||||
config.put(POST_BINDING_AUTHN_REQUEST, "true");
|
config.put(POST_BINDING_AUTHN_REQUEST, "true");
|
||||||
config.put(VALIDATE_SIGNATURE, "false");
|
config.put(VALIDATE_SIGNATURE, "false");
|
||||||
|
|
|
@ -5,6 +5,7 @@ import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
|
||||||
import org.keycloak.protocol.saml.SamlProtocol;
|
import org.keycloak.protocol.saml.SamlProtocol;
|
||||||
import org.keycloak.saml.SignatureAlgorithm;
|
import org.keycloak.saml.SignatureAlgorithm;
|
||||||
import org.keycloak.saml.common.constants.GeneralConstants;
|
import org.keycloak.saml.common.constants.GeneralConstants;
|
||||||
|
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
||||||
import org.keycloak.saml.common.exceptions.ConfigurationException;
|
import org.keycloak.saml.common.exceptions.ConfigurationException;
|
||||||
import org.keycloak.saml.common.exceptions.ParsingException;
|
import org.keycloak.saml.common.exceptions.ParsingException;
|
||||||
import org.keycloak.saml.common.exceptions.ProcessingException;
|
import org.keycloak.saml.common.exceptions.ProcessingException;
|
||||||
|
@ -13,12 +14,19 @@ import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
|
||||||
import org.keycloak.saml.processing.web.util.RedirectBindingUtil;
|
import org.keycloak.saml.processing.web.util.RedirectBindingUtil;
|
||||||
import org.keycloak.services.resources.RealmsResource;
|
import org.keycloak.services.resources.RealmsResource;
|
||||||
import org.keycloak.testsuite.util.KeyUtils;
|
import org.keycloak.testsuite.util.KeyUtils;
|
||||||
|
import org.keycloak.testsuite.util.Matchers;
|
||||||
import org.keycloak.testsuite.util.SamlClient;
|
import org.keycloak.testsuite.util.SamlClient;
|
||||||
import org.keycloak.testsuite.util.SamlClient.Binding;
|
import org.keycloak.testsuite.util.SamlClient.Binding;
|
||||||
import org.keycloak.testsuite.util.SamlClient.RedirectStrategyWithSwitchableFollowRedirect;
|
import org.keycloak.testsuite.util.SamlClient.RedirectStrategyWithSwitchableFollowRedirect;
|
||||||
|
import org.keycloak.testsuite.util.SamlClient.Step;
|
||||||
import org.keycloak.testsuite.util.SamlClientBuilder;
|
import org.keycloak.testsuite.util.SamlClientBuilder;
|
||||||
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.security.Signature;
|
import java.security.Signature;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.Response.Status;
|
import javax.ws.rs.core.Response.Status;
|
||||||
import javax.ws.rs.core.UriBuilder;
|
import javax.ws.rs.core.UriBuilder;
|
||||||
|
@ -177,4 +185,57 @@ public class BasicSamlTest extends AbstractSamlTest {
|
||||||
assertThat(EntityUtils.toString(response.getEntity(), "UTF-8"), pageTextMatcher);
|
assertThat(EntityUtils.toString(response.getEntity(), "UTF-8"), pageTextMatcher);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReauthnWithForceAuthnNotSet() throws Exception {
|
||||||
|
testReauthnWithForceAuthn(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReauthnWithForceAuthnFalse() throws Exception {
|
||||||
|
testReauthnWithForceAuthn(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReauthnWithForceAuthnTrue() throws Exception {
|
||||||
|
testReauthnWithForceAuthn(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testReauthnWithForceAuthn(Boolean reloginRequired) throws Exception {
|
||||||
|
// Ensure that the first authentication passes
|
||||||
|
SamlClient samlClient = new SamlClientBuilder()
|
||||||
|
// First authn
|
||||||
|
.authnRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST, SAML_ASSERTION_CONSUMER_URL_SALES_POST, Binding.POST)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
.login().user(bburkeUser).build()
|
||||||
|
|
||||||
|
.execute(hr -> {
|
||||||
|
try {
|
||||||
|
SAMLDocumentHolder doc = Binding.POST.extractResponse(hr);
|
||||||
|
assertThat(doc.getSamlObject(), Matchers.isSamlStatusResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
|
||||||
|
} catch (IOException ex) {
|
||||||
|
Logger.getLogger(BasicSamlTest.class.getName()).log(Level.SEVERE, null, ex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
List<Step> secondAuthn = new SamlClientBuilder()
|
||||||
|
// Second authn with forceAuth not set (SSO)
|
||||||
|
.authnRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST2, SAML_ASSERTION_CONSUMER_URL_SALES_POST2, Binding.POST)
|
||||||
|
.transformObject(so -> {
|
||||||
|
so.setForceAuthn(reloginRequired);
|
||||||
|
return so;
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
|
||||||
|
.assertResponse(Matchers.bodyHC(containsString(
|
||||||
|
Objects.equals(reloginRequired, Boolean.TRUE)
|
||||||
|
? "Log in"
|
||||||
|
: GeneralConstants.SAML_RESPONSE_KEY
|
||||||
|
)))
|
||||||
|
|
||||||
|
.getSteps();
|
||||||
|
|
||||||
|
samlClient.execute(secondAuthn);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue