Details
diff --git a/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java b/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java
index c576a5d..e49842b 100755
--- a/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java
+++ b/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java
@@ -289,10 +289,9 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
}
JsonWebToken token = jws.readJsonContent(JsonWebToken.class);
- String aud = token.getAudience();
String iss = token.getIssuer();
- if (aud != null && !aud.equals(getConfig().getClientId())) {
+ if (!token.hasAudience(getConfig().getClientId())) {
throw new IdentityBrokerException("Wrong audience from token.");
}
diff --git a/core/src/main/java/org/keycloak/json/StringOrArrayDeserializer.java b/core/src/main/java/org/keycloak/json/StringOrArrayDeserializer.java
new file mode 100644
index 0000000..00b8173
--- /dev/null
+++ b/core/src/main/java/org/keycloak/json/StringOrArrayDeserializer.java
@@ -0,0 +1,30 @@
+package org.keycloak.json;
+
+import org.codehaus.jackson.JsonNode;
+import org.codehaus.jackson.JsonParser;
+import org.codehaus.jackson.JsonProcessingException;
+import org.codehaus.jackson.map.DeserializationContext;
+import org.codehaus.jackson.map.JsonDeserializer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+public class StringOrArrayDeserializer extends JsonDeserializer<Object> {
+
+ @Override
+ public Object deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
+ JsonNode jsonNode = jsonParser.readValueAsTree();
+ if (jsonNode.isArray()) {
+ ArrayList<String> a = new ArrayList<>(1);
+ Iterator<JsonNode> itr = jsonNode.iterator();
+ while (itr.hasNext()) {
+ a.add(itr.next().getTextValue());
+ }
+ return a.toArray(new String[a.size()]);
+ } else {
+ return new String[] { jsonNode.getTextValue() };
+ }
+ }
+
+}
diff --git a/core/src/main/java/org/keycloak/json/StringOrArraySerializer.java b/core/src/main/java/org/keycloak/json/StringOrArraySerializer.java
new file mode 100644
index 0000000..f9b3547
--- /dev/null
+++ b/core/src/main/java/org/keycloak/json/StringOrArraySerializer.java
@@ -0,0 +1,25 @@
+package org.keycloak.json;
+
+import org.codehaus.jackson.JsonGenerator;
+import org.codehaus.jackson.map.JsonSerializer;
+import org.codehaus.jackson.map.SerializerProvider;
+
+import java.io.IOException;
+
+public class StringOrArraySerializer extends JsonSerializer<Object> {
+ @Override
+ public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
+ String[] array = (String[]) o;
+ if (array == null) {
+ jsonGenerator.writeNull();
+ } else if (array.length == 1) {
+ jsonGenerator.writeString(array[0]);
+ } else {
+ jsonGenerator.writeStartArray();
+ for (String s : array) {
+ jsonGenerator.writeString(s);
+ }
+ jsonGenerator.writeEndArray();
+ }
+ }
+}
diff --git a/core/src/main/java/org/keycloak/representations/AccessToken.java b/core/src/main/java/org/keycloak/representations/AccessToken.java
index 1380c2f..092298e 100755
--- a/core/src/main/java/org/keycloak/representations/AccessToken.java
+++ b/core/src/main/java/org/keycloak/representations/AccessToken.java
@@ -164,11 +164,6 @@ public class AccessToken extends IDToken {
}
@Override
- public AccessToken audience(String audience) {
- return (AccessToken) super.audience(audience);
- }
-
- @Override
public AccessToken subject(String subject) {
return (AccessToken) super.subject(subject);
}
diff --git a/core/src/main/java/org/keycloak/representations/IDToken.java b/core/src/main/java/org/keycloak/representations/IDToken.java
index 42f7679..499180b 100755
--- a/core/src/main/java/org/keycloak/representations/IDToken.java
+++ b/core/src/main/java/org/keycloak/representations/IDToken.java
@@ -1,12 +1,6 @@
package org.keycloak.representations;
-import org.codehaus.jackson.annotate.JsonAnyGetter;
-import org.codehaus.jackson.annotate.JsonAnySetter;
import org.codehaus.jackson.annotate.JsonProperty;
-import org.codehaus.jackson.annotate.JsonUnwrapped;
-
-import java.util.HashMap;
-import java.util.Map;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
diff --git a/core/src/main/java/org/keycloak/representations/JsonWebToken.java b/core/src/main/java/org/keycloak/representations/JsonWebToken.java
index f714d4f..b547fca 100755
--- a/core/src/main/java/org/keycloak/representations/JsonWebToken.java
+++ b/core/src/main/java/org/keycloak/representations/JsonWebToken.java
@@ -4,6 +4,10 @@ import org.codehaus.jackson.annotate.JsonAnyGetter;
import org.codehaus.jackson.annotate.JsonAnySetter;
import org.codehaus.jackson.annotate.JsonIgnore;
import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonDeserialize;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.keycloak.json.StringOrArrayDeserializer;
+import org.keycloak.json.StringOrArraySerializer;
import org.keycloak.util.Time;
import java.io.Serializable;
@@ -26,14 +30,16 @@ public class JsonWebToken implements Serializable {
@JsonProperty("iss")
protected String issuer;
@JsonProperty("aud")
- protected String audience;
+ @JsonSerialize(using = StringOrArraySerializer.class)
+ @JsonDeserialize(using = StringOrArrayDeserializer.class)
+ protected String[] audience;
@JsonProperty("sub")
protected String subject;
@JsonProperty("typ")
protected String type;
@JsonProperty("azp")
public String issuedFor;
- protected Map<String, Object> otherClaims = new HashMap<String, Object>();
+ protected Map<String, Object> otherClaims = new HashMap<>();
public String getId() {
return id;
@@ -72,7 +78,6 @@ public class JsonWebToken implements Serializable {
@JsonIgnore
public boolean isNotBefore() {
return Time.currentTime() >= notBefore;
-
}
/**
@@ -113,12 +118,21 @@ public class JsonWebToken implements Serializable {
return this;
}
-
- public String getAudience() {
+ @JsonIgnore
+ public String[] getAudience() {
return audience;
}
- public JsonWebToken audience(String audience) {
+ public boolean hasAudience(String audience) {
+ for (String a : this.audience) {
+ if (a.equals(audience)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public JsonWebToken audience(String... audience) {
this.audience = audience;
return this;
}
diff --git a/core/src/main/java/org/keycloak/util/JsonSerialization.java b/core/src/main/java/org/keycloak/util/JsonSerialization.java
index ff080de..a1a93ba 100755
--- a/core/src/main/java/org/keycloak/util/JsonSerialization.java
+++ b/core/src/main/java/org/keycloak/util/JsonSerialization.java
@@ -3,7 +3,6 @@ package org.keycloak.util;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;
import org.codehaus.jackson.map.annotate.JsonSerialize;
-import org.codehaus.jackson.type.TypeReference;
import java.io.IOException;
import java.io.InputStream;
diff --git a/core/src/test/java/org/keycloak/jose/JsonWebTokenTest.java b/core/src/test/java/org/keycloak/jose/JsonWebTokenTest.java
new file mode 100644
index 0000000..dbe0ecb
--- /dev/null
+++ b/core/src/test/java/org/keycloak/jose/JsonWebTokenTest.java
@@ -0,0 +1,44 @@
+package org.keycloak.jose;
+
+import org.junit.Test;
+import org.keycloak.representations.JsonWebToken;
+import org.keycloak.util.JsonSerialization;
+
+import java.io.IOException;
+
+import static org.junit.Assert.*;
+
+/**
+ * Created by st on 20.08.15.
+ */
+public class JsonWebTokenTest {
+
+ @Test
+ public void testAudSingle() throws IOException {
+ String single = "{ \"aud\": \"test\" }";
+ JsonWebToken s = JsonSerialization.readValue(single, JsonWebToken.class);
+ assertArrayEquals(new String[] { "test" }, s.getAudience());
+ }
+
+ @Test
+ public void testAudArray() throws IOException {
+ String single = "{ \"aud\": [\"test\"] }";
+ JsonWebToken s = JsonSerialization.readValue(single, JsonWebToken.class);
+ assertArrayEquals(new String[]{"test"}, s.getAudience());
+ }
+
+ @Test
+ public void test() throws IOException {
+ JsonWebToken jsonWebToken = new JsonWebToken();
+ jsonWebToken.audience("test");
+ assertTrue(JsonSerialization.writeValueAsPrettyString(jsonWebToken).contains("\"aud\" : \"test\""));
+ }
+
+ @Test
+ public void testArray() throws IOException {
+ JsonWebToken jsonWebToken = new JsonWebToken();
+ jsonWebToken.audience("test", "test2");
+ assertTrue(JsonSerialization.writeValueAsPrettyString(jsonWebToken).contains("\"aud\" : [ \"test\", \"test2\" ]"));
+ }
+
+}
diff --git a/integration/js/src/main/resources/keycloak.js b/integration/js/src/main/resources/keycloak.js
index 8917581..46d3b18 100755
--- a/integration/js/src/main/resources/keycloak.js
+++ b/integration/js/src/main/resources/keycloak.js
@@ -309,7 +309,7 @@
var tokenResponse = JSON.parse(req.responseText);
setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token']);
- kc.timeSkew = Math.floor(timeLocal / 1000) - keycloak.tokenParsed.iat;
+ kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
kc.onAuthRefreshSuccess && kc.onAuthRefreshSuccess();
for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) {
@@ -402,7 +402,7 @@
var tokenResponse = JSON.parse(req.responseText);
setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token']);
- kc.timeSkew = Math.floor(timeLocal / 1000) - keycloak.tokenParsed.iat;
+ kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
kc.onAuthSuccess && kc.onAuthSuccess();
promise && promise.setSuccess();
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java
index 5191ed0..3a98c8d 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java
@@ -111,13 +111,9 @@ public class JWTClientAuthenticator extends AbstractClientAuthenticator {
}
// Validate other things
- String audience = token.getAudience();
String expectedAudience = Urls.realmIssuer(context.getUriInfo().getBaseUri(), realm.getName());
- if (audience == null) {
- throw new RuntimeException("Audience is null on JWT");
- }
- if (!audience.equals(expectedAudience)) {
- throw new RuntimeException("Token audience doesn't match domain. Realm audience is '" + expectedAudience + "' but audience from token is '" + audience + "'");
+ if (!token.hasAudience(expectedAudience)) {
+ throw new RuntimeException("Token audience doesn't match domain. Realm audience is '" + expectedAudience + "' but audience from token is '" + token.getAudience() + "'");
}
if (!token.isActive()) {
diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
index c5a7f8f..15c5f38 100755
--- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
+++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
@@ -190,8 +190,8 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
if (authResult != null) {
AccessToken token = authResult.getToken();
- String audience = token.getAudience();
- ClientModel clientModel = this.realmModel.getClientByClientId(audience);
+ String[] audience = token.getAudience();
+ ClientModel clientModel = this.realmModel.getClientByClientId(audience[0]);
if (clientModel == null) {
return badRequest("Invalid client.");
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
index 8ce5c7c..fb2b5df 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
@@ -48,6 +48,7 @@ import org.keycloak.testsuite.pages.VerifyEmailPage;
import org.keycloak.testsuite.rule.GreenMailRule;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
+import org.keycloak.util.JsonSerialization;
import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebDriver;
@@ -801,10 +802,9 @@ public abstract class AbstractIdentityProviderTest {
UserSessionStatus sessionStatus = null;
try {
- ObjectMapper objectMapper = new ObjectMapper();
String pageSource = this.driver.getPageSource();
- sessionStatus = objectMapper.readValue(pageSource.getBytes(), UserSessionStatus.class);
+ sessionStatus = JsonSerialization.readValue(pageSource.getBytes(), UserSessionStatus.class);
} catch (IOException ignore) {
ignore.printStackTrace();
}