net.optionfactory.keycloak.cookies.SiteLocalAsSecureCookieProvider Maven / Gradle / Ivy
package net.optionfactory.keycloak.cookies;
import jakarta.ws.rs.core.Cookie;
import jakarta.ws.rs.core.NewCookie;
import java.net.Inet4Address;
import java.net.URI;
import java.net.UnknownHostException;
import java.util.Map;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.cookie.CookieMaxAge;
import org.keycloak.cookie.CookieProvider;
import org.keycloak.cookie.CookieProviderFactory;
import org.keycloak.cookie.CookieType;
import org.keycloak.models.KeycloakContext;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.services.resources.RealmsResource;
import org.keycloak.utils.SecureContextResolver;
/**
* must be used together with
*
* --unsafely-treat-insecure-origin-as-secure=http://172.18.xxx.xxx:8080/
*
* on chrome:
*
* chrome://flags/#unsafely-treat-insecure-origin-as-secure
*
*
*
* @author rferranti
*/
public class SiteLocalAsSecureCookieProvider implements CookieProvider {
private static final Logger logger = Logger.getLogger(SiteLocalAsSecureCookieProvider.class);
private final KeycloakSession session;
private final CookiePathResolver pathResolver;
private final boolean secure;
private final boolean allowed;
private final Map cookies;
public SiteLocalAsSecureCookieProvider(KeycloakSession session) {
KeycloakContext context = session.getContext();
this.session = session;
this.cookies = context.getRequestHeaders().getCookies();
this.pathResolver = new CookiePathResolver(context);
this.secure = SecureContextResolver.isSecureContext(session);
this.allowed = isAllowed(context.getUri().getRequestUri());
if (logger.isTraceEnabled()) {
logger.tracef("Received cookies: %s, path: %s", String.join(", ", this.cookies.keySet()), context.getUri().getRequestUri().getRawPath());
}
if (!secure && !allowed) {
logger.warnf("Non-secure context detected; cookies are not secured, and will not be available in cross-origin POST requests");
}
expireOldUnusedCookies();
}
@Override
public void set(CookieType cookieType, String value) {
if (cookieType.getDefaultMaxAge() == null) {
throw new IllegalArgumentException(cookieType + " has no default max-age");
}
set(cookieType, value, cookieType.getDefaultMaxAge());
}
@Override
public void set(CookieType cookieType, String value, int maxAge) {
String name = cookieType.getName();
NewCookie.SameSite sameSite = cookieType.getScope().getSameSite();
if (NewCookie.SameSite.NONE.equals(sameSite) && !secure && !allowed) {
sameSite = NewCookie.SameSite.LAX;
}
String path = pathResolver.resolvePath(cookieType);
boolean httpOnly = cookieType.getScope().isHttpOnly();
NewCookie newCookie = new NewCookie.Builder(name)
.version(1)
.value(value)
.path(path)
.maxAge(maxAge)
.secure(secure || allowed)
.httpOnly(httpOnly)
.sameSite(sameSite)
.build();
session.getContext().getHttpResponse().setCookieIfAbsent(newCookie);
logger.tracef("Setting cookie: name: %s, path: %s, same-site: %s, secure: %s, http-only: %s, max-age: %d", name, path, sameSite, secure, httpOnly, maxAge);
}
@Override
public String get(CookieType cookieType) {
Cookie cookie = cookies.get(cookieType.getName());
return cookie != null ? cookie.getValue() : null;
}
@Override
public void expire(CookieType cookieType) {
String cookieName = cookieType.getName();
Cookie cookie = cookies.get(cookieName);
if (cookie != null) {
String path = pathResolver.resolvePath(cookieType);
NewCookie newCookie = new NewCookie.Builder(cookieName)
.version(1)
.path(path)
.maxAge(CookieMaxAge.EXPIRED)
.build();
session.getContext().getHttpResponse().setCookieIfAbsent(newCookie);
logger.tracef("Expiring cookie: name: %s, path: %s", cookie.getName(), path);
}
}
private void expireOldUnusedCookies() {
for (CookieType cookieType : CookieType.OLD_UNUSED_COOKIES) {
expire(cookieType);
}
}
@Override
public void close() {
}
private boolean isAllowed(URI uri) {
String host = uri.getHost();
if (host == null) {
return false;
}
try {
return Inet4Address.getByName(host).isSiteLocalAddress();
} catch (UnknownHostException ex) {
return false;
}
}
/**
* duplicated from keycloak for visibility
*/
public static class CookiePathResolver {
private final KeycloakContext context;
private String realmPath;
private String requestPath;
CookiePathResolver(KeycloakContext context) {
this.context = context;
}
String resolvePath(CookieType cookieType) {
switch (cookieType.getPath()) {
case REALM:
if (realmPath == null) {
realmPath = RealmsResource.realmBaseUrl(context.getUri()).path("/").build(context.getRealm().getName()).getRawPath();
}
return realmPath;
case REQUEST:
if (requestPath == null) {
requestPath = context.getUri().getRequestUri().getRawPath();
}
return requestPath;
default:
throw new IllegalArgumentException("Unsupported enum value " + cookieType.getPath().name());
}
}
}
public static class Factory implements CookieProviderFactory {
@Override
public CookieProvider create(KeycloakSession session) {
return new SiteLocalAsSecureCookieProvider(session);
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return "site-local-as-secure";
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy