Callers need to indicate if cookies need to be set at the end of the transaction
Closes #17141
This commit is contained in:
parent
ac04ef634c
commit
54048f1e6c
3 changed files with 26 additions and 1 deletions
|
@ -49,6 +49,7 @@ import org.keycloak.services.clientpolicy.context.PreAuthorizationRequestContext
|
||||||
import org.keycloak.services.messages.Messages;
|
import org.keycloak.services.messages.Messages;
|
||||||
import org.keycloak.services.resources.LoginActionsService;
|
import org.keycloak.services.resources.LoginActionsService;
|
||||||
import org.keycloak.services.util.CacheControlUtil;
|
import org.keycloak.services.util.CacheControlUtil;
|
||||||
|
import org.keycloak.services.util.CookieHelper;
|
||||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||||
import org.keycloak.util.TokenUtil;
|
import org.keycloak.util.TokenUtil;
|
||||||
|
|
||||||
|
@ -132,6 +133,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
|
||||||
return KeycloakModelUtils.runJobInRetriableTransaction(session.getKeycloakSessionFactory(), new ResponseSessionTask(session) {
|
return KeycloakModelUtils.runJobInRetriableTransaction(session.getKeycloakSessionFactory(), new ResponseSessionTask(session) {
|
||||||
@Override
|
@Override
|
||||||
public Response runInternal(KeycloakSession session) {
|
public Response runInternal(KeycloakSession session) {
|
||||||
|
CookieHelper.addCookiesAtEndOfTransaction(session);
|
||||||
// create another instance of the endpoint to isolate each run.
|
// create another instance of the endpoint to isolate each run.
|
||||||
AuthorizationEndpoint other = new AuthorizationEndpoint(session,
|
AuthorizationEndpoint other = new AuthorizationEndpoint(session,
|
||||||
new EventBuilder(session.getContext().getRealm(), session, clientConnection), action);
|
new EventBuilder(session.getContext().getRealm(), session, clientConnection), action);
|
||||||
|
|
|
@ -81,6 +81,7 @@ import org.keycloak.services.messages.Messages;
|
||||||
import org.keycloak.services.util.AuthenticationFlowURLHelper;
|
import org.keycloak.services.util.AuthenticationFlowURLHelper;
|
||||||
import org.keycloak.services.util.BrowserHistoryHelper;
|
import org.keycloak.services.util.BrowserHistoryHelper;
|
||||||
import org.keycloak.services.util.CacheControlUtil;
|
import org.keycloak.services.util.CacheControlUtil;
|
||||||
|
import org.keycloak.services.util.CookieHelper;
|
||||||
import org.keycloak.services.util.LocaleUtil;
|
import org.keycloak.services.util.LocaleUtil;
|
||||||
import org.keycloak.sessions.AuthenticationSessionCompoundId;
|
import org.keycloak.sessions.AuthenticationSessionCompoundId;
|
||||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||||
|
@ -260,6 +261,7 @@ public class LoginActionsService {
|
||||||
@Override
|
@Override
|
||||||
public Response runInternal(KeycloakSession session) {
|
public Response runInternal(KeycloakSession session) {
|
||||||
// create another instance of the endpoint to isolate each run.
|
// create another instance of the endpoint to isolate each run.
|
||||||
|
CookieHelper.addCookiesAtEndOfTransaction(session);
|
||||||
LoginActionsService other = new LoginActionsService(session, new EventBuilder(session.getContext().getRealm(), session, clientConnection));
|
LoginActionsService other = new LoginActionsService(session, new EventBuilder(session.getContext().getRealm(), session, clientConnection));
|
||||||
// process the request in the created instance.
|
// process the request in the created instance.
|
||||||
return other.authenticateInternal(authSessionId, code, execution, clientId, tabId);
|
return other.authenticateInternal(authSessionId, code, execution, clientId, tabId);
|
||||||
|
|
|
@ -29,6 +29,7 @@ import javax.ws.rs.core.HttpHeaders;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.keycloak.common.util.ServerCookie.SameSiteAttributeValue;
|
import static org.keycloak.common.util.ServerCookie.SameSiteAttributeValue;
|
||||||
|
@ -43,6 +44,7 @@ public class CookieHelper {
|
||||||
public static final String LEGACY_COOKIE = "_LEGACY";
|
public static final String LEGACY_COOKIE = "_LEGACY";
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(CookieHelper.class);
|
private static final Logger logger = Logger.getLogger(CookieHelper.class);
|
||||||
|
private static final String ADD_COOKIES_AT_END_OF_TRANSACTION = CookieHelper.class.getName() + "_ADD_COOKIES_AT_END_OF_TRANSACTION";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a response cookie. This solely exists because JAX-RS 1.1 does not support setting HttpOnly cookies
|
* Set a response cookie. This solely exists because JAX-RS 1.1 does not support setting HttpOnly cookies
|
||||||
|
@ -70,7 +72,11 @@ public class CookieHelper {
|
||||||
StringBuffer cookieBuf = new StringBuffer();
|
StringBuffer cookieBuf = new StringBuffer();
|
||||||
ServerCookie.appendCookieValue(cookieBuf, 1, name, value, path, domain, comment, maxAge, secure_sameSite, httpOnly, sameSite);
|
ServerCookie.appendCookieValue(cookieBuf, 1, name, value, path, domain, comment, maxAge, secure_sameSite, httpOnly, sameSite);
|
||||||
String cookie = cookieBuf.toString();
|
String cookie = cookieBuf.toString();
|
||||||
session.getTransactionManager().enlistAfterCompletion(new CookieTransaction(response, cookie));
|
if (shouldAddCookiesAtEndOfTransaction(session)) {
|
||||||
|
session.getTransactionManager().enlistAfterCompletion(new CookieTransaction(response, cookie));
|
||||||
|
} else {
|
||||||
|
response.addHeader(HttpHeaders.SET_COOKIE, cookie);
|
||||||
|
}
|
||||||
|
|
||||||
// a workaround for browser in older Apple OSs – browsers ignore cookies with SameSite=None
|
// a workaround for browser in older Apple OSs – browsers ignore cookies with SameSite=None
|
||||||
if (sameSiteParam == SameSiteAttributeValue.NONE) {
|
if (sameSiteParam == SameSiteAttributeValue.NONE) {
|
||||||
|
@ -78,6 +84,21 @@ public class CookieHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean shouldAddCookiesAtEndOfTransaction(KeycloakSession session) {
|
||||||
|
return Objects.equals(session.getAttribute(ADD_COOKIES_AT_END_OF_TRANSACTION), Boolean.TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adding cookies at the end of the transaction helps when retrying a transaction might add the
|
||||||
|
* cookie multiple times. In some scenarios it must not be added at the end of the transaction,
|
||||||
|
* as at that time the response has already been sent to the caller ("committed"), so the code
|
||||||
|
* needs to make a choice. As retrying transactions is the exception, adding cookies at the end
|
||||||
|
* of the transaction is also the exception and needs to be switched on where necessary.
|
||||||
|
*/
|
||||||
|
public static void addCookiesAtEndOfTransaction(KeycloakSession session) {
|
||||||
|
session.setAttribute(ADD_COOKIES_AT_END_OF_TRANSACTION, Boolean.TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a response cookie avoiding SameSite parameter
|
* Set a response cookie avoiding SameSite parameter
|
||||||
* @param name
|
* @param name
|
||||||
|
|
Loading…
Reference in a new issue