Merge pull request #697 from mposolda/master
Cookie & rememberMe fix. Fixing testsuite with mongo
This commit is contained in:
commit
364174fb7e
10 changed files with 105 additions and 14 deletions
|
@ -40,7 +40,11 @@
|
||||||
<#if realm.rememberMe>
|
<#if realm.rememberMe>
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<label>
|
<label>
|
||||||
<input id="rememberMe" name="rememberMe" type="checkbox" tabindex="3"> Remember Me
|
<#if login.rememberMe??>
|
||||||
|
<input id="rememberMe" name="rememberMe" type="checkbox" tabindex="3" checked> Remember Me
|
||||||
|
<#else>
|
||||||
|
<input id="rememberMe" name="rememberMe" type="checkbox" tabindex="3"> Remember Me
|
||||||
|
</#if>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</#if>
|
</#if>
|
||||||
|
|
|
@ -34,11 +34,14 @@ public class LoginBean {
|
||||||
|
|
||||||
private String passwordToken;
|
private String passwordToken;
|
||||||
|
|
||||||
|
private String rememberMe;
|
||||||
|
|
||||||
public LoginBean(MultivaluedMap<String, String> formData){
|
public LoginBean(MultivaluedMap<String, String> formData){
|
||||||
if (formData != null) {
|
if (formData != null) {
|
||||||
username = formData.getFirst("username");
|
username = formData.getFirst("username");
|
||||||
password = formData.getFirst("password");
|
password = formData.getFirst("password");
|
||||||
passwordToken = formData.getFirst("password-token");
|
passwordToken = formData.getFirst("password-token");
|
||||||
|
rememberMe = formData.getFirst("rememberMe");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,4 +56,8 @@ public class LoginBean {
|
||||||
public String getPasswordToken() {
|
public String getPasswordToken() {
|
||||||
return passwordToken;
|
return passwordToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getRememberMe() {
|
||||||
|
return rememberMe;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,6 +70,9 @@ public class MongoUserSessionProvider implements UserSessionProvider {
|
||||||
|
|
||||||
MongoUserSessionEntity userSessionEntity = entities.get(0);
|
MongoUserSessionEntity userSessionEntity = entities.get(0);
|
||||||
List<MongoClientSessionEntity> sessions = userSessionEntity.getClientSessions();
|
List<MongoClientSessionEntity> sessions = userSessionEntity.getClientSessions();
|
||||||
|
if (sessions == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
for (MongoClientSessionEntity s : sessions) {
|
for (MongoClientSessionEntity s : sessions) {
|
||||||
if (s.getId().equals(id)) {
|
if (s.getId().equals(id)) {
|
||||||
return new ClientSessionAdapter(session, this, realm, s, userSessionEntity, invocationContext);
|
return new ClientSessionAdapter(session, this, realm, s, userSessionEntity, invocationContext);
|
||||||
|
@ -240,6 +243,10 @@ public class MongoUserSessionProvider implements UserSessionProvider {
|
||||||
.get();
|
.get();
|
||||||
List<MongoUserSessionEntity> userSessionEntities = mongoStore.loadEntities(MongoUserSessionEntity.class, query, invocationContext);
|
List<MongoUserSessionEntity> userSessionEntities = mongoStore.loadEntities(MongoUserSessionEntity.class, query, invocationContext);
|
||||||
for (MongoUserSessionEntity e : userSessionEntities) {
|
for (MongoUserSessionEntity e : userSessionEntities) {
|
||||||
|
if (e.getClientSessions() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
List<MongoClientSessionEntity> remove = new LinkedList<MongoClientSessionEntity>();
|
List<MongoClientSessionEntity> remove = new LinkedList<MongoClientSessionEntity>();
|
||||||
for (MongoClientSessionEntity c : e.getClientSessions()) {
|
for (MongoClientSessionEntity c : e.getClientSessions()) {
|
||||||
if (c.getClientId().equals(client.getId())) {
|
if (c.getClientId().equals(client.getId())) {
|
||||||
|
|
|
@ -131,6 +131,10 @@ public class UserSessionAdapter extends AbstractMongoAdapter<MongoUserSessionEnt
|
||||||
@Override
|
@Override
|
||||||
public List<ClientSessionModel> getClientSessions() {
|
public List<ClientSessionModel> getClientSessions() {
|
||||||
List<ClientSessionModel> sessions = new LinkedList<ClientSessionModel>();
|
List<ClientSessionModel> sessions = new LinkedList<ClientSessionModel>();
|
||||||
|
if (entity.getClientSessions() == null) {
|
||||||
|
return sessions;
|
||||||
|
}
|
||||||
|
|
||||||
for (MongoClientSessionEntity e : entity.getClientSessions()) {
|
for (MongoClientSessionEntity e : entity.getClientSessions()) {
|
||||||
sessions.add(new ClientSessionAdapter(keycloakSession, provider, realm, e, entity, invocationContext));
|
sessions.add(new ClientSessionAdapter(keycloakSession, provider, realm, e, entity, invocationContext));
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ public class AppAuthManager extends AuthenticationManager {
|
||||||
if (authResult == null) return null;
|
if (authResult == null) return null;
|
||||||
// refresh the cookies!
|
// refresh the cookies!
|
||||||
createLoginCookie(realm, authResult.getUser(), authResult.getSession(), uriInfo, connection);
|
createLoginCookie(realm, authResult.getUser(), authResult.getSession(), uriInfo, connection);
|
||||||
if (authResult.getSession().isRememberMe()) createRememberMeCookie(realm, uriInfo, connection);
|
if (authResult.getSession().isRememberMe()) createRememberMeCookie(realm, authResult.getUser().getUsername(), uriInfo, connection);
|
||||||
return authResult;
|
return authResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -87,8 +87,8 @@ public class AuthenticationManager {
|
||||||
if (session != null) {
|
if (session != null) {
|
||||||
token.setSessionState(session.getId());
|
token.setSessionState(session.getId());
|
||||||
}
|
}
|
||||||
if (realm.getSsoSessionIdleTimeout() > 0) {
|
if (realm.getSsoSessionMaxLifespan() > 0) {
|
||||||
token.expiration(Time.currentTime() + realm.getSsoSessionIdleTimeout());
|
token.expiration(Time.currentTime() + realm.getSsoSessionMaxLifespan());
|
||||||
}
|
}
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
@ -100,7 +100,7 @@ public class AuthenticationManager {
|
||||||
boolean secureOnly = realm.getSslRequired().isRequired(connection);
|
boolean secureOnly = realm.getSslRequired().isRequired(connection);
|
||||||
int maxAge = NewCookie.DEFAULT_MAX_AGE;
|
int maxAge = NewCookie.DEFAULT_MAX_AGE;
|
||||||
if (session.isRememberMe()) {
|
if (session.isRememberMe()) {
|
||||||
maxAge = realm.getSsoSessionIdleTimeout();
|
maxAge = realm.getSsoSessionMaxLifespan();
|
||||||
}
|
}
|
||||||
logger.debugv("Create login cookie - name: {0}, path: {1}, max-age: {2}", KEYCLOAK_IDENTITY_COOKIE, cookiePath, maxAge);
|
logger.debugv("Create login cookie - name: {0}, path: {1}, max-age: {2}", KEYCLOAK_IDENTITY_COOKIE, cookiePath, maxAge);
|
||||||
CookieHelper.addCookie(KEYCLOAK_IDENTITY_COOKIE, encoded, cookiePath, null, null, maxAge, secureOnly, true);
|
CookieHelper.addCookie(KEYCLOAK_IDENTITY_COOKIE, encoded, cookiePath, null, null, maxAge, secureOnly, true);
|
||||||
|
@ -116,12 +116,12 @@ public class AuthenticationManager {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void createRememberMeCookie(RealmModel realm, UriInfo uriInfo, ClientConnection connection) {
|
public void createRememberMeCookie(RealmModel realm, String username, UriInfo uriInfo, ClientConnection connection) {
|
||||||
String path = getIdentityCookiePath(realm, uriInfo);
|
String path = getIdentityCookiePath(realm, uriInfo);
|
||||||
boolean secureOnly = realm.getSslRequired().isRequired(connection);
|
boolean secureOnly = realm.getSslRequired().isRequired(connection);
|
||||||
// remember me cookie should be persistent
|
// remember me cookie should be persistent (hardcoded to 1 month for now)
|
||||||
//NewCookie cookie = new NewCookie(KEYCLOAK_REMEMBER_ME, "true", path, null, null, realm.getCentralLoginLifespan(), secureOnly);// todo httponly , true);
|
//NewCookie cookie = new NewCookie(KEYCLOAK_REMEMBER_ME, "true", path, null, null, realm.getCentralLoginLifespan(), secureOnly);// todo httponly , true);
|
||||||
CookieHelper.addCookie(KEYCLOAK_REMEMBER_ME, "true", path, null, null, realm.getSsoSessionIdleTimeout(), secureOnly, true);
|
CookieHelper.addCookie(KEYCLOAK_REMEMBER_ME, username, path, null, null, 2592000, secureOnly, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String encodeToken(RealmModel realm, Object token) {
|
protected String encodeToken(RealmModel realm, Object token) {
|
||||||
|
@ -136,7 +136,6 @@ public class AuthenticationManager {
|
||||||
String path = getIdentityCookiePath(realm, uriInfo);
|
String path = getIdentityCookiePath(realm, uriInfo);
|
||||||
expireCookie(realm, KEYCLOAK_IDENTITY_COOKIE, path, true, connection);
|
expireCookie(realm, KEYCLOAK_IDENTITY_COOKIE, path, true, connection);
|
||||||
expireCookie(realm, KEYCLOAK_SESSION_COOKIE, path, false, connection);
|
expireCookie(realm, KEYCLOAK_SESSION_COOKIE, path, false, connection);
|
||||||
expireRememberMeCookie(realm, uriInfo, connection);
|
|
||||||
}
|
}
|
||||||
public static void expireRememberMeCookie(RealmModel realm, UriInfo uriInfo, ClientConnection connection) {
|
public static void expireRememberMeCookie(RealmModel realm, UriInfo uriInfo, ClientConnection connection) {
|
||||||
logger.debug("Expiring remember me cookie");
|
logger.debug("Expiring remember me cookie");
|
||||||
|
|
|
@ -59,6 +59,7 @@ import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
import javax.ws.rs.QueryParam;
|
import javax.ws.rs.QueryParam;
|
||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
|
import javax.ws.rs.core.Cookie;
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
|
@ -523,7 +524,7 @@ public class TokenService {
|
||||||
AuthenticationStatus status = authManager.authenticateForm(session, clientConnection, realm, formData);
|
AuthenticationStatus status = authManager.authenticateForm(session, clientConnection, realm, formData);
|
||||||
|
|
||||||
if (remember) {
|
if (remember) {
|
||||||
authManager.createRememberMeCookie(realm, uriInfo, clientConnection);
|
authManager.createRememberMeCookie(realm, username, uriInfo, clientConnection);
|
||||||
} else {
|
} else {
|
||||||
authManager.expireRememberMeCookie(realm, uriInfo, clientConnection);
|
authManager.expireRememberMeCookie(realm, uriInfo, clientConnection);
|
||||||
}
|
}
|
||||||
|
@ -962,9 +963,21 @@ public class TokenService {
|
||||||
|
|
||||||
LoginFormsProvider forms = Flows.forms(session, realm, client, uriInfo);
|
LoginFormsProvider forms = Flows.forms(session, realm, client, uriInfo);
|
||||||
|
|
||||||
if (loginHint != null) {
|
String rememberMeUsername = null;
|
||||||
|
Cookie rememberMeCookie = headers.getCookies().get(AuthenticationManager.KEYCLOAK_REMEMBER_ME);
|
||||||
|
if (rememberMeCookie != null && !"".equals(rememberMeCookie.getValue())) {
|
||||||
|
rememberMeUsername = rememberMeCookie.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loginHint != null || rememberMeUsername != null) {
|
||||||
MultivaluedMap<String, String> formData = new MultivaluedMapImpl<String, String>();
|
MultivaluedMap<String, String> formData = new MultivaluedMapImpl<String, String>();
|
||||||
formData.add(AuthenticationManager.FORM_USERNAME, loginHint);
|
|
||||||
|
if (loginHint != null) {
|
||||||
|
formData.add(AuthenticationManager.FORM_USERNAME, loginHint);
|
||||||
|
} else {
|
||||||
|
formData.add(AuthenticationManager.FORM_USERNAME, rememberMeUsername);
|
||||||
|
formData.add("rememberMe", "on");
|
||||||
|
}
|
||||||
|
|
||||||
forms.setFormData(formData);
|
forms.setFormData(formData);
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,7 +92,6 @@ public class OAuthFlows {
|
||||||
if (state != null)
|
if (state != null)
|
||||||
redirectUri.queryParam(OAuth2Constants.STATE, state);
|
redirectUri.queryParam(OAuth2Constants.STATE, state);
|
||||||
Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
|
Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
|
||||||
Cookie remember = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_REMEMBER_ME);
|
|
||||||
|
|
||||||
Cookie sessionCookie = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_SESSION_COOKIE);
|
Cookie sessionCookie = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_SESSION_COOKIE);
|
||||||
if (sessionCookie != null) {
|
if (sessionCookie != null) {
|
||||||
|
@ -112,7 +111,7 @@ public class OAuthFlows {
|
||||||
|
|
||||||
// refresh the cookies!
|
// refresh the cookies!
|
||||||
authManager.createLoginCookie(realm, accessCode.getUser(), userSession, uriInfo, clientConnection);
|
authManager.createLoginCookie(realm, accessCode.getUser(), userSession, uriInfo, clientConnection);
|
||||||
if (userSession.isRememberMe()) authManager.createRememberMeCookie(realm, uriInfo, clientConnection);
|
if (userSession.isRememberMe()) authManager.createRememberMeCookie(realm, accessCode.getUser().getUsername(), uriInfo, clientConnection);
|
||||||
return location.build();
|
return location.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,9 @@ import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
|
import org.keycloak.events.Event;
|
||||||
import org.keycloak.models.BrowserSecurityHeaders;
|
import org.keycloak.models.BrowserSecurityHeaders;
|
||||||
|
import org.keycloak.models.PasswordPolicy;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
@ -165,6 +167,49 @@ public class LoginTest {
|
||||||
events.expectLogin().user(userId).detail(Details.USERNAME, "login@test.com").assertEvent();
|
events.expectLogin().user(userId).detail(Details.USERNAME, "login@test.com").assertEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void loginWithRememberMe() {
|
||||||
|
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
|
||||||
|
@Override
|
||||||
|
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||||
|
appRealm.setRememberMe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
loginPage.open();
|
||||||
|
Assert.assertFalse(loginPage.isRememberMeChecked());
|
||||||
|
loginPage.setRememberMe(true);
|
||||||
|
Assert.assertTrue(loginPage.isRememberMeChecked());
|
||||||
|
loginPage.login("login-test", "password");
|
||||||
|
|
||||||
|
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||||
|
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
|
||||||
|
Event loginEvent = events.expectLogin().user(userId)
|
||||||
|
.detail(Details.USERNAME, "login-test")
|
||||||
|
.detail(Details.REMEMBER_ME, "true")
|
||||||
|
.assertEvent();
|
||||||
|
String sessionId = loginEvent.getSessionId();
|
||||||
|
|
||||||
|
// Expire session
|
||||||
|
keycloakRule.removeUserSession(sessionId);
|
||||||
|
|
||||||
|
// Assert rememberMe checked and username/email prefilled
|
||||||
|
loginPage.open();
|
||||||
|
Assert.assertTrue(loginPage.isRememberMeChecked());
|
||||||
|
Assert.assertEquals("login-test", loginPage.getUsername());
|
||||||
|
|
||||||
|
loginPage.setRememberMe(false);
|
||||||
|
} finally {
|
||||||
|
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
|
||||||
|
@Override
|
||||||
|
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||||
|
appRealm.setRememberMe(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void loginCancel() {
|
public void loginCancel() {
|
||||||
loginPage.open();
|
loginPage.open();
|
||||||
|
|
|
@ -44,6 +44,9 @@ public class LoginPage extends AbstractPage {
|
||||||
@FindBy(id = "totp")
|
@FindBy(id = "totp")
|
||||||
private WebElement totp;
|
private WebElement totp;
|
||||||
|
|
||||||
|
@FindBy(id = "rememberMe")
|
||||||
|
private WebElement rememberMe;
|
||||||
|
|
||||||
@FindBy(name = "login")
|
@FindBy(name = "login")
|
||||||
private WebElement submitButton;
|
private WebElement submitButton;
|
||||||
|
|
||||||
|
@ -119,6 +122,16 @@ public class LoginPage extends AbstractPage {
|
||||||
recoverUsernameLink.click();
|
recoverUsernameLink.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setRememberMe(boolean enable) {
|
||||||
|
boolean current = rememberMe.isSelected();
|
||||||
|
if (current != enable) {
|
||||||
|
rememberMe.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRememberMeChecked() {
|
||||||
|
return rememberMe.isSelected();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void open() {
|
public void open() {
|
||||||
|
|
Loading…
Reference in a new issue