commit
1acfe1502a
21 changed files with 248 additions and 132 deletions
|
@ -549,7 +549,7 @@ module.controller('RealmTokenDetailCtrl', function($scope, Realm, realm, $http,
|
|||
$scope.realm.accessTokenLifespan = TimeUnit.convert($scope.realm.accessTokenLifespan, from, to);
|
||||
});
|
||||
|
||||
$scope.realm.centralLoginLifespanUnit = TimeUnit.autoUnit(realm.accessTokenLifespan);
|
||||
$scope.realm.centralLoginLifespanUnit = TimeUnit.autoUnit(realm.centralLoginLifespan);
|
||||
$scope.realm.centralLoginLifespan = TimeUnit.toUnit(realm.centralLoginLifespan, $scope.realm.centralLoginLifespanUnit);
|
||||
$scope.$watch('realm.centralLoginLifespanUnit', function(to, from) {
|
||||
$scope.realm.centralLoginLifespan = TimeUnit.convert($scope.realm.centralLoginLifespan, from, to);
|
||||
|
@ -592,6 +592,7 @@ module.controller('RealmTokenDetailCtrl', function($scope, Realm, realm, $http,
|
|||
delete realmCopy["accessCodeLifespanUserActionUnit"];
|
||||
|
||||
realmCopy.accessTokenLifespan = TimeUnit.toSeconds($scope.realm.accessTokenLifespan, $scope.realm.accessTokenLifespanUnit)
|
||||
realmCopy.centralLoginLifespan = TimeUnit.toSeconds($scope.realm.centralLoginLifespan, $scope.realm.centralLoginLifespanUnit)
|
||||
realmCopy.refreshTokenLifespan = TimeUnit.toSeconds($scope.realm.refreshTokenLifespan, $scope.realm.refreshTokenLifespanUnit)
|
||||
realmCopy.accessCodeLifespan = TimeUnit.toSeconds($scope.realm.accessCodeLifespan, $scope.realm.accessCodeLifespanUnit)
|
||||
realmCopy.accessCodeLifespanUserAction = TimeUnit.toSeconds($scope.realm.accessCodeLifespanUserAction, $scope.realm.accessCodeLifespanUserActionUnit)
|
||||
|
|
|
@ -35,4 +35,7 @@ public class KeycloakAuthenticatedSession implements Serializable {
|
|||
return metadata;
|
||||
}
|
||||
|
||||
public void setMetadata(ResourceMetadata metadata) {
|
||||
this.metadata = metadata;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
package org.keycloak;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.security.Principal;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class KeycloakPrincipal implements Principal {
|
||||
public class KeycloakPrincipal implements Principal, Serializable {
|
||||
protected String name;
|
||||
protected String surrogate;
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
<module name="org.codehaus.jackson.jackson-mapper-asl"/>
|
||||
<module name="org.codehaus.jackson.jackson-xc"/>
|
||||
<module name="org.apache.httpcomponents" />
|
||||
<module name="org.jboss.logging"/>
|
||||
<module name="org.keycloak.keycloak-core"/>
|
||||
</dependencies>
|
||||
|
||||
|
|
|
@ -28,6 +28,13 @@
|
|||
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
|
||||
<#if realm.rememberMe>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input id="rememberMe" name="rememberMe" type="checkbox" tabindex="3"> Remember Me
|
||||
</label>
|
||||
</div>
|
||||
</#if>
|
||||
<div class="${properties.kcFormOptionsWrapperClass!}">
|
||||
<#if realm.registrationAllowed>
|
||||
<span>${rb.noAccount} <a href="${url.registrationUrl}">${rb.register}</a></span>
|
||||
|
@ -43,7 +50,7 @@
|
|||
<input class="btn btn-primary btn-lg" name="login" id="kc-login" type="submit" value="${rb.logIn}"/>
|
||||
<input class="btn btn-default btn-lg" name="cancel" id="kc-cancel" type="submit" value="${rb.cancel}"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<#elseif section = "info" >
|
||||
|
|
1
forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties
Normal file → Executable file
1
forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties
Normal file → Executable file
|
@ -16,6 +16,7 @@ firstName=First name
|
|||
lastName=Last name
|
||||
email=Email
|
||||
password=Password
|
||||
rememberMe=Remember me
|
||||
passwordConfirm=Confirm password
|
||||
passwordNew=New Password
|
||||
passwordNewConfirm=New Password confirmation
|
||||
|
|
|
@ -49,5 +49,9 @@ public class RealmBean {
|
|||
public boolean isResetPasswordAllowed() {
|
||||
return realm.isResetPasswordAllowed();
|
||||
}
|
||||
|
||||
public boolean isRememberMe() {
|
||||
return realm.isRememberMe();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -13,6 +13,12 @@
|
|||
<description/>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
<version>3.1.2.GA</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
package org.keycloak.adapters;
|
||||
|
||||
import org.keycloak.KeycloakAuthenticatedSession;
|
||||
import org.keycloak.RSATokenVerifier;
|
||||
import org.keycloak.VerificationException;
|
||||
import org.keycloak.adapters.config.RealmConfiguration;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class RefreshableKeycloakSession extends KeycloakAuthenticatedSession {
|
||||
|
||||
protected static Logger log = Logger.getLogger(RefreshableKeycloakSession.class);
|
||||
|
||||
protected transient RealmConfiguration realmConfiguration;
|
||||
protected String refreshToken;
|
||||
|
||||
public RefreshableKeycloakSession() {
|
||||
}
|
||||
|
||||
public RefreshableKeycloakSession(String tokenString, AccessToken token, ResourceMetadata metadata, RealmConfiguration realmConfiguration, String refreshToken) {
|
||||
super(tokenString, token, metadata);
|
||||
this.realmConfiguration = realmConfiguration;
|
||||
this.refreshToken = refreshToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccessToken getToken() {
|
||||
refreshExpiredToken();
|
||||
return super.getToken();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTokenString() {
|
||||
refreshExpiredToken();
|
||||
return super.getTokenString();
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return this.token.isActive();
|
||||
}
|
||||
|
||||
public void setRealmConfiguration(RealmConfiguration realmConfiguration) {
|
||||
this.realmConfiguration = realmConfiguration;
|
||||
}
|
||||
|
||||
public void refreshExpiredToken() {
|
||||
if (this.token.isActive()) return;
|
||||
if (this.realmConfiguration == null || refreshToken == null) return; // Might be serialized in HttpSession?
|
||||
|
||||
log.info("Doing refresh");
|
||||
AccessTokenResponse response = null;
|
||||
try {
|
||||
response = TokenGrantRequest.invokeRefresh(realmConfiguration, refreshToken);
|
||||
} catch (IOException e) {
|
||||
log.error("Refresh token failure", e);
|
||||
return;
|
||||
} catch (TokenGrantRequest.HttpFailure httpFailure) {
|
||||
log.error("Refresh token failure status: " + httpFailure.getStatus() + " " + httpFailure.getError());
|
||||
return;
|
||||
}
|
||||
log.info("received refresh response");
|
||||
String tokenString = response.getToken();
|
||||
AccessToken token = null;
|
||||
try {
|
||||
token = RSATokenVerifier.verifyToken(tokenString, realmConfiguration.getMetadata().getRealmKey(), realmConfiguration.getMetadata().getRealm());
|
||||
log.info("Token Verification succeeded!");
|
||||
} catch (VerificationException e) {
|
||||
log.error("failed verification of token");
|
||||
}
|
||||
this.token = token;
|
||||
this.refreshToken = response.getRefreshToken();
|
||||
this.tokenString = tokenString;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -16,6 +16,7 @@ import org.jboss.logging.Logger;
|
|||
import org.keycloak.KeycloakAuthenticatedSession;
|
||||
import org.keycloak.KeycloakPrincipal;
|
||||
import org.keycloak.adapters.AdapterConstants;
|
||||
import org.keycloak.adapters.RefreshableKeycloakSession;
|
||||
import org.keycloak.adapters.ResourceMetadata;
|
||||
import org.keycloak.adapters.as7.config.CatalinaAdapterConfigLoader;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
|
@ -92,6 +93,7 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
|
|||
remoteLogout(input, response);
|
||||
return;
|
||||
}
|
||||
checkKeycloakSession(request);
|
||||
super.invoke(request, response);
|
||||
} finally {
|
||||
}
|
||||
|
@ -184,13 +186,39 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that access token is still valid. Will attempt refresh of token if it is not.
|
||||
*
|
||||
* @param request
|
||||
*/
|
||||
protected void checkKeycloakSession(Request request) {
|
||||
if (request.getSessionInternal(false) == null || request.getSessionInternal().getPrincipal() == null) return;
|
||||
RefreshableKeycloakSession session = (RefreshableKeycloakSession)request.getSessionInternal().getNote(KeycloakAuthenticatedSession.class.getName());
|
||||
if (session == null) return;
|
||||
// just in case session got serialized
|
||||
session.setRealmConfiguration(realmConfiguration);
|
||||
session.setMetadata(resourceMetadata);
|
||||
if (session.isActive()) return;
|
||||
|
||||
// FYI: A refresh requires same scope, so same roles will be set. Otherwise, refresh will fail and token will
|
||||
// not be updated
|
||||
session.refreshExpiredToken();
|
||||
if (session.isActive()) return;
|
||||
|
||||
request.getSessionInternal().removeNote(KeycloakAuthenticatedSession.class.getName());
|
||||
request.setUserPrincipal(null);
|
||||
request.setAuthType(null);
|
||||
request.getSessionInternal().setPrincipal(null);
|
||||
request.getSessionInternal().setAuthType(null);
|
||||
}
|
||||
|
||||
protected boolean checkLoggedIn(Request request, HttpServletResponse response) {
|
||||
if (request.getSessionInternal() == null || request.getSessionInternal().getPrincipal() == null)
|
||||
if (request.getSessionInternal(false) == null || request.getSessionInternal().getPrincipal() == null)
|
||||
return false;
|
||||
log.debug("remote logged in already");
|
||||
GenericPrincipal principal = (GenericPrincipal) request.getSessionInternal().getPrincipal();
|
||||
request.setUserPrincipal(principal);
|
||||
request.setAuthType("OAUTH");
|
||||
request.setAuthType("KEYCLOAK");
|
||||
Session session = request.getSessionInternal();
|
||||
if (session != null) {
|
||||
KeycloakAuthenticatedSession skSession = (KeycloakAuthenticatedSession) session.getNote(KeycloakAuthenticatedSession.class.getName());
|
||||
|
@ -234,7 +262,7 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
|
|||
Session session = request.getSessionInternal(true);
|
||||
session.setPrincipal(principal);
|
||||
session.setAuthType("OAUTH");
|
||||
KeycloakAuthenticatedSession skSession = new KeycloakAuthenticatedSession(oauth.getTokenString(), token, realmConfiguration.getMetadata());
|
||||
KeycloakAuthenticatedSession skSession = new RefreshableKeycloakSession(oauth.getTokenString(), oauth.getToken(), resourceMetadata, realmConfiguration, oauth.getRefreshToken());
|
||||
session.setNote(KeycloakAuthenticatedSession.class.getName(), skSession);
|
||||
|
||||
String username = token.getSubject();
|
||||
|
|
|
@ -29,6 +29,7 @@ public class ServletOAuthLogin {
|
|||
protected int redirectPort;
|
||||
protected String tokenString;
|
||||
protected AccessToken token;
|
||||
protected String refreshToken;
|
||||
|
||||
public ServletOAuthLogin(RealmConfiguration realmInfo, HttpServletRequest request, HttpServletResponse response, int redirectPort) {
|
||||
this.request = request;
|
||||
|
@ -45,6 +46,10 @@ public class ServletOAuthLogin {
|
|||
return token;
|
||||
}
|
||||
|
||||
public String getRefreshToken() {
|
||||
return refreshToken;
|
||||
}
|
||||
|
||||
public RealmConfiguration getRealmInfo() {
|
||||
return realmInfo;
|
||||
}
|
||||
|
@ -249,6 +254,7 @@ public class ServletOAuthLogin {
|
|||
sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||
return false;
|
||||
}
|
||||
refreshToken = tokenResponse.getRefreshToken();
|
||||
// redirect to URL without oauth query parameters
|
||||
sendRedirect(redirectUri);
|
||||
return true;
|
||||
|
|
|
@ -8,6 +8,7 @@ import io.undertow.util.AttachmentKey;
|
|||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.KeycloakAuthenticatedSession;
|
||||
import org.keycloak.KeycloakPrincipal;
|
||||
import org.keycloak.adapters.RefreshableKeycloakSession;
|
||||
import org.keycloak.adapters.config.RealmConfiguration;
|
||||
import org.keycloak.adapters.ResourceMetadata;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
|
@ -93,7 +94,8 @@ public class KeycloakAuthenticationMechanism implements AuthenticationMechanism
|
|||
|
||||
protected void completeAuthentication(HttpServerExchange exchange, SecurityContext securityContext, OAuthAuthenticator oauth) {
|
||||
final KeycloakPrincipal principal = new KeycloakPrincipal(oauth.getToken().getSubject(), null);
|
||||
KeycloakUndertowAccount account = new KeycloakUndertowAccount(principal, oauth.getToken(), oauth.getTokenString(), oauth.getRefreshToken(), realmConfig, resourceMetadata, adapterConfig);
|
||||
RefreshableKeycloakSession session = new RefreshableKeycloakSession(oauth.getTokenString(), oauth.getToken(), resourceMetadata, realmConfig, oauth.getRefreshToken());
|
||||
KeycloakUndertowAccount account = new KeycloakUndertowAccount(principal, session, adapterConfig, resourceMetadata);
|
||||
securityContext.authenticationComplete(account, "KEYCLOAK", true);
|
||||
login(exchange, account);
|
||||
}
|
||||
|
@ -105,7 +107,8 @@ public class KeycloakAuthenticationMechanism implements AuthenticationMechanism
|
|||
|
||||
protected void completeAuthentication(SecurityContext securityContext, BearerTokenAuthenticator bearer) {
|
||||
final KeycloakPrincipal principal = new KeycloakPrincipal(bearer.getToken().getSubject(), bearer.getSurrogate());
|
||||
KeycloakUndertowAccount account = new KeycloakUndertowAccount(principal, bearer.getToken(), bearer.getTokenString(), null, realmConfig, resourceMetadata, adapterConfig);
|
||||
RefreshableKeycloakSession session = new RefreshableKeycloakSession(bearer.getTokenString(), bearer.getToken(), resourceMetadata, realmConfig, null);
|
||||
KeycloakUndertowAccount account = new KeycloakUndertowAccount(principal, session, adapterConfig, resourceMetadata);
|
||||
securityContext.authenticationComplete(account, "KEYCLOAK", false);
|
||||
}
|
||||
|
||||
|
|
|
@ -23,29 +23,29 @@ import java.io.IOException;
|
|||
*/
|
||||
class KeycloakIdentityManager implements IdentityManager {
|
||||
protected static Logger log = Logger.getLogger(KeycloakIdentityManager.class);
|
||||
protected AdapterConfig adapterConfig;
|
||||
protected RealmConfiguration realmConfiguration;
|
||||
|
||||
KeycloakIdentityManager(AdapterConfig adapterConfig, RealmConfiguration realmConfiguration) {
|
||||
this.adapterConfig = adapterConfig;
|
||||
this.realmConfiguration = realmConfiguration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Account verify(Account account) {
|
||||
log.info("Verifying account in IdentityManager");
|
||||
KeycloakUndertowAccount keycloakAccount = (KeycloakUndertowAccount)account;
|
||||
if (keycloakAccount.getAccessToken().isActive()) {
|
||||
log.info("account is still active. Time left: " + (keycloakAccount.getAccessToken().getExpiration() - (System.currentTimeMillis()/1000)) );
|
||||
return account;
|
||||
}
|
||||
keycloakAccount.refreshExpiredToken();
|
||||
if (!keycloakAccount.getAccessToken().isActive()) return null;
|
||||
if (!keycloakAccount.isActive(realmConfiguration, adapterConfig)) return null;
|
||||
return account;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Account verify(String id, Credential credential) {
|
||||
KeycloakServletExtension.log.warn("Shouldn't call verify!!!");
|
||||
throw new IllegalStateException("Not allowed");
|
||||
throw new IllegalStateException("Unsupported verify method");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Account verify(Credential credential) {
|
||||
KeycloakServletExtension.log.warn("Shouldn't call verify!!!");
|
||||
throw new IllegalStateException("Not allowed");
|
||||
throw new IllegalStateException("Unsupported verify method");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -95,7 +95,7 @@ public class KeycloakServletExtension implements ServletExtension {
|
|||
deploymentInfo.addInnerHandlerChainWrapper(ServletPropagateSessionHandler.WRAPPER); // propagates SkeletonKeySession
|
||||
deploymentInfo.addInnerHandlerChainWrapper(actions); // handles authenticated actions and cors.
|
||||
|
||||
deploymentInfo.setIdentityManager(new KeycloakIdentityManager());
|
||||
deploymentInfo.setIdentityManager(new KeycloakIdentityManager(keycloakConfig, realmConfiguration));
|
||||
|
||||
log.info("Setting jsession cookie path to: " + deploymentInfo.getContextPath());
|
||||
ServletSessionConfig cookieConfig = new ServletSessionConfig();
|
||||
|
|
|
@ -3,16 +3,13 @@ package org.keycloak.adapters.undertow;
|
|||
import io.undertow.security.idm.Account;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.KeycloakPrincipal;
|
||||
import org.keycloak.RSATokenVerifier;
|
||||
import org.keycloak.VerificationException;
|
||||
import org.keycloak.adapters.RefreshableKeycloakSession;
|
||||
import org.keycloak.adapters.ResourceMetadata;
|
||||
import org.keycloak.adapters.TokenGrantRequest;
|
||||
import org.keycloak.adapters.config.RealmConfiguration;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.security.Principal;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
@ -21,30 +18,19 @@ import java.util.Set;
|
|||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class KeycloakUndertowAccount implements Account {
|
||||
public class KeycloakUndertowAccount implements Account, Serializable {
|
||||
protected static Logger log = Logger.getLogger(KeycloakUndertowAccount.class);
|
||||
protected AccessToken accessToken;
|
||||
protected String encodedAccessToken;
|
||||
protected String refreshToken;
|
||||
protected RefreshableKeycloakSession session;
|
||||
protected KeycloakPrincipal principal;
|
||||
protected Set<String> accountRoles;
|
||||
protected RealmConfiguration realmConfiguration;
|
||||
protected ResourceMetadata resourceMetadata;
|
||||
protected AdapterConfig adapterConfig;
|
||||
|
||||
public KeycloakUndertowAccount(KeycloakPrincipal principal, AccessToken accessToken, String encodedAccessToken, String refreshToken,
|
||||
RealmConfiguration realmConfiguration, ResourceMetadata resourceMetadata, AdapterConfig adapterConfig) {
|
||||
public KeycloakUndertowAccount(KeycloakPrincipal principal, RefreshableKeycloakSession session, AdapterConfig config, ResourceMetadata metadata) {
|
||||
this.principal = principal;
|
||||
this.accessToken = accessToken;
|
||||
this.encodedAccessToken = encodedAccessToken;
|
||||
this.refreshToken = refreshToken;
|
||||
this.realmConfiguration = realmConfiguration;
|
||||
this.resourceMetadata = resourceMetadata;
|
||||
this.adapterConfig = adapterConfig;
|
||||
setRoles(accessToken);
|
||||
this.session = session;
|
||||
setRoles(session.getToken(), config, metadata);
|
||||
}
|
||||
|
||||
protected void setRoles(AccessToken accessToken) {
|
||||
protected void setRoles(AccessToken accessToken, AdapterConfig adapterConfig, ResourceMetadata resourceMetadata) {
|
||||
Set<String> roles = null;
|
||||
if (adapterConfig.isUseResourceRoleMappings()) {
|
||||
AccessToken.Access access = accessToken.getResourceAccess(resourceMetadata.getResourceName());
|
||||
|
@ -68,48 +54,30 @@ public class KeycloakUndertowAccount implements Account {
|
|||
}
|
||||
|
||||
public AccessToken getAccessToken() {
|
||||
return accessToken;
|
||||
return session.getToken();
|
||||
}
|
||||
|
||||
public String getEncodedAccessToken() {
|
||||
return encodedAccessToken;
|
||||
return session.getTokenString();
|
||||
}
|
||||
|
||||
public String getRefreshToken() {
|
||||
return refreshToken;
|
||||
public RefreshableKeycloakSession getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
public ResourceMetadata getResourceMetadata() {
|
||||
return resourceMetadata;
|
||||
public boolean isActive(RealmConfiguration realmConfiguration, AdapterConfig config) {
|
||||
// this object may have been serialized, so we need to reset realm config/metadata
|
||||
session.setRealmConfiguration(realmConfiguration);
|
||||
session.setMetadata(realmConfiguration.getMetadata());
|
||||
if (session.isActive()) return true;
|
||||
|
||||
session.refreshExpiredToken();
|
||||
if (!session.isActive()) return false;
|
||||
|
||||
setRoles(session.getToken(), config, realmConfiguration.getMetadata());
|
||||
return true;
|
||||
}
|
||||
|
||||
public void refreshExpiredToken() {
|
||||
if (accessToken.isActive()) return;
|
||||
|
||||
log.info("Doing refresh");
|
||||
AccessTokenResponse response = null;
|
||||
try {
|
||||
response = TokenGrantRequest.invokeRefresh(realmConfiguration, getRefreshToken());
|
||||
} catch (IOException e) {
|
||||
log.error("Refresh token failure", e);
|
||||
return;
|
||||
} catch (TokenGrantRequest.HttpFailure httpFailure) {
|
||||
log.error("Refresh token failure status: " + httpFailure.getStatus() + " " + httpFailure.getError());
|
||||
return;
|
||||
}
|
||||
log.info("received refresh response");
|
||||
String tokenString = response.getToken();
|
||||
AccessToken token = null;
|
||||
try {
|
||||
token = RSATokenVerifier.verifyToken(tokenString, realmConfiguration.getMetadata().getRealmKey(), realmConfiguration.getMetadata().getRealm());
|
||||
log.info("Token Verification succeeded!");
|
||||
} catch (VerificationException e) {
|
||||
log.error("failed verification of token");
|
||||
}
|
||||
this.accessToken = token;
|
||||
this.refreshToken = response.getRefreshToken();
|
||||
this.encodedAccessToken = tokenString;
|
||||
setRoles(this.accessToken);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,12 +40,10 @@ public class ServletPropagateSessionHandler implements HttpHandler {
|
|||
next.handleRequest(exchange);
|
||||
return;
|
||||
}
|
||||
UndertowKeycloakSession skSession = new UndertowKeycloakSession(account);
|
||||
|
||||
|
||||
final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
|
||||
HttpServletRequest req = (HttpServletRequest) servletRequestContext.getServletRequest();
|
||||
req.setAttribute(KeycloakAuthenticatedSession.class.getName(), skSession);
|
||||
req.setAttribute(KeycloakAuthenticatedSession.class.getName(), account.getSession());
|
||||
|
||||
HttpSession session = req.getSession(false);
|
||||
if (session == null) {
|
||||
|
@ -53,7 +51,7 @@ public class ServletPropagateSessionHandler implements HttpHandler {
|
|||
return;
|
||||
}
|
||||
log.debug("propagating to HTTP Session");
|
||||
session.setAttribute(KeycloakAuthenticatedSession.class.getName(), skSession);
|
||||
session.setAttribute(KeycloakAuthenticatedSession.class.getName(), account.getSession());
|
||||
next.handleRequest(exchange);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
package org.keycloak.adapters.undertow;
|
||||
|
||||
import org.keycloak.KeycloakAuthenticatedSession;
|
||||
import org.keycloak.adapters.ResourceMetadata;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class UndertowKeycloakSession extends KeycloakAuthenticatedSession {
|
||||
|
||||
private transient KeycloakUndertowAccount account;
|
||||
|
||||
public UndertowKeycloakSession(KeycloakUndertowAccount account) {
|
||||
super(account.getEncodedAccessToken(), account.getAccessToken(), account.getResourceMetadata());
|
||||
this.account = account;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccessToken getToken() {
|
||||
checkExpiration();
|
||||
return super.getToken();
|
||||
}
|
||||
|
||||
private void checkExpiration() {
|
||||
if (token.isExpired() && account != null) {
|
||||
account.refreshExpiredToken();
|
||||
this.token = account.getAccessToken();
|
||||
this.tokenString = account.getEncodedAccessToken();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTokenString() {
|
||||
checkExpiration();
|
||||
return super.getTokenString();
|
||||
}
|
||||
|
||||
}
|
|
@ -22,6 +22,7 @@ public class AccessCodeEntry {
|
|||
protected String code;
|
||||
protected String state;
|
||||
protected String redirectUri;
|
||||
protected boolean rememberMe;
|
||||
|
||||
protected long expiration;
|
||||
protected RealmModel realm;
|
||||
|
@ -119,4 +120,12 @@ public class AccessCodeEntry {
|
|||
public void setRedirectUri(String redirectUri) {
|
||||
this.redirectUri = redirectUri;
|
||||
}
|
||||
|
||||
public boolean isRememberMe() {
|
||||
return rememberMe;
|
||||
}
|
||||
|
||||
public void setRememberMe(boolean rememberMe) {
|
||||
this.rememberMe = rememberMe;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ public class AuthenticationManager {
|
|||
protected static Logger logger = Logger.getLogger(AuthenticationManager.class);
|
||||
public static final String FORM_USERNAME = "username";
|
||||
public static final String KEYCLOAK_IDENTITY_COOKIE = "KEYCLOAK_IDENTITY";
|
||||
public static final String KEYCLOAK_REMEMBER_ME = "KEYCLOAK_REMEMBER_ME";
|
||||
|
||||
public AccessToken createIdentityToken(RealmModel realm, UserModel user) {
|
||||
AccessToken token = new AccessToken();
|
||||
|
@ -52,26 +53,26 @@ public class AuthenticationManager {
|
|||
return token;
|
||||
}
|
||||
|
||||
public NewCookie createLoginCookie(RealmModel realm, UserModel user, UriInfo uriInfo) {
|
||||
public NewCookie createLoginCookie(RealmModel realm, UserModel user, UriInfo uriInfo, boolean rememberMe) {
|
||||
String cookieName = KEYCLOAK_IDENTITY_COOKIE;
|
||||
String cookiePath = getIdentityCookiePath(realm, uriInfo);
|
||||
return createLoginCookie(realm, user, null, cookieName, cookiePath);
|
||||
return createLoginCookie(realm, user, null, cookieName, cookiePath, rememberMe);
|
||||
}
|
||||
|
||||
public NewCookie createSaasIdentityCookie(RealmModel realm, UserModel user, UriInfo uriInfo) {
|
||||
String cookieName = AdminService.SAAS_IDENTITY_COOKIE;
|
||||
URI uri = AdminService.saasCookiePath(uriInfo).build();
|
||||
String cookiePath = uri.getRawPath();
|
||||
return createLoginCookie(realm, user, null, cookieName, cookiePath);
|
||||
return createLoginCookie(realm, user, null, cookieName, cookiePath, false);
|
||||
}
|
||||
|
||||
public NewCookie createAccountIdentityCookie(RealmModel realm, UserModel user, UserModel client, URI uri) {
|
||||
String cookieName = AccountService.ACCOUNT_IDENTITY_COOKIE;
|
||||
String cookiePath = uri.getRawPath();
|
||||
return createLoginCookie(realm, user, client, cookieName, cookiePath);
|
||||
return createLoginCookie(realm, user, client, cookieName, cookiePath, false);
|
||||
}
|
||||
|
||||
protected NewCookie createLoginCookie(RealmModel realm, UserModel user, UserModel client, String cookieName, String cookiePath) {
|
||||
protected NewCookie createLoginCookie(RealmModel realm, UserModel user, UserModel client, String cookieName, String cookiePath, boolean rememberMe) {
|
||||
AccessToken identityToken = createIdentityToken(realm, user);
|
||||
if (client != null) {
|
||||
identityToken.issuedFor(client.getLoginName());
|
||||
|
@ -80,15 +81,22 @@ public class AuthenticationManager {
|
|||
boolean secureOnly = !realm.isSslNotRequired();
|
||||
logger.debug("creatingLoginCookie - name: {0} path: {1}", cookieName, cookiePath);
|
||||
int maxAge = NewCookie.DEFAULT_MAX_AGE;
|
||||
/*
|
||||
if (realm.isRememberMe()) {
|
||||
if (rememberMe) {
|
||||
maxAge = realm.getCentralLoginLifespan();
|
||||
logger.info("createLoginCookie maxAge: " + maxAge);
|
||||
}
|
||||
*/
|
||||
NewCookie cookie = new NewCookie(cookieName, encoded, cookiePath, null, null, maxAge, secureOnly, true);
|
||||
return cookie;
|
||||
}
|
||||
|
||||
public NewCookie createRememberMeCookie(RealmModel realm, UriInfo uriInfo) {
|
||||
String path = getIdentityCookiePath(realm, uriInfo);
|
||||
boolean secureOnly = !realm.isSslNotRequired();
|
||||
// remember me cookie should be persistent
|
||||
NewCookie cookie = new NewCookie(KEYCLOAK_REMEMBER_ME, "true", path, null, null, realm.getCentralLoginLifespan(), secureOnly, true);
|
||||
return cookie;
|
||||
}
|
||||
|
||||
protected String encodeToken(RealmModel realm, Object token) {
|
||||
String encodedToken = new JWSBuilder()
|
||||
.jsonContent(token)
|
||||
|
@ -103,6 +111,12 @@ public class AuthenticationManager {
|
|||
String cookieName = KEYCLOAK_IDENTITY_COOKIE;
|
||||
expireCookie(cookieName, path);
|
||||
}
|
||||
public void expireRememberMeCookie(RealmModel realm, UriInfo uriInfo) {
|
||||
logger.debug("Expiring remember me cookie");
|
||||
String path = getIdentityCookiePath(realm, uriInfo);
|
||||
String cookieName = KEYCLOAK_REMEMBER_ME;
|
||||
expireCookie(cookieName, path);
|
||||
}
|
||||
|
||||
protected String getIdentityCookiePath(RealmModel realm, UriInfo uriInfo) {
|
||||
URI uri = RealmsResource.realmBaseUrl(uriInfo).build(realm.getName());
|
||||
|
|
|
@ -45,6 +45,7 @@ import javax.ws.rs.core.Context;
|
|||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.NewCookie;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.SecurityContext;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
|
@ -235,10 +236,20 @@ public class TokenService {
|
|||
|
||||
AuthenticationStatus status = authManager.authenticateForm(realm, user, formData);
|
||||
|
||||
String rememberMe = formData.getFirst("rememberMe");
|
||||
boolean remember = rememberMe != null && rememberMe.equalsIgnoreCase("on");
|
||||
logger.debug("*** Remember me: " + remember);
|
||||
if (remember) {
|
||||
NewCookie cookie = authManager.createRememberMeCookie(realm, uriInfo);
|
||||
response.addNewCookie(cookie);
|
||||
} else {
|
||||
authManager.expireRememberMeCookie(realm, uriInfo);
|
||||
}
|
||||
|
||||
switch (status) {
|
||||
case SUCCESS:
|
||||
case ACTIONS_REQUIRED:
|
||||
return oauth.processAccessCode(scopeParam, state, redirect, client, user);
|
||||
return oauth.processAccessCode(scopeParam, state, redirect, client, user, remember);
|
||||
case ACCOUNT_DISABLED:
|
||||
return Flows.forms(realm, request, uriInfo).setError(Messages.ACCOUNT_DISABLED).setFormData(formData).createLogin();
|
||||
case MISSING_TOTP:
|
||||
|
@ -544,6 +555,7 @@ public class TokenService {
|
|||
if (user != null) {
|
||||
logger.info("Logging out: {0}", user.getLoginName());
|
||||
authManager.expireIdentityCookie(realm, uriInfo);
|
||||
authManager.expireRememberMeCookie(realm, uriInfo);
|
||||
resourceAdminManager.singleLogOut(realm, user.getId());
|
||||
} else {
|
||||
logger.info("No user logged in for logout");
|
||||
|
|
|
@ -35,6 +35,7 @@ import org.keycloak.services.managers.AuthenticationManager;
|
|||
import org.keycloak.services.managers.TokenManager;
|
||||
import org.keycloak.services.resources.TokenService;
|
||||
|
||||
import javax.ws.rs.core.Cookie;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
|
@ -69,13 +70,20 @@ public class OAuthFlows {
|
|||
}
|
||||
|
||||
public Response redirectAccessCode(AccessCodeEntry accessCode, String state, String redirect) {
|
||||
return redirectAccessCode(accessCode, state, redirect, false);
|
||||
}
|
||||
|
||||
|
||||
public Response redirectAccessCode(AccessCodeEntry accessCode, String state, String redirect, boolean rememberMe) {
|
||||
String code = accessCode.getCode();
|
||||
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam("code", code);
|
||||
log.debug("redirectAccessCode: state: {0}", state);
|
||||
if (state != null)
|
||||
redirectUri.queryParam("state", state);
|
||||
Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
|
||||
location.cookie(authManager.createLoginCookie(realm, accessCode.getUser(), uriInfo));
|
||||
Cookie remember = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_REMEMBER_ME);
|
||||
rememberMe = rememberMe || remember != null;
|
||||
location.cookie(authManager.createLoginCookie(realm, accessCode.getUser(), uriInfo, rememberMe));
|
||||
return location.build();
|
||||
}
|
||||
|
||||
|
@ -89,6 +97,11 @@ public class OAuthFlows {
|
|||
}
|
||||
|
||||
public Response processAccessCode(String scopeParam, String state, String redirect, UserModel client, UserModel user) {
|
||||
return processAccessCode(scopeParam, state, redirect, client, user, false);
|
||||
}
|
||||
|
||||
|
||||
public Response processAccessCode(String scopeParam, String state, String redirect, UserModel client, UserModel user, boolean rememberMe) {
|
||||
isTotpConfigurationRequired(user);
|
||||
isEmailVerificationRequired(user);
|
||||
|
||||
|
@ -121,7 +134,7 @@ public class OAuthFlows {
|
|||
}
|
||||
|
||||
if (redirect != null) {
|
||||
return redirectAccessCode(accessCode, state, redirect);
|
||||
return redirectAccessCode(accessCode, state, redirect, rememberMe);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue