Merge pull request #4074 from sebastienblanc/allow_headers

Keycloak-3297 : adding cors-exposed-headers to conf
This commit is contained in:
Stian Thorgersen 2017-05-05 12:54:47 +02:00 committed by GitHub
commit e0da7ed6b4
14 changed files with 53 additions and 2 deletions

View file

@ -101,6 +101,7 @@ public class AuthenticatedActionsHandler {
if (!deployment.isCors()) return false; if (!deployment.isCors()) return false;
KeycloakSecurityContext securityContext = facade.getSecurityContext(); KeycloakSecurityContext securityContext = facade.getSecurityContext();
String origin = facade.getRequest().getHeader(CorsHeaders.ORIGIN); String origin = facade.getRequest().getHeader(CorsHeaders.ORIGIN);
String exposeHeaders = deployment.getCorsExposedHeaders();
String requestOrigin = UriUtils.getOrigin(facade.getRequest().getURI()); String requestOrigin = UriUtils.getOrigin(facade.getRequest().getURI());
log.debugv("Origin: {0} uri: {1}", origin, facade.getRequest().getURI()); log.debugv("Origin: {0} uri: {1}", origin, facade.getRequest().getURI());
if (securityContext != null && origin != null && !origin.equals(requestOrigin)) { if (securityContext != null && origin != null && !origin.equals(requestOrigin)) {
@ -124,6 +125,9 @@ public class AuthenticatedActionsHandler {
facade.getResponse().setStatus(200); facade.getResponse().setStatus(200);
facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, origin); facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, origin);
facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true"); facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
if (exposeHeaders != null) {
facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, exposeHeaders);
}
} else { } else {
log.debugv("cors validation not needed as we're not a secure session or origin header was null: {0}", facade.getRequest().getURI()); log.debugv("cors validation not needed as we're not a secure session or origin header was null: {0}", facade.getRequest().getURI());
} }

View file

@ -30,4 +30,5 @@ public interface CorsHeaders {
String ORIGIN = "Origin"; String ORIGIN = "Origin";
String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method"; String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method";
String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers"; String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers";
String ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers";
} }

View file

@ -73,6 +73,7 @@ public class KeycloakDeployment {
protected int corsMaxAge = -1; protected int corsMaxAge = -1;
protected String corsAllowedHeaders; protected String corsAllowedHeaders;
protected String corsAllowedMethods; protected String corsAllowedMethods;
protected String corsExposedHeaders;
protected boolean exposeToken; protected boolean exposeToken;
protected boolean alwaysRefreshToken; protected boolean alwaysRefreshToken;
protected boolean registerNodeAtStartup; protected boolean registerNodeAtStartup;
@ -325,6 +326,14 @@ public class KeycloakDeployment {
this.corsAllowedMethods = corsAllowedMethods; this.corsAllowedMethods = corsAllowedMethods;
} }
public String getCorsExposedHeaders() {
return corsExposedHeaders;
}
public void setCorsExposedHeaders(String corsExposedHeaders) {
this.corsExposedHeaders = corsExposedHeaders;
}
public boolean isExposeToken() { public boolean isExposeToken() {
return exposeToken; return exposeToken;
} }

View file

@ -96,6 +96,7 @@ public class KeycloakDeploymentBuilder {
deployment.setCorsMaxAge(adapterConfig.getCorsMaxAge()); deployment.setCorsMaxAge(adapterConfig.getCorsMaxAge());
deployment.setCorsAllowedHeaders(adapterConfig.getCorsAllowedHeaders()); deployment.setCorsAllowedHeaders(adapterConfig.getCorsAllowedHeaders());
deployment.setCorsAllowedMethods(adapterConfig.getCorsAllowedMethods()); deployment.setCorsAllowedMethods(adapterConfig.getCorsAllowedMethods());
deployment.setCorsExposedHeaders(adapterConfig.getCorsExposedHeaders());
} }
// https://tools.ietf.org/html/rfc7636 // https://tools.ietf.org/html/rfc7636

View file

