KEYCLOAK-10734 Let the check-sso feature do the check in hidden iframe

This commit is contained in:
Niko Köbler 2019-06-27 14:06:40 +02:00 committed by Bruno Oliveira da Silva
parent b3004482fb
commit 49e9cd759b
6 changed files with 103 additions and 4 deletions

View file

@ -106,6 +106,13 @@ declare namespace Keycloak {
*/
redirectUri?: string;
/**
* Specifies an uri to redirect to after silent check-sso.
* Silent check-sso will only happen, when this redirect uri is given and
* the specified uri is available whithin the application.
*/
silentCheckSsoRedirectUri?: string;
/**
* Set the OpenID Connect flow.
* @default standard

View file

@ -116,6 +116,10 @@
kc.redirectUri = initOptions.redirectUri;
}
if (initOptions.silentCheckSsoRedirectUri) {
kc.silentCheckSsoRedirectUri = initOptions.silentCheckSsoRedirectUri;
}
if (initOptions.pkceMethod) {
if (initOptions.pkceMethod !== "S256") {
throw 'Invalid value for pkceMethod';
@ -157,6 +161,29 @@
});
}
var checkSsoSilently = function() {
var ifrm = document.createElement("iframe");
var src = kc.createLoginUrl({prompt: 'none', redirectUri: kc.silentCheckSsoRedirectUri});
ifrm.setAttribute("src", src);
ifrm.setAttribute("title", "keycloak-silent-check-sso");
ifrm.style.display = "none";
document.body.appendChild(ifrm);
var messageCallback = function(event) {
if (event.origin !== window.location.origin || ifrm.contentWindow !== event.source) {
return;
}
var oauth = parseCallback(event.data);
processCallback(oauth, initPromise);
document.body.removeChild(ifrm);
window.removeEventListener("message", messageCallback);
};
window.addEventListener("message", messageCallback);
};
var options = {};
switch (initOptions.onLoad) {
case 'check-sso':
@ -164,7 +191,7 @@
setupCheckLoginIframe().success(function() {
checkLoginIframe().success(function (unchanged) {
if (!unchanged) {
doLogin(false);
kc.silentCheckSsoRedirectUri ? checkSsoSilently() : doLogin(false);
} else {
initPromise.setSuccess();
}
@ -173,7 +200,7 @@
});
});
} else {
doLogin(false);
kc.silentCheckSsoRedirectUri ? checkSsoSilently() : doLogin(false);
}
break;
case 'login-required':

View file

@ -30,6 +30,13 @@ public class TestJavascriptResource {
return resourceToString("/javascript/index.html");
}
@GET
@Path("/silent-check-sso.html")
@Produces(MediaType.TEXT_HTML)
public String getJavascriptTestingEnvironmentSilentCheckSso() throws IOException {
return resourceToString("/javascript/silent-check-sso.html");
}
@GET
@Path("/keycloak.json")
@Produces(MediaType.APPLICATION_JSON)

View file

@ -0,0 +1 @@
<html><body><script>parent.postMessage(location.href, location.origin)</script></body></html>

View file

@ -78,6 +78,10 @@ public class JSObjectBuilder {
return this;
}
private boolean skipQuotes(Object o) {
return (o instanceof Integer || o instanceof Boolean);
}
public String build() {
StringBuilder argument = new StringBuilder("{");
String comma = "";
@ -86,11 +90,11 @@ public class JSObjectBuilder {
.append(option.getKey())
.append(" : ");
if (!(option.getValue() instanceof Integer)) argument.append("\"");
if (!skipQuotes(option.getValue())) argument.append("\"");
argument.append(option.getValue());
if (!(option.getValue() instanceof Integer)) argument.append("\"");
if (!skipQuotes(option.getValue())) argument.append("\"");
comma = ",";
}

View file

@ -47,6 +47,7 @@ import static org.hamcrest.collection.IsMapContaining.hasEntry;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlDoesntStartWith;
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
@ -153,6 +154,58 @@ public class JavascriptAdapterTest extends AbstractJavascriptTest {
.init(pkceS256, this::assertInitNotAuth);
}
@Test
public void testSilentCheckSso() {
JSObjectBuilder checkSSO = defaultArguments().checkSSOOnLoad();
testExecutor.init(checkSSO, this::assertInitNotAuth)
.login(this::assertOnLoginPage)
.loginForm(testUser, this::assertOnTestAppUrl)
.init(checkSSO, this::assertSuccessfullyLoggedIn)
.refresh()
.init(checkSSO
.add("silentCheckSsoRedirectUri", authServerContextRootPage + JAVASCRIPT_URL + "/silent-check-sso.html")
, this::assertSuccessfullyLoggedIn);
}
@Test
public void testSilentCheckSsoLoginWithLoginIframeDisabled() {
JSObjectBuilder checkSSO = defaultArguments().checkSSOOnLoad();
testExecutor.init(checkSSO, this::assertInitNotAuth)
.login(this::assertOnLoginPage)
.loginForm(testUser, this::assertOnTestAppUrl)
.init(checkSSO, this::assertSuccessfullyLoggedIn)
.refresh()
.init(checkSSO
.add("checkLoginIframe", false)
.add("silentCheckSsoRedirectUri", authServerContextRootPage + JAVASCRIPT_URL + "/silent-check-sso.html")
, this::assertSuccessfullyLoggedIn);
}
@Test
public void testSilentCheckSsoWithoutRedirectUri() {
JSObjectBuilder checkSSO = defaultArguments().checkSSOOnLoad();
try {
testExecutor.init(checkSSO, this::assertInitNotAuth)
.login(this::assertOnLoginPage)
.loginForm(testUser, this::assertOnTestAppUrl)
.init(checkSSO, this::assertSuccessfullyLoggedIn)
.refresh()
.init(checkSSO);
fail();
} catch (WebDriverException e) {
// should happen
}
}
@Test
public void testSilentCheckSsoNotAuthenticated() {
JSObjectBuilder checkSSO = defaultArguments().checkSSOOnLoad();
testExecutor.init(checkSSO
.add("checkLoginIframe", false)
.add("silentCheckSsoRedirectUri", authServerContextRootPage + JAVASCRIPT_URL + "/silent-check-sso.html")
, this::assertInitNotAuth);
}
@Test
public void testRefreshToken() {
testExecutor.init(defaultArguments(), this::assertInitNotAuth)