Remove duplicated set-cookie header from response when expiring cookies

Closes #17192
This commit is contained in:
Pedro Igor 2023-02-23 21:11:26 -03:00
parent b491469e50
commit fbf5541802
13 changed files with 225 additions and 91 deletions

View file

@ -207,7 +207,7 @@ public class ServletHttpFacade implements HttpFacade {
@Override @Override
public void setCookie(String name, String value, String path, String domain, int maxAge, boolean secure, boolean httpOnly) { public void setCookie(String name, String value, String path, String domain, int maxAge, boolean secure, boolean httpOnly) {
StringBuffer cookieBuf = new StringBuffer(); StringBuilder cookieBuf = new StringBuilder();
ServerCookie.appendCookieValue(cookieBuf, 1, name, value, path, domain, null, maxAge, secure, httpOnly, null); ServerCookie.appendCookieValue(cookieBuf, 1, name, value, path, domain, null, maxAge, secure, httpOnly, null);
String cookie = cookieBuf.toString(); String cookie = cookieBuf.toString();
response.addHeader("Set-Cookie", cookie); response.addHeader("Set-Cookie", cookie);

View file

@ -218,7 +218,7 @@ public class CatalinaHttpFacade implements HttpFacade {
@Override @Override
public void setCookie(String name, String value, String path, String domain, int maxAge, boolean secure, boolean httpOnly) { public void setCookie(String name, String value, String path, String domain, int maxAge, boolean secure, boolean httpOnly) {
StringBuffer cookieBuf = new StringBuffer(); StringBuilder cookieBuf = new StringBuilder();
ServerCookie.appendCookieValue(cookieBuf, 1, name, value, path, domain, null, maxAge, secure, httpOnly, null); ServerCookie.appendCookieValue(cookieBuf, 1, name, value, path, domain, null, maxAge, secure, httpOnly, null);
String cookie = cookieBuf.toString(); String cookie = cookieBuf.toString();
response.addHeader("Set-Cookie", cookie); response.addHeader("Set-Cookie", cookie);

View file

@ -178,7 +178,7 @@ public class ServerCookie implements Serializable {
// TODO RFC2965 fields also need to be passed // TODO RFC2965 fields also need to be passed
public static void appendCookieValue(StringBuffer headerBuf, public static void appendCookieValue(StringBuilder headerBuf,
int version, int version,
String name, String name,
String value, String value,

View file

@ -17,6 +17,7 @@
package org.keycloak.examples.authenticator; package org.keycloak.examples.authenticator;
import org.keycloak.http.HttpCookie;
import org.keycloak.http.HttpResponse; import org.keycloak.http.HttpResponse;
import org.keycloak.authentication.AuthenticationFlowContext; import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.authentication.AuthenticationFlowError; import org.keycloak.authentication.AuthenticationFlowError;
@ -24,7 +25,6 @@ import org.keycloak.authentication.Authenticator;
import org.keycloak.authentication.CredentialValidator; import org.keycloak.authentication.CredentialValidator;
import org.keycloak.authentication.RequiredActionFactory; import org.keycloak.authentication.RequiredActionFactory;
import org.keycloak.authentication.RequiredActionProvider; import org.keycloak.authentication.RequiredActionProvider;
import org.keycloak.common.util.ServerCookie;
import org.keycloak.credential.CredentialProvider; import org.keycloak.credential.CredentialProvider;
import org.keycloak.models.AuthenticatorConfigModel; import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
@ -33,7 +33,6 @@ import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import javax.ws.rs.core.Cookie; import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import java.net.URI; import java.net.URI;
@ -97,10 +96,7 @@ public class SecretQuestionAuthenticator implements Authenticator, CredentialVal
public void addCookie(AuthenticationFlowContext context, String name, String value, String path, String domain, String comment, int maxAge, boolean secure, boolean httpOnly) { public void addCookie(AuthenticationFlowContext context, String name, String value, String path, String domain, String comment, int maxAge, boolean secure, boolean httpOnly) {
HttpResponse response = context.getSession().getContext().getHttpResponse(); HttpResponse response = context.getSession().getContext().getHttpResponse();
StringBuffer cookieBuf = new StringBuffer(); response.setCookieIfAbsent(new HttpCookie(1, name, value, path, domain, comment, maxAge, secure, httpOnly, null));
ServerCookie.appendCookieValue(cookieBuf, 1, name, value, path, domain, comment, maxAge, secure, httpOnly, null);
String cookie = cookieBuf.toString();
response.addHeader(HttpHeaders.SET_COOKIE, cookie);
} }

View file

@ -0,0 +1,49 @@
/*
* Copyright 2023 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.http;
import org.keycloak.common.util.ServerCookie;
import org.keycloak.common.util.ServerCookie.SameSiteAttributeValue;
/**
* An extension of {@link javax.ws.rs.core.Cookie} in order to support additional
* fields and behavior.
*/
public final class HttpCookie extends javax.ws.rs.core.Cookie {
private final String comment;
private final int maxAge;
private final boolean secure;
private final boolean httpOnly;
private final SameSiteAttributeValue sameSite;
public HttpCookie(int version, String name, String value, String path, String domain, String comment, int maxAge, boolean secure, boolean httpOnly, SameSiteAttributeValue sameSite) {
super(name, value, path, domain, version);
this.comment = comment;
this.maxAge = maxAge;
this.secure = secure;
this.httpOnly = httpOnly;
this.sameSite = sameSite;
}
public String toHeaderValue() {
StringBuilder cookieBuf = new StringBuilder();
ServerCookie.appendCookieValue(cookieBuf, getVersion(), getName(), getValue(), getPath(), getDomain(), comment, maxAge, secure, httpOnly, sameSite);
return cookieBuf.toString();
}
}

View file

@ -16,6 +16,7 @@
*/ */
package org.keycloak.http; package org.keycloak.http;
/** /**
* <p>Represents an out coming HTTP response. * <p>Represents an out coming HTTP response.
* *
@ -45,4 +46,20 @@ public interface HttpResponse {
* @param value the header value * @param value the header value
*/ */
void setHeader(String name, String value); void setHeader(String name, String value);
/**
* Sets a new cookie only if not yet set.
*
* @param cookie the cookie
*/
void setCookieIfAbsent(HttpCookie cookie);
/**
* 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.
*/
void setWriteCookiesOnTransactionComplete();
} }

View file

@ -49,7 +49,6 @@ 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;
@ -133,7 +132,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); session.getContext().getHttpResponse().setWriteCookiesOnTransactionComplete();
// 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);

View file

@ -176,6 +176,6 @@ public class DefaultKeycloakContext implements KeycloakContext {
} }
protected HttpResponse createHttpResponse() { protected HttpResponse createHttpResponse() {
return new HttpResponseImpl(getContextObject(org.jboss.resteasy.spi.HttpResponse.class)); return new HttpResponseImpl(session, getContextObject(org.jboss.resteasy.spi.HttpResponse.class));
} }
} }

View file

@ -17,14 +17,26 @@
package org.keycloak.services; package org.keycloak.services;
import org.keycloak.http.HttpResponse; import java.util.HashSet;
import java.util.Set;
public class HttpResponseImpl implements HttpResponse { import javax.ws.rs.core.HttpHeaders;
import org.keycloak.http.HttpCookie;
import org.keycloak.http.HttpResponse;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakTransaction;
public class HttpResponseImpl implements HttpResponse, KeycloakTransaction {
private final org.jboss.resteasy.spi.HttpResponse delegate; private final org.jboss.resteasy.spi.HttpResponse delegate;
private Set<HttpCookie> cookies;
private boolean transactionActive;
private boolean writeCookiesOnTransactionComplete;
public HttpResponseImpl(org.jboss.resteasy.spi.HttpResponse delegate) { public HttpResponseImpl(KeycloakSession session, org.jboss.resteasy.spi.HttpResponse delegate) {
this.delegate = delegate; this.delegate = delegate;
session.getTransactionManager().enlistAfterCompletion(this);
} }
@Override @Override
@ -44,6 +56,31 @@ public class HttpResponseImpl implements HttpResponse {
delegate.getOutputHeaders().putSingle(name, value); delegate.getOutputHeaders().putSingle(name, value);
} }
@Override
public void setCookieIfAbsent(HttpCookie cookie) {
if (cookie == null) {
throw new IllegalArgumentException("Cookie is null");
}
if (cookies == null) {
cookies = new HashSet<>();
}
if (cookies.add(cookie)) {
if (writeCookiesOnTransactionComplete) {
// cookies are written after transaction completes
return;
}
addHeader(HttpHeaders.SET_COOKIE, cookie.toHeaderValue());
}
}
@Override
public void setWriteCookiesOnTransactionComplete() {
this.writeCookiesOnTransactionComplete = true;
}
/** /**
* Validate that the response has not been committed. * Validate that the response has not been committed.
* If the response is already committed, the headers and part of the response have been sent already. * If the response is already committed, the headers and part of the response have been sent already.
@ -55,4 +92,58 @@ public class HttpResponseImpl implements HttpResponse {
} }
} }
@Override
public void begin() {
transactionActive = true;
}
@Override
public void commit() {
if (!transactionActive) {
throw new IllegalStateException("Transaction not active. Response already committed or rolled back");
}
try {
addCookiesAfterTransaction();
} finally {
close();
}
}
@Override
public void rollback() {
close();
}
@Override
public void setRollbackOnly() {
}
@Override
public boolean getRollbackOnly() {
return false;
}
@Override
public boolean isActive() {
return transactionActive;
}
private void close() {
transactionActive = false;
cookies = null;
}
private void addCookiesAfterTransaction() {
if (cookies == null || !writeCookiesOnTransactionComplete) {
return;
}
// Ensure that cookies are only added when the transaction is complete, as otherwise cookies will be set for
// error pages, or will be added twice when running retries.
for (HttpCookie cookie : cookies) {
addHeader(HttpHeaders.SET_COOKIE, cookie.toHeaderValue());
}
}
} }

View file

@ -909,7 +909,6 @@ public class AuthenticationManager {
AuthResult authResult = verifyIdentityToken(session, realm, session.getContext().getUri(), session.getContext().getConnection(), checkActive, false, null, true, tokenString, session.getContext().getRequestHeaders(), VALIDATE_IDENTITY_COOKIE); AuthResult authResult = verifyIdentityToken(session, realm, session.getContext().getUri(), session.getContext().getConnection(), checkActive, false, null, true, tokenString, session.getContext().getRequestHeaders(), VALIDATE_IDENTITY_COOKIE);
if (authResult == null) { if (authResult == null) {
expireIdentityCookie(realm, session.getContext().getUri(), session); expireIdentityCookie(realm, session.getContext().getUri(), session);
expireOldIdentityCookie(realm, session.getContext().getUri(), session);
return null; return null;
} }
authResult.getSession().setLastSessionRefresh(Time.currentTime()); authResult.getSession().setLastSessionRefresh(Time.currentTime());

View file

@ -81,7 +81,6 @@ 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;
@ -261,7 +260,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); session.getContext().getHttpResponse().setWriteCookiesOnTransactionComplete();
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);

View file

@ -18,18 +18,16 @@
package org.keycloak.services.util; package org.keycloak.services.util;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.http.HttpCookie;
import org.keycloak.http.HttpResponse; import org.keycloak.http.HttpResponse;
import org.jboss.resteasy.util.CookieParser; import org.jboss.resteasy.util.CookieParser;
import org.keycloak.common.util.ServerCookie;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakTransaction;
import javax.ws.rs.core.Cookie; import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.HttpHeaders; 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;
@ -44,7 +42,6 @@ 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
@ -69,14 +66,9 @@ public class CookieHelper {
boolean secure_sameSite = sameSite == SameSiteAttributeValue.NONE || secure; // when SameSite=None, Secure attribute must be set boolean secure_sameSite = sameSite == SameSiteAttributeValue.NONE || secure; // when SameSite=None, Secure attribute must be set
HttpResponse response = session.getContext().getHttpResponse(); HttpResponse response = session.getContext().getHttpResponse();
StringBuffer cookieBuf = new StringBuffer(); HttpCookie cookie = new HttpCookie(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(); response.setCookieIfAbsent(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) {
@ -84,21 +76,6 @@ 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
@ -172,49 +149,4 @@ public class CookieHelper {
return cookies.get(legacy); return cookies.get(legacy);
} }
} }
/**
* Ensure that cookies are only added when the transaction is complete, as otherwise cookies will be set for error pages,
* or will be added twice when running retries.
*/
private static class CookieTransaction implements KeycloakTransaction {
private final HttpResponse response;
private final String cookie;
private boolean transactionActive;
public CookieTransaction(HttpResponse response, String cookie) {
this.response = response;
this.cookie = cookie;
}
@Override
public void begin() {
transactionActive = true;
}
@Override
public void commit() {
response.addHeader(HttpHeaders.SET_COOKIE, cookie);
transactionActive = false;
}
@Override
public void rollback() {
transactionActive = false;
}
@Override
public void setRollbackOnly() {
}
@Override
public boolean getRollbackOnly() {
return false;
}
@Override
public boolean isActive() {
return transactionActive;
}
}
} }

View file

@ -16,20 +16,25 @@
*/ */
package org.keycloak.testsuite.cookies; package org.keycloak.testsuite.cookies;
import org.apache.http.Header;
import org.apache.http.client.CookieStore;
import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.protocol.HttpClientContext; import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.impl.client.BasicCookieStore; import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.cookie.BasicClientCookie; import org.apache.http.impl.cookie.BasicClientCookie;
import org.apache.http.protocol.BasicHttpContext; import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext; import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils; import org.apache.http.util.EntityUtils;
import org.jboss.arquillian.graphene.page.Page; import org.jboss.arquillian.graphene.page.Page;
import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.common.Profile; import org.keycloak.common.Profile;
import org.keycloak.models.Constants;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest; import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.arquillian.annotation.DisableFeature; import org.keycloak.testsuite.arquillian.annotation.DisableFeature;
import org.keycloak.testsuite.auth.page.AuthRealm; import org.keycloak.testsuite.auth.page.AuthRealm;
@ -40,7 +45,10 @@ import org.keycloak.testsuite.util.OAuthClient.AuthorizationEndpointResponse;
import org.keycloak.testsuite.util.RealmBuilder; import org.keycloak.testsuite.util.RealmBuilder;
import org.openqa.selenium.Cookie; import org.openqa.selenium.Cookie;
import java.io.IOException;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
@ -56,6 +64,8 @@ import static org.keycloak.services.util.CookieHelper.LEGACY_COOKIE;
import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson; import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWithLoginUrlOf; import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWithLoginUrlOf;
import javax.ws.rs.core.HttpHeaders;
/** /**
* *
* @author hmlnarik * @author hmlnarik
@ -186,6 +196,48 @@ public class CookieTest extends AbstractKeycloakTest {
assertSameSiteCookies(sameSiteAuthSessionIdCookie, legacyAuthSessionIdCookie); assertSameSiteCookies(sameSiteAuthSessionIdCookie, legacyAuthSessionIdCookie);
} }
@Test
public void testNoDuplicationsWhenExpiringCookies() throws IOException {
ContainerAssume.assumeAuthServerSSL();
accountPage.navigateTo();
assertCurrentUrlStartsWithLoginUrlOf(accountPage);
loginPage.login("test-user@localhost", "password");
UsersResource usersResource = realmsResouce().realm(AuthRealm.TEST).users();
UserRepresentation user = usersResource.search("test-user@localhost").get(0);
usersResource.get(user.getId()).logout();
Cookie invalidIdentityCookie = driver.manage().getCookieNamed(KEYCLOAK_IDENTITY_COOKIE);
CookieStore cookieStore = new BasicCookieStore();
BasicClientCookie invalidClientIdentityCookie = new BasicClientCookie(invalidIdentityCookie.getName(), invalidIdentityCookie.getValue());
invalidClientIdentityCookie.setDomain(invalidIdentityCookie.getDomain());
invalidClientIdentityCookie.setPath(invalidClientIdentityCookie.getPath());
cookieStore.addCookie(invalidClientIdentityCookie);
try (CloseableHttpClient client = HttpClients.custom().setDefaultCookieStore(cookieStore).build()) {
HttpGet get = new HttpGet(
suiteContext.getAuthServerInfo().getContextRoot() + "/auth/realms/" + AuthRealm.TEST + "/protocol/openid-connect/auth?response_type=code&client_id=" + Constants.ACCOUNT_CONSOLE_CLIENT_ID +
"&redirect_uri=" + suiteContext.getAuthServerInfo().getContextRoot() + "/auth/realms/" + AuthRealm.TEST + "/account&scope=openid");
try (CloseableHttpResponse response = client.execute(get)) {
Header[] headers = response.getHeaders(HttpHeaders.SET_COOKIE);
Set<String> cookies = new HashSet<>();
for (Header header : headers) {
assertTrue("Cookie '" + header.getValue() + "' is duplicated", cookies.add(header.getValue()));
}
assertFalse(cookies.isEmpty());
}
}
}
private void assertSameSiteCookies(Cookie sameSiteCookie, Cookie legacyCookie) { private void assertSameSiteCookies(Cookie sameSiteCookie, Cookie legacyCookie) {
assertNotNull("SameSite cookie shouldn't be null", sameSiteCookie); assertNotNull("SameSite cookie shouldn't be null", sameSiteCookie);
assertNotNull("Legacy cookie shouldn't be null", legacyCookie); assertNotNull("Legacy cookie shouldn't be null", legacyCookie);