@ -53,6 +53,7 @@ public class KeycloakDeploymentBuilderTest {
assertEquals(1000, deployment.getCorsMaxAge()); assertEquals(1000, deployment.getCorsMaxAge());
assertEquals("POST, PUT, DELETE, GET", deployment.getCorsAllowedMethods()); assertEquals("POST, PUT, DELETE, GET", deployment.getCorsAllowedMethods());
assertEquals("X-Custom, X-Custom2", deployment.getCorsAllowedHeaders()); assertEquals("X-Custom, X-Custom2", deployment.getCorsAllowedHeaders());
assertEquals("X-Custom3, X-Custom4", deployment.getCorsExposedHeaders());
assertTrue(deployment.isBearerOnly()); assertTrue(deployment.isBearerOnly());
assertTrue(deployment.isPublicClient()); assertTrue(deployment.isPublicClient());
assertTrue(deployment.isEnableBasicAuth()); assertTrue(deployment.isEnableBasicAuth());

View file

@ -9,6 +9,7 @@
"cors-max-age": 1000, "cors-max-age": 1000,
"cors-allowed-methods": "POST, PUT, DELETE, GET", "cors-allowed-methods": "POST, PUT, DELETE, GET",
"cors-allowed-headers": "X-Custom, X-Custom2", "cors-allowed-headers": "X-Custom, X-Custom2",
"cors-exposed-headers": "X-Custom3, X-Custom4",
"bearer-only": true, "bearer-only": true,
"public-client": true, "public-client": true,
"enable-basic-auth": true, "enable-basic-auth": true,

View file

@ -124,6 +124,12 @@ public class SharedAttributeDefinitons {
.setAllowExpression(true) .setAllowExpression(true)
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true)) .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
.build(); .build();
protected static final SimpleAttributeDefinition CORS_EXPOSED_HEADERS =
new SimpleAttributeDefinitionBuilder("cors-exposed-headers", ModelType.STRING, true)
.setXmlName("cors-exposed-headers")
.setAllowExpression(true)
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
.build();
protected static final SimpleAttributeDefinition EXPOSE_TOKEN = protected static final SimpleAttributeDefinition EXPOSE_TOKEN =
new SimpleAttributeDefinitionBuilder("expose-token", ModelType.BOOLEAN, true) new SimpleAttributeDefinitionBuilder("expose-token", ModelType.BOOLEAN, true)
.setXmlName("expose-token") .setXmlName("expose-token")
@ -191,6 +197,7 @@ public class SharedAttributeDefinitons {
ATTRIBUTES.add(CORS_MAX_AGE); ATTRIBUTES.add(CORS_MAX_AGE);
ATTRIBUTES.add(CORS_ALLOWED_HEADERS); ATTRIBUTES.add(CORS_ALLOWED_HEADERS);
ATTRIBUTES.add(CORS_ALLOWED_METHODS); ATTRIBUTES.add(CORS_ALLOWED_METHODS);
ATTRIBUTES.add(CORS_EXPOSED_HEADERS);
ATTRIBUTES.add(EXPOSE_TOKEN); ATTRIBUTES.add(EXPOSE_TOKEN);
ATTRIBUTES.add(AUTH_SERVER_URL_FOR_BACKEND_REQUESTS); ATTRIBUTES.add(AUTH_SERVER_URL_FOR_BACKEND_REQUESTS);
ATTRIBUTES.add(ALWAYS_REFRESH_TOKEN); ATTRIBUTES.add(ALWAYS_REFRESH_TOKEN);

View file

@ -39,6 +39,7 @@ keycloak.realm.client-key-password=n/a
keycloak.realm.cors-max-age=CORS max-age header keycloak.realm.cors-max-age=CORS max-age header
keycloak.realm.cors-allowed-headers=CORS allowed headers keycloak.realm.cors-allowed-headers=CORS allowed headers
keycloak.realm.cors-allowed-methods=CORS allowed methods keycloak.realm.cors-allowed-methods=CORS allowed methods
keycloak.realm.cors-exposed-headers=CORS exposed headers
keycloak.realm.expose-token=Enable secure URL that exposes access token keycloak.realm.expose-token=Enable secure URL that exposes access token
keycloak.realm.auth-server-url-for-backend-requests=URL to use to make background calls to auth server keycloak.realm.auth-server-url-for-backend-requests=URL to use to make background calls to auth server
keycloak.realm.always-refresh-token=Refresh token on every single web request keycloak.realm.always-refresh-token=Refresh token on every single web request
@ -73,6 +74,7 @@ keycloak.secure-deployment.client-key-password=n/a
keycloak.secure-deployment.cors-max-age=CORS max-age header keycloak.secure-deployment.cors-max-age=CORS max-age header
keycloak.secure-deployment.cors-allowed-headers=CORS allowed headers keycloak.secure-deployment.cors-allowed-headers=CORS allowed headers
keycloak.secure-deployment.cors-allowed-methods=CORS allowed methods keycloak.secure-deployment.cors-allowed-methods=CORS allowed methods
keycloak.secure-deployment.cors-exposed-headers=CORS exposed headers
keycloak.secure-deployment.expose-token=Enable secure URL that exposes access token keycloak.secure-deployment.expose-token=Enable secure URL that exposes access token
keycloak.secure-deployment.auth-server-url-for-backend-requests=URL to use to make background calls to auth server keycloak.secure-deployment.auth-server-url-for-backend-requests=URL to use to make background calls to auth server
keycloak.secure-deployment.always-refresh-token=Refresh token on every single web request keycloak.secure-deployment.always-refresh-token=Refresh token on every single web request

View file

@ -58,6 +58,7 @@
<xs:element name="disable-trust-manager" type="xs:boolean" minOccurs="0" maxOccurs="1" /> <xs:element name="disable-trust-manager" type="xs:boolean" minOccurs="0" maxOccurs="1" />
<xs:element name="ssl-required" type="xs:string" minOccurs="0" maxOccurs="1" /> <xs:element name="ssl-required" type="xs:string" minOccurs="0" maxOccurs="1" />
<xs:element name="cors-allowed-methods" type="xs:string" minOccurs="0" maxOccurs="1"/> <xs:element name="cors-allowed-methods" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="cors-exposed-headers" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="realm-public-key" type="xs:string" minOccurs="1" maxOccurs="1"/> <xs:element name="realm-public-key" type="xs:string" minOccurs="1" maxOccurs="1"/>
<xs:element name="auth-server-url-for-backend-requests" type="xs:string" minOccurs="0" maxOccurs="1"/> <xs:element name="auth-server-url-for-backend-requests" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="always-refresh-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/> <xs:element name="always-refresh-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
@ -88,6 +89,7 @@
<xs:element name="cors-allowed-methods" type="xs:string" minOccurs="0" maxOccurs="1"/> <xs:element name="cors-allowed-methods" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="bearer-only" type="xs:boolean" minOccurs="0" maxOccurs="1" /> <xs:element name="bearer-only" type="xs:boolean" minOccurs="0" maxOccurs="1" />
<xs:element name="cors-allowed-headers" type="xs:string" minOccurs="0" maxOccurs="1"/> <xs:element name="cors-allowed-headers" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="cors-exposed-headers" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="resource" type="xs:string" minOccurs="0" maxOccurs="1" /> <xs:element name="resource" type="xs:string" minOccurs="0" maxOccurs="1" />
<xs:element name="truststore" type="xs:string" minOccurs="0" maxOccurs="1"/> <xs:element name="truststore" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="truststore-password" type="xs:string" minOccurs="0" maxOccurs="1"/> <xs:element name="truststore-password" type="xs:string" minOccurs="0" maxOccurs="1"/>

View file

@ -124,6 +124,12 @@ public class SharedAttributeDefinitons {
.setAllowExpression(true) .setAllowExpression(true)
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true)) .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
.build(); .build();
protected static final SimpleAttributeDefinition CORS_EXPOSED_HEADERS =
new SimpleAttributeDefinitionBuilder("cors-exposed-headers", ModelType.STRING, true)
.setXmlName("cors-exposed-headers")
.setAllowExpression(true)
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
.build();
protected static final SimpleAttributeDefinition EXPOSE_TOKEN = protected static final SimpleAttributeDefinition EXPOSE_TOKEN =
new SimpleAttributeDefinitionBuilder("expose-token", ModelType.BOOLEAN, true) new SimpleAttributeDefinitionBuilder("expose-token", ModelType.BOOLEAN, true)
.setXmlName("expose-token") .setXmlName("expose-token")
@ -175,6 +181,8 @@ public class SharedAttributeDefinitons {
protected static final List<SimpleAttributeDefinition> ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>(); protected static final List<SimpleAttributeDefinition> ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
static { static {
ATTRIBUTES.add(REALM_PUBLIC_KEY); ATTRIBUTES.add(REALM_PUBLIC_KEY);
@ -192,6 +200,7 @@ public class SharedAttributeDefinitons {
ATTRIBUTES.add(CORS_MAX_AGE); ATTRIBUTES.add(CORS_MAX_AGE);
ATTRIBUTES.add(CORS_ALLOWED_HEADERS); ATTRIBUTES.add(CORS_ALLOWED_HEADERS);
ATTRIBUTES.add(CORS_ALLOWED_METHODS); ATTRIBUTES.add(CORS_ALLOWED_METHODS);
ATTRIBUTES.add(CORS_EXPOSED_HEADERS);
ATTRIBUTES.add(EXPOSE_TOKEN); ATTRIBUTES.add(EXPOSE_TOKEN);
ATTRIBUTES.add(AUTH_SERVER_URL_FOR_BACKEND_REQUESTS); ATTRIBUTES.add(AUTH_SERVER_URL_FOR_BACKEND_REQUESTS);
ATTRIBUTES.add(ALWAYS_REFRESH_TOKEN); ATTRIBUTES.add(ALWAYS_REFRESH_TOKEN);

View file

@ -39,6 +39,7 @@ keycloak.realm.client-key-password=n/a
keycloak.realm.cors-max-age=CORS max-age header keycloak.realm.cors-max-age=CORS max-age header
keycloak.realm.cors-allowed-headers=CORS allowed headers keycloak.realm.cors-allowed-headers=CORS allowed headers
keycloak.realm.cors-allowed-methods=CORS allowed methods keycloak.realm.cors-allowed-methods=CORS allowed methods
keycloak.realm.cors-exposed-headers=CORS exposed headers
keycloak.realm.expose-token=Enable secure URL that exposes access token keycloak.realm.expose-token=Enable secure URL that exposes access token
keycloak.realm.auth-server-url-for-backend-requests=URL to use to make background calls to auth server keycloak.realm.auth-server-url-for-backend-requests=URL to use to make background calls to auth server
keycloak.realm.always-refresh-token=Refresh token on every single web request keycloak.realm.always-refresh-token=Refresh token on every single web request
@ -74,6 +75,7 @@ keycloak.secure-deployment.client-key-password=n/a
keycloak.secure-deployment.cors-max-age=CORS max-age header keycloak.secure-deployment.cors-max-age=CORS max-age header
keycloak.secure-deployment.cors-allowed-headers=CORS allowed headers keycloak.secure-deployment.cors-allowed-headers=CORS allowed headers
keycloak.secure-deployment.cors-allowed-methods=CORS allowed methods keycloak.secure-deployment.cors-allowed-methods=CORS allowed methods
keycloak.secure-deployment.cors-exposed-headers=CORS exposed headers
keycloak.secure-deployment.expose-token=Enable secure URL that exposes access token keycloak.secure-deployment.expose-token=Enable secure URL that exposes access token
keycloak.secure-deployment.auth-server-url-for-backend-requests=URL to use to make background calls to auth server keycloak.secure-deployment.auth-server-url-for-backend-requests=URL to use to make background calls to auth server
keycloak.secure-deployment.always-refresh-token=Refresh token on every single web request keycloak.secure-deployment.always-refresh-token=Refresh token on every single web request

View file

@ -58,6 +58,7 @@
<xs:element name="disable-trust-manager" type="xs:boolean" minOccurs="0" maxOccurs="1" /> <xs:element name="disable-trust-manager" type="xs:boolean" minOccurs="0" maxOccurs="1" />
<xs:element name="ssl-required" type="xs:string" minOccurs="0" maxOccurs="1" /> <xs:element name="ssl-required" type="xs:string" minOccurs="0" maxOccurs="1" />
<xs:element name="cors-allowed-methods" type="xs:string" minOccurs="0" maxOccurs="1"/> <xs:element name="cors-allowed-methods" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="cors-exposed-headers" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="realm-public-key" type="xs:string" minOccurs="1" maxOccurs="1"/> <xs:element name="realm-public-key" type="xs:string" minOccurs="1" maxOccurs="1"/>
<xs:element name="auth-server-url-for-backend-requests" type="xs:string" minOccurs="0" maxOccurs="1"/> <xs:element name="auth-server-url-for-backend-requests" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="always-refresh-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/> <xs:element name="always-refresh-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
@ -88,6 +89,7 @@
<xs:element name="cors-allowed-methods" type="xs:string" minOccurs="0" maxOccurs="1"/> <xs:element name="cors-allowed-methods" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="bearer-only" type="xs:boolean" minOccurs="0" maxOccurs="1" /> <xs:element name="bearer-only" type="xs:boolean" minOccurs="0" maxOccurs="1" />
<xs:element name="cors-allowed-headers" type="xs:string" minOccurs="0" maxOccurs="1"/> <xs:element name="cors-allowed-headers" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="cors-exposed-headers" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="resource" type="xs:string" minOccurs="0" maxOccurs="1" /> <xs:element name="resource" type="xs:string" minOccurs="0" maxOccurs="1" />
<xs:element name="truststore" type="xs:string" minOccurs="0" maxOccurs="1"/> <xs:element name="truststore" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="truststore-password" type="xs:string" minOccurs="0" maxOccurs="1"/> <xs:element name="truststore-password" type="xs:string" minOccurs="0" maxOccurs="1"/>

View file

@ -29,7 +29,7 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder;
@JsonPropertyOrder({"realm", "realm-public-key", "auth-server-url", "ssl-required", @JsonPropertyOrder({"realm", "realm-public-key", "auth-server-url", "ssl-required",
"resource", "public-client", "credentials", "resource", "public-client", "credentials",
"use-resource-role-mappings", "use-resource-role-mappings",
"enable-cors", "cors-max-age", "cors-allowed-methods", "enable-cors", "cors-max-age", "cors-allowed-methods", "cors-exposed-headers",
"expose-token", "bearer-only", "autodetect-bearer-only", "expose-token", "bearer-only", "autodetect-bearer-only",
"connection-pool-size", "connection-pool-size",
"allow-any-hostname", "disable-trust-manager", "truststore", "truststore-password", "allow-any-hostname", "disable-trust-manager", "truststore", "truststore-password",

View file

@ -33,7 +33,7 @@ import java.util.TreeMap;
@JsonPropertyOrder({"realm", "realm-public-key", "auth-server-url", "ssl-required", @JsonPropertyOrder({"realm", "realm-public-key", "auth-server-url", "ssl-required",
"resource", "public-client", "credentials", "resource", "public-client", "credentials",
"use-resource-role-mappings", "use-resource-role-mappings",
"enable-cors", "cors-max-age", "cors-allowed-methods", "enable-cors", "cors-max-age", "cors-allowed-methods", "cors-exposed-headers",
"expose-token", "bearer-only", "autodetect-bearer-only", "enable-basic-auth"}) "expose-token", "bearer-only", "autodetect-bearer-only", "enable-basic-auth"})
public class BaseAdapterConfig extends BaseRealmConfig { public class BaseAdapterConfig extends BaseRealmConfig {
@JsonProperty("resource") @JsonProperty("resource")
@ -48,6 +48,8 @@ public class BaseAdapterConfig extends BaseRealmConfig {
protected String corsAllowedHeaders; protected String corsAllowedHeaders;
@JsonProperty("cors-allowed-methods") @JsonProperty("cors-allowed-methods")
protected String corsAllowedMethods; protected String corsAllowedMethods;
@JsonProperty("cors-exposed-headers")
protected String corsExposedHeaders;
@JsonProperty("expose-token") @JsonProperty("expose-token")
protected boolean exposeToken; protected boolean exposeToken;
@JsonProperty("bearer-only") @JsonProperty("bearer-only")
@ -110,6 +112,14 @@ public class BaseAdapterConfig extends BaseRealmConfig {
this.corsAllowedMethods = corsAllowedMethods; this.corsAllowedMethods = corsAllowedMethods;
} }
public String getCorsExposedHeaders() {
return corsExposedHeaders;
}
public void setCorsExposedHeaders(String corsExposedHeaders) {
this.corsExposedHeaders = corsExposedHeaders;
}
public boolean isExposeToken() { public boolean isExposeToken() {
return exposeToken; return exposeToken;
} }