Details
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/realm.js b/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/realm.js
index 9c5e974..760d501 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/realm.js
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/realm.js
@@ -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);
diff --git a/forms/common-themes/src/main/resources/theme/login/base/login.ftl b/forms/common-themes/src/main/resources/theme/login/base/login.ftl
index 4fd6f63..b609784 100755
--- a/forms/common-themes/src/main/resources/theme/login/base/login.ftl
+++ b/forms/common-themes/src/main/resources/theme/login/base/login.ftl
@@ -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" >
diff --git a/forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties b/forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties
old mode 100644
new mode 100755
index 79c8e7b..9aa907e
--- a/forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties
+++ b/forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties
@@ -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
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/RealmBean.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/RealmBean.java
index 8a71244..a6b52f0 100755
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/RealmBean.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/RealmBean.java
@@ -49,5 +49,9 @@ public class RealmBean {
public boolean isResetPasswordAllowed() {
return realm.isResetPasswordAllowed();
}
+
+ public boolean isRememberMe() {
+ return realm.isRememberMe();
+ }
}
diff --git a/services/src/main/java/org/keycloak/services/managers/AccessCodeEntry.java b/services/src/main/java/org/keycloak/services/managers/AccessCodeEntry.java
index e3268cf..a94cb1b 100755
--- a/services/src/main/java/org/keycloak/services/managers/AccessCodeEntry.java
+++ b/services/src/main/java/org/keycloak/services/managers/AccessCodeEntry.java
@@ -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;
+ }
}
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index 237b7dc..b82f585 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -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());
diff --git a/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java b/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java
index 626aab8..8524807 100755
--- a/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java
+++ b/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java
@@ -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;
}
diff --git a/services/src/main/java/org/keycloak/services/resources/TokenService.java b/services/src/main/java/org/keycloak/services/resources/TokenService.java
index 4983d37..24d8090 100755
--- a/services/src/main/java/org/keycloak/services/resources/TokenService.java
+++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java
@@ -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");