/* * 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.adapters; import org.jboss.logging.Logger; import org.keycloak.AuthorizationContext; import org.keycloak.KeycloakSecurityContext; import org.keycloak.adapters.rotation.AdapterRSATokenVerifier; 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; import java.io.IOException; /** * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @version $Revision: 1 $ */ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext { protected static Logger log = Logger.getLogger(RefreshableKeycloakSecurityContext.class); protected transient KeycloakDeployment deployment; protected transient AdapterTokenStore tokenStore; protected String refreshToken; public RefreshableKeycloakSecurityContext() { } public RefreshableKeycloakSecurityContext(KeycloakDeployment deployment, AdapterTokenStore tokenStore, String tokenString, AccessToken token, String idTokenString, IDToken idToken, String refreshToken) { super(tokenString, token, idTokenString, idToken); this.deployment = deployment; this.tokenStore = tokenStore; this.refreshToken = refreshToken; } @Override public AccessToken getToken() { refreshExpiredToken(true); return super.getToken(); } @Override public String getTokenString() { refreshExpiredToken(true); return super.getTokenString(); } public String getRefreshToken() { return refreshToken; } public void logout(KeycloakDeployment deployment) { try { ServerRequest.invokeLogout(deployment, refreshToken); } catch (Exception e) { log.error("failed to invoke remote logout", e); } } public boolean isActive() { return token != null && this.token.isActive() && deployment!=null && 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; } public void setCurrentRequestInfo(KeycloakDeployment deployment, AdapterTokenStore tokenStore) { this.deployment = deployment; this.tokenStore = tokenStore; } /** * @param checkActive if true, then we won't send refresh request if current accessToken is still active. * @return true if accessToken is active or was successfully refreshed */ public boolean refreshExpiredToken(boolean checkActive) { if (checkActive) { if (log.isTraceEnabled()) { log.trace("checking whether to refresh."); } if (isActive() && isTokenTimeToLiveSufficient(this.token)) return true; } if (this.deployment == null || refreshToken == null) return false; // Might be serialized in HttpSession? if (!this.getRealm().equals(this.deployment.getRealm())) { // this should not happen, but let's check it anyway return false; } if (log.isTraceEnabled()) { log.trace("Doing refresh"); } AccessTokenResponse response = null; try { response = ServerRequest.invokeRefresh(deployment, refreshToken); } catch (IOException e) { log.error("Refresh token failure", e); return false; } catch (ServerRequest.HttpFailure httpFailure) { log.error("Refresh token failure status: " + httpFailure.getStatus() + " " + httpFailure.getError()); return false; } if (log.isTraceEnabled()) { log.trace("received refresh response"); } String tokenString = response.getToken(); AccessToken token = null; try { token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment); log.debug("Token Verification succeeded!"); } catch (VerificationException e) { 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.updateNotBefore(response.getNotBeforePolicy()); } this.token = token; if (response.getRefreshToken() != null) { if (log.isTraceEnabled()) { log.trace("Setup new refresh token to the security context"); } this.refreshToken = response.getRefreshToken(); } this.tokenString = tokenString; tokenStore.refreshCallback(this); return true; } public void setAuthorizationContext(AuthorizationContext authorizationContext) { this.authorizationContext = authorizationContext; } }