8000 #12682 [CORS] Allow Access-Control-Allow-Headers customization by dteleguin · Pull Request #40011 · keycloak/keycloak · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

#12682 [CORS] Allow Access-Control-Allow-Headers customization #40011

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public interface Cors extends Provider {

long DEFAULT_MAX_AGE = TimeUnit.HOURS.toSeconds(1);
String DEFAULT_ALLOW_METHODS = "GET, HEAD, OPTIONS";
String DEFAULT_ALLOW_HEADERS = "Origin, Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers, DPoP";
HeaderSet DEFAULT_ALLOW_HEADERS = HeaderSet.parse("Origin, Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers, DPoP");

String ORIGIN_HEADER = "Origin";
String AUTHORIZATION_HEADER = "Authorization";
Expand Down Expand Up @@ -70,6 +70,8 @@ static Cors builder() {

Cors allowedMethods(String... allowedMethods);

Cors allowedHeaders(String... allowedHeaders);

Cors exposedHeaders(String... exposedHeaders);

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
/*
* Copyright 2025 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.services.cors;

import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;

import org.keycloak.common.util.CollectionUtil;

/**
* A {@link Set} of {@link String} elements backed by a {@link TreeSet}, which provides sorting, deduplication
* and caching of the string representation.
*
* The string representation, returned by {@link #toHeaderString}, is a concatenation of the elements with ", " as a
* separator. It will be recomputed (and cached) every time the HeaderSet is modified.
*
* @author <a href="mailto:demetrio@carretti.pro">Dmitry Telegin</a>
*/
public class HeaderSet implements Set<String> {

private final TreeSet<String> set;
private String string;

public HeaderSet(Collection<String> c) {
this.set = new TreeSet<>(c);
update();
}

private HeaderSet(TreeSet<String> set, String string) {
this.set = set;
this.string = string;
}

/**
* Returns a mutable copy of the given {@link HeaderSet}.
*
* @param hs source {@link HeaderSet}
* @return a mutable copy of hs
*/
@SuppressWarnings("unchecked")
public static HeaderSet copyOf(HeaderSet hs) {
return new HeaderSet((TreeSet<String>) hs.set.clone(), hs.string);
}

/**
* Parses a comma-separated HTTP header list into a {@link HeaderSet}.
*
* @param s a comma-separated list of headers
* @return a {@link HeaderSet}
*/
public static HeaderSet parse(String s) {
String[] split = s.split("[,\\s]+");
return new HeaderSet(Arrays.asList(split));
}

@Override
public int size() {
return set.size();
}

@Override
public boolean isEmpty() {
return set.isEmpty();
}

@Override
public boolean contains(Object o) {
return set.contains(o);
}

@Override
public Iterator<String> iterator() {
return new Iterator<String>() {

private final Iterator<String> iterator = set.iterator();

@Override
public boolean hasNext() {
return iterator.hasNext();
}

@Override
public String next() {
return iterator.next();
}

@Override
public void remove() {
iterator.remove();
update();
}

};
}

@Override
public Object[] toArray() {
return set.toArray();
}

@Override
public <T> T[] toArray(T[] a) {
return set.toArray(a);
}

@Override
public boolean add(String s) {
boolean added = set.add(s);
if (added) update();
return added;
}

@Override
public boolean remove(Object o) {
boolean removed = set.remove(o);
if (removed) update();
return removed;
}

@Override
public boolean containsAll(Collection c) {
return set.containsAll(c);
}

@Override
public boolean addAll(Collection<? extends String> c) {
boolean changed = set.addAll(c);
if (changed) update();
return changed;
}

@Override
public boolean retainAll(Collection c) {
boolean changed = set.retainAll(c);
if (changed) update();
return changed;
}

@Override
public boolean removeAll(Collection c) {
boolean changed = set.removeAll(c);
if (changed) update();
return changed;
}

@Override
public void clear() {
set.clear();
update();
}

@Override
public String toString() {
return set.toString();
}

/**
* Returns a concatenation of the elements with ", " as a separator. The value will be recomputed (and cached)
* every time the @{link HeaderSet} is modified.
*
* @return string representation of the {@link HeaderSet}
*/
public String toHeaderString() {
return string;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
} else if (o != null && o instanceof HeaderSet hs) {
return set.equals(hs.set);
} else {
return false;
}
}

@Override
public int hashCode() {
return set.hashCode();
}

private void update() {
string = CollectionUtil.join(set);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,13 @@ public class DefaultCors implements Cors {
private ResponseBuilder builder;
private Set<String> allowedOrigins;
private Set<String> allowedMethods;
private final HeaderSet allowedHeaders = HeaderSet.copyOf(DEFAULT_ALLOW_HEADERS);
private Set<String> exposedHeaders;

private boolean preflight;
private boolean auth;

DefaultCors(KeycloakSession session) {
public DefaultCors(KeycloakSession session) {
this.session = session;
this.request = session.getContext().getHttpRequest();
this.response = session.getContext().getHttpResponse();
Expand Down Expand Up @@ -112,6 +113,12 @@ public Cors allowedMethods(String... allowedMethods) {
return this;
}

@Override
public Cors allowedHeaders(String... allowedHeaders) {
this.allowedHeaders.addAll(Arrays.asList(allowedHeaders));
return this;
}

@Override
public Cors exposedHeaders(String... exposedHeaders) {
if (this.exposedHeaders == null) {
Expand Down Expand Up @@ -165,13 +172,9 @@ public void add() {

if (preflight) {
if (auth) {
response.setHeader(ACCESS_CONTROL_ALLOW_HEADERS, String.format("%s, %s", DEFAULT_ALLOW_HEADERS, AUTHORIZATION_HEADER));
} else {
response.setHeader(ACCESS_CONTROL_ALLOW_HEADERS, DEFAULT_ALLOW_HEADERS);
allowedHeaders(AUTHORIZATION_HEADER);
}
}

if (preflight) {
response.setHeader(ACCESS_CONTROL_ALLOW_HEADERS, allowedHeaders.toHeaderString());
response.setHeader(ACCESS_CONTROL_MAX_AGE, String.valueOf(DEFAULT_MAX_AGE));
}
}
Expand Down
Loading
0