Merge pull request #237 from patriot1burke/master

remember me
This commit is contained in:
Bill Burke 2014-02-24 17:19:53 -05:00
commit 1acfe1502a
21 changed files with 248 additions and 132 deletions

View file

@ -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)

View file

@ -35,4 +35,7 @@ public class KeycloakAuthenticatedSession implements Serializable {
return metadata;
}
public void setMetadata(ResourceMetadata metadata) {
this.metadata = metadata;
}
}

View file

@ -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;

View file

@ -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>

View file

@ -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" >

View 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

View file

@ -49,5 +49,9 @@ public class RealmBean {
public boolean isResetPasswordAllowed() {
return realm.isResetPasswordAllowed();
}
public boolean isRememberMe() {
return realm.isRememberMe();
}
}

View file

@ -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>

View file

@ -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;
}
}

View file

@ -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();

View file

@ -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;

View file

@ -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);
}

View file

@ -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");
}
}

View file

@ -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();

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -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;
}
}

View file

@ -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());

View file

@ -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");

View file

@ -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;
}