Merge pull request #2924 from mposolda/master

KEYCLOAK-2028: Add preemptive access token refresh support to servlet adapters
This commit is contained in:
Marek Posolda 2016-06-09 21:51:15 +02:00
commit 443772d5ec
24 changed files with 569 additions and 66 deletions

View file

@ -456,6 +456,16 @@ public class AdapterDeploymentContext {
public void setTurnOffChangeSessionIdOnLogin(boolean turnOffChangeSessionIdOnLogin) {
delegate.setTurnOffChangeSessionIdOnLogin(turnOffChangeSessionIdOnLogin);
}
@Override
public int getTokenMinimumTimeToLive() {
return delegate.getTokenMinimumTimeToLive();
}
@Override
public void setTokenMinimumTimeToLive(final int tokenMinimumTimeToLive) {
delegate.setTokenMinimumTimeToLive(tokenMinimumTimeToLive);
}
}
protected KeycloakUriBuilder getBaseBuilder(HttpFacade facade, String base) {

View file

@ -77,6 +77,7 @@ public class KeycloakDeployment {
protected boolean turnOffChangeSessionIdOnLogin;
protected volatile int notBefore;
protected int tokenMinimumTimeToLive;
public KeycloakDeployment() {
}
@ -357,4 +358,12 @@ public class KeycloakDeployment {
public void setTurnOffChangeSessionIdOnLogin(boolean turnOffChangeSessionIdOnLogin) {
this.turnOffChangeSessionIdOnLogin = turnOffChangeSessionIdOnLogin;
}
public int getTokenMinimumTimeToLive() {
return tokenMinimumTimeToLive;
}
public void setTokenMinimumTimeToLive(final int tokenMinimumTimeToLive) {
this.tokenMinimumTimeToLive = tokenMinimumTimeToLive;
}
}

View file

@ -94,6 +94,7 @@ public class KeycloakDeploymentBuilder {
deployment.setAlwaysRefreshToken(adapterConfig.isAlwaysRefreshToken());
deployment.setRegisterNodeAtStartup(adapterConfig.isRegisterNodeAtStartup());
deployment.setRegisterNodePeriod(adapterConfig.getRegisterNodePeriod());
deployment.setTokenMinimumTimeToLive(adapterConfig.getTokenMinimumTimeToLive());
if (realmKeyPem == null && adapterConfig.isBearerOnly() && adapterConfig.getAuthServerUrl() == null) {
throw new IllegalArgumentException("For bearer auth, you must set the realm-public-key or auth-server-url");

View file

@ -21,6 +21,7 @@ import org.jboss.logging.Logger;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.RSATokenVerifier;
import org.keycloak.common.VerificationException;
import org.keycloak.common.util.Time;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.IDToken;
@ -77,6 +78,10 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
return token != null && this.token.isActive() && this.token.getIssuedAt() > deployment.getNotBefore();
}
public boolean isTokenTimeToLiveSufficient(AccessToken token) {
return token != null && (token.getExpiration() - this.deployment.getTokenMinimumTimeToLive()) > Time.currentTime();
}
public KeycloakDeployment getDeployment() {
return deployment;
}
@ -95,7 +100,7 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
if (log.isTraceEnabled()) {
log.trace("checking whether to refresh.");
}
if (isActive()) return true;
if (isActive() && isTokenTimeToLiveSufficient(this.token)) return true;
}
if (this.deployment == null || refreshToken == null) return false; // Might be serialized in HttpSession?
@ -130,6 +135,13 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
log.error("failed verification of token");
return false;
}
// If the TTL is greater-or-equal to the expire time on the refreshed token, have to abort or go into an infinite refresh loop
if (!isTokenTimeToLiveSufficient(token)) {
log.error("failed to refresh the token with a longer time-to-live than the minimum");
return false;
}
if (response.getNotBeforePolicy() > deployment.getNotBefore()) {
deployment.setNotBefore(response.getNotBeforePolicy());
}

View file

@ -61,6 +61,7 @@ public class KeycloakDeploymentBuilderTest {
assertEquals(1000, deployment.getRegisterNodePeriod());
assertEquals(TokenStore.COOKIE, deployment.getTokenStore());
assertEquals("email", deployment.getPrincipalAttribute());
assertEquals(10, deployment.getTokenMinimumTimeToLive());
}
@Test

View file

@ -28,5 +28,6 @@
"register-node-at-startup": true,
"register-node-period": 1000,
"token-store": "cookie",
"principal-attribute": "email"
"principal-attribute": "email",
"token-minimum-time-to-live": 10
}

View file

@ -36,7 +36,7 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder;
"client-keystore", "client-keystore-password", "client-key-password",
"always-refresh-token",
"register-node-at-startup", "register-node-period", "token-store", "principal-attribute",
"proxy-url"
"proxy-url", "turn-off-change-session-id-on-login", "token-minimum-time-to-live"
})
public class AdapterConfig extends BaseAdapterConfig {
@ -68,6 +68,8 @@ public class AdapterConfig extends BaseAdapterConfig {
protected String principalAttribute;
@JsonProperty("turn-off-change-session-id-on-login")
protected Boolean turnOffChangeSessionIdOnLogin;
@JsonProperty("token-minimum-time-to-live")
protected int tokenMinimumTimeToLive = 0;
/**
* The Proxy url to use for requests to the auth-server, configurable via the adapter config property {@code proxy-url}.
@ -194,4 +196,13 @@ public class AdapterConfig extends BaseAdapterConfig {
public void setProxyUrl(String proxyUrl) {
this.proxyUrl = proxyUrl;
}
public int getTokenMinimumTimeToLive() {
return tokenMinimumTimeToLive;
}
public void setTokenMinimumTimeToLive(final int tokenMinimumTimeToLive) {
this.tokenMinimumTimeToLive = tokenMinimumTimeToLive;
}
}

View file

@ -0,0 +1,71 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.adapter.filter;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import org.keycloak.common.util.Time;
/**
* Filter to handle "special" requests to perform actions on adapter side (for example setting time offset )
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class AdapterActionsFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletResponse servletResp = (HttpServletResponse) response;
//Accept timeOffset as argument to enforce timeouts
String timeOffsetParam = request.getParameter("timeOffset");
if (timeOffsetParam != null && !timeOffsetParam.isEmpty()) {
Time.setOffset(Integer.parseInt(timeOffsetParam));
}
// Continue request
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
private void writeResponse(HttpServletResponse response, String responseText) throws IOException {
PrintWriter writer = response.getWriter();
writer.println(responseText);
writer.flush();
}
}

View file

@ -0,0 +1,59 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.adapter.page;
import java.io.IOException;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.RefreshToken;
import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl;
import org.keycloak.util.JsonSerialization;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public abstract class AbstractShowTokensPage extends AbstractPageWithInjectedUrl {
@FindBy(id = "accessToken")
private WebElement accessToken;
@FindBy(id = "refreshToken")
private WebElement refreshToken;
public AccessToken getAccessToken() {
try {
return JsonSerialization.readValue(accessToken.getText(), AccessToken.class);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public RefreshToken getRefreshToken() {
try {
return JsonSerialization.readValue(refreshToken.getText(), RefreshToken.class);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}

View file

@ -19,7 +19,7 @@ import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
/**
* @author <a href="mailto:bruno@abstractj.org">Bruno Oliveira</a>.
*/
public class OfflineToken extends AbstractPageWithInjectedUrl {
public class OfflineToken extends AbstractShowTokensPage {
public static final String DEPLOYMENT_NAME = "offline-client";
@ -32,35 +32,6 @@ public class OfflineToken extends AbstractPageWithInjectedUrl {
return url;
}
@FindBy(id = "accessToken")
private WebElement accessToken;
@FindBy(id = "refreshToken")
private WebElement refreshToken;
@FindBy(id = "prettyToken")
private WebElement prettyToken;
public AccessToken getAccessToken() {
try {
return JsonSerialization.readValue(accessToken.getText(), AccessToken.class);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public RefreshToken getRefreshToken() {
try {
return JsonSerialization.readValue(refreshToken.getText(), RefreshToken.class);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public void logout() {
log.info("Logging out, navigating to: " + getUriBuilder().path("/logout").build().toASCIIString());
driver.navigate().to(getUriBuilder().path("/logout").build().toASCIIString());

View file

@ -0,0 +1,40 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.adapter.page;
import java.net.URL;
import org.jboss.arquillian.container.test.api.OperateOnDeployment;
import org.jboss.arquillian.test.api.ArquillianResource;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class TokenMinTTLPage extends AbstractShowTokensPage {
public static final String DEPLOYMENT_NAME = "token-min-ttl";
@ArquillianResource
@OperateOnDeployment(DEPLOYMENT_NAME)
private URL url;
@Override
public URL getInjectedUrl() {
return url;
}
}

View file

@ -0,0 +1,57 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.adapter.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.representations.RefreshToken;
import org.keycloak.util.JsonSerialization;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public abstract class AbstractShowTokensServlet extends HttpServlet {
private static final String LINK = "<a href=\"%s\" id=\"%s\">%s</a>";
protected String renderTokens(HttpServletRequest req) throws ServletException, IOException {
RefreshableKeycloakSecurityContext ctx = (RefreshableKeycloakSecurityContext) req.getAttribute(KeycloakSecurityContext.class.getName());
String accessTokenPretty = JsonSerialization.writeValueAsPrettyString(ctx.getToken());
RefreshToken refreshToken;
try {
refreshToken = new JWSInput(ctx.getRefreshToken()).readJsonContent(RefreshToken.class);
} catch (JWSInputException e) {
throw new IOException(e);
}
String refreshTokenPretty = JsonSerialization.writeValueAsPrettyString(refreshToken);
return new StringBuilder("<span id=\"accessToken\">" + accessTokenPretty + "</span>")
.append("<span id=\"refreshToken\">" + refreshTokenPretty + "</span>")
.toString();
}
}

View file

@ -3,14 +3,12 @@ package org.keycloak.testsuite.adapter.servlet;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.OAuth2Constants;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.common.util.Time;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.representations.RefreshToken;
import org.keycloak.util.JsonSerialization;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.UriBuilder;
@ -19,7 +17,7 @@ import java.io.IOException;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class OfflineTokenServlet extends HttpServlet {
public class OfflineTokenServlet extends AbstractShowTokensServlet {
private static final String OFFLINE_CLIENT_APP_URI = (System.getProperty("app.server.ssl.required", "false").equals("true")) ?
System.getProperty("app.server.ssl.base.url", "https://localhost:8643") + "/offline-client" :
@ -31,12 +29,6 @@ public class OfflineTokenServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//Accept timeOffset as argument to enforce timeouts
String timeOffsetParam = req.getParameter("timeOffset");
if (timeOffsetParam != null && !timeOffsetParam.isEmpty()) {
Time.setOffset(Integer.parseInt(timeOffsetParam));
}
if (req.getRequestURI().endsWith("logout")) {
UriBuilder redirectUriBuilder = UriBuilder.fromUri(OFFLINE_CLIENT_APP_URI);
@ -54,19 +46,11 @@ public class OfflineTokenServlet extends HttpServlet {
}
StringBuilder response = new StringBuilder("<html><head><title>Offline token servlet</title></head><body><pre>");
RefreshableKeycloakSecurityContext ctx = (RefreshableKeycloakSecurityContext) req.getAttribute(KeycloakSecurityContext.class.getName());
String accessTokenPretty = JsonSerialization.writeValueAsPrettyString(ctx.getToken());
RefreshToken refreshToken;
try {
refreshToken = new JWSInput(ctx.getRefreshToken()).readJsonContent(RefreshToken.class);
} catch (JWSInputException e) {
throw new IOException(e);
}
String refreshTokenPretty = JsonSerialization.writeValueAsPrettyString(refreshToken);
response = response.append("<span id=\"accessToken\">" + accessTokenPretty + "</span>")
.append("<span id=\"refreshToken\">" + refreshTokenPretty + "</span>")
.append("</pre></body></html>");
String tokens = renderTokens(req);
response = response.append(tokens);
response.append("</pre></body></html>");
resp.getWriter().println(response.toString());
}
}

View file

@ -0,0 +1,42 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.adapter.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class TokenMinTTLServlet extends AbstractShowTokensServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
StringBuilder response = new StringBuilder("<html><head><title>Token Min TTL Servlet</title></head><body><pre>");
String tokens = renderTokens(req);
response = response.append(tokens);
response.append("</pre></body></html>");
resp.getWriter().println(response.toString());
}
}

View file

@ -26,6 +26,7 @@ import org.jboss.logging.Logger.Level;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.keycloak.representations.adapters.config.BaseAdapterConfig;
import org.keycloak.testsuite.adapter.AdapterLibsMode;
import org.keycloak.testsuite.util.IOUtil;
@ -118,8 +119,8 @@ public class DeploymentArchiveProcessor implements ApplicationArchiveProcessor {
}
} else { // OIDC adapter config
try {
BaseAdapterConfig adapterConfig = loadJson(archive.get(adapterConfigPath)
.getAsset().openStream(), BaseAdapterConfig.class);
AdapterConfig adapterConfig = loadJson(archive.get(adapterConfigPath)
.getAsset().openStream(), AdapterConfig.class);
log.info(" setting " + (relative ? "" : "non-") + "relative auth-server-url");
if (relative) {

View file

@ -22,11 +22,15 @@ import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.util.WaitUtils;
import org.openqa.selenium.By;
import java.io.IOException;
import java.net.URL;
import java.util.List;
import javax.ws.rs.core.UriBuilder;
import static org.keycloak.testsuite.auth.page.AuthRealm.DEMO;
import static org.keycloak.testsuite.util.IOUtil.loadRealm;
@ -107,4 +111,14 @@ public abstract class AbstractServletsAdapterTest extends AbstractAdapterTest {
testRealmPage.setAuthRealm(DEMO);
}
protected void setAdapterAndServerTimeOffset(int timeOffset, String servletUri) {
setTimeOffset(timeOffset);
String timeOffsetUri = UriBuilder.fromUri(servletUri)
.queryParam("timeOffset", timeOffset)
.build().toString();
driver.navigate().to(timeOffsetUri);
WaitUtils.waitUntilElement(By.tagName("body")).is().visible();
}
}

View file

@ -27,9 +27,11 @@ import org.keycloak.OAuth2Constants;
import org.keycloak.common.Version;
import org.keycloak.constants.AdapterConstants;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.VersionRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.adapter.AbstractServletsAdapterTest;
import org.keycloak.testsuite.adapter.filter.AdapterActionsFilter;
import org.keycloak.testsuite.adapter.page.*;
import org.keycloak.util.BasicAuthHelper;
@ -70,6 +72,8 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
private ProductPortal productPortal;
@Page
private InputPortal inputPortal;
@Page
private TokenMinTTLPage tokenMinTTLPage;
@Deployment(name = CustomerPortal.DEPLOYMENT_NAME)
protected static WebArchive customerPortal() {
@ -106,6 +110,11 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
return servletDeployment(InputPortal.DEPLOYMENT_NAME, "keycloak.json", InputServlet.class);
}
@Deployment(name = TokenMinTTLPage.DEPLOYMENT_NAME)
protected static WebArchive tokenMinTTLPage() {
return servletDeployment(TokenMinTTLPage.DEPLOYMENT_NAME, AdapterActionsFilter.class, AbstractShowTokensServlet.class, TokenMinTTLServlet.class, ErrorServlet.class);
}
@Test
public void testCustomerPortalWithSubsystemSettings() {
customerPortalSubsystem.navigateTo();
@ -408,4 +417,36 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
}
// Tests "token-minimum-time-to-live" adapter configuration option
@Test
public void testTokenMinTTL() {
// Login
tokenMinTTLPage.navigateTo();
testRealmLoginPage.form().waitForUsernameInputPresent();
assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
testRealmLoginPage.form().login("bburke@redhat.com", "password");
assertCurrentUrlEquals(tokenMinTTLPage);
// Get time of token
AccessToken token = tokenMinTTLPage.getAccessToken();
int tokenIssued1 = token.getIssuedAt();
// Sets 5 minutes offset and assert access token will be still the same
setAdapterAndServerTimeOffset(300, tokenMinTTLPage.toString());
token = tokenMinTTLPage.getAccessToken();
int tokenIssued2 = token.getIssuedAt();
Assert.assertEquals(tokenIssued1, tokenIssued2);
Assert.assertFalse(token.isExpired());
// Sets 9 minutes offset and assert access token will be refreshed (accessTokenTimeout is 10 minutes, token-min-ttl is 2 minutes. Hence 8 minutes or more should be sufficient)
setAdapterAndServerTimeOffset(540, tokenMinTTLPage.toString());
token = tokenMinTTLPage.getAccessToken();
int tokenIssued3 = token.getIssuedAt();
Assert.assertTrue(tokenIssued3 > tokenIssued1);
// Revert times
setAdapterAndServerTimeOffset(0, tokenMinTTLPage.toString());
}
}

View file

@ -12,6 +12,7 @@ import org.keycloak.events.EventType;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.adapter.AbstractServletsAdapterTest;
import org.keycloak.testsuite.adapter.filter.AdapterActionsFilter;
import org.keycloak.testsuite.adapter.page.OfflineToken;
import org.keycloak.testsuite.pages.AccountApplicationsPage;
import org.keycloak.testsuite.pages.LoginPage;
@ -48,7 +49,7 @@ public abstract class AbstractOfflineServletsAdapterTest extends AbstractServlet
@Deployment(name = OfflineToken.DEPLOYMENT_NAME)
protected static WebArchive offlineClient() {
return servletDeployment(OfflineToken.DEPLOYMENT_NAME, OfflineTokenServlet.class, ErrorServlet.class);
return servletDeployment(OfflineToken.DEPLOYMENT_NAME, AdapterActionsFilter.class, AbstractShowTokensServlet.class, OfflineTokenServlet.class, ErrorServlet.class);
}
@Override
@ -186,14 +187,7 @@ public abstract class AbstractOfflineServletsAdapterTest extends AbstractServlet
}
private void setAdapterAndServerTimeOffset(int timeOffset) {
setTimeOffset(timeOffset);
String timeOffsetUri = UriBuilder.fromUri(offlineTokenPage.toString())
.queryParam("timeOffset", timeOffset)
.build().toString();
driver.navigate().to(timeOffsetUri);
waitUntilElement(By.tagName("body")).is().visible();
super.setAdapterAndServerTimeOffset(timeOffset, offlineTokenPage.toString());
}
}

View file

@ -2,7 +2,7 @@
"id": "demo",
"realm": "demo",
"enabled": true,
"accessTokenLifespan": 3000,
"accessTokenLifespan": 600,
"accessCodeLifespan": 10,
"accessCodeLifespanUserAction": 6000,
"sslRequired": "external",
@ -208,6 +208,16 @@
],
"secret": "password"
},
{
"clientId": "token-min-ttl",
"enabled": true,
"adminUrl": "/token-min-ttl",
"baseUrl": "/token-min-ttl",
"redirectUris": [
"/token-min-ttl/*"
],
"secret": "password"
},
{
"clientId": "third-party",
"enabled": true,

View file

@ -23,6 +23,11 @@
<module-name>offline-client</module-name>
<filter>
<filter-name>AdapterActionsFilter</filter-name>
<filter-class>org.keycloak.testsuite.adapter.filter.AdapterActionsFilter</filter-class>
</filter>
<servlet>
<servlet-name>Servlet</servlet-name>
<servlet-class>org.keycloak.testsuite.adapter.servlet.OfflineTokenServlet</servlet-class>
@ -33,6 +38,11 @@
<servlet-class>org.keycloak.testsuite.adapter.servlet.ErrorServlet</servlet-class>
</servlet>
<filter-mapping>
<filter-name>AdapterActionsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet-mapping>
<servlet-name>Servlet</servlet-name>
<url-pattern>/*</url-pattern>

View file

@ -0,0 +1,20 @@
<!--
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
~ and other contributors as indicated by the @author tags.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<Context path="/token-min-ttl">
<Valve className="org.keycloak.adapters.tomcat.KeycloakAuthenticatorValve"/>
</Context>

View file

@ -0,0 +1,46 @@
<?xml version="1.0"?>
<!--
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
~ and other contributors as indicated by the @author tags.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
<Configure class="org.eclipse.jetty.webapp.WebAppContext">
<Get name="securityHandler">
<Set name="authenticator">
<New class="org.keycloak.adapters.jetty.KeycloakJettyAuthenticator">
<!--
<Set name="adapterConfig">
<New class="org.keycloak.representations.adapters.config.AdapterConfig">
<Set name="realm">tomcat</Set>
<Set name="resource">customer-portal</Set>
<Set name="authServerUrl">http://localhost:8180/auth</Set>
<Set name="sslRequired">external</Set>
<Set name="credentials">
<Map>
<Entry>
<Item>secret</Item>
<Item>password</Item>
</Entry>
</Map>
</Set>
<Set name="realmKey">MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB</Set>
</New>
</Set>
-->
</New>
</Set>
</Get>
</Configure>

View file

@ -0,0 +1,11 @@
{
"realm": "demo",
"resource": "token-min-ttl",
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "http://localhost:8180/auth",
"ssl-required" : "external",
"credentials": {
"secret": "password"
},
"token-minimum-time-to-live": 120
}

View file

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
~ and other contributors as indicated by the @author tags.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<module-name>token-min-ttl</module-name>
<filter>
<filter-name>AdapterActionsFilter</filter-name>
<filter-class>org.keycloak.testsuite.adapter.filter.AdapterActionsFilter</filter-class>
</filter>
<servlet>
<servlet-name>Servlet</servlet-name>
<servlet-class>org.keycloak.testsuite.adapter.servlet.TokenMinTTLServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>Error Servlet</servlet-name>
<servlet-class>org.keycloak.testsuite.adapter.servlet.ErrorServlet</servlet-class>
</servlet>
<filter-mapping>
<filter-name>AdapterActionsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet-mapping>
<servlet-name>Servlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Error Servlet</servlet-name>
<url-pattern>/error.html</url-pattern>
</servlet-mapping>
<security-constraint>
<web-resource-collection>
<web-resource-name>Users</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>user</role-name>
</auth-constraint>
</security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name>Errors</web-resource-name>
<url-pattern>/error.html</url-pattern>
</web-resource-collection>
</security-constraint>
<login-config>
<auth-method>KEYCLOAK</auth-method>
<realm-name>test</realm-name>
<form-login-config>
<form-login-page>/error.html</form-login-page>
<form-error-page>/error.html</form-error-page>
</form-login-config>
</login-config>
<security-role>
<role-name>admin</role-name>
</security-role>
<security-role>
<role-name>user</role-name>
</security-role>
</web-app>