keycloak-developers

Merge pull request #919 from pedroigor/KEYCLOAK-884 [KEYCLOAK-884]

1/16/2015 5:28:30 PM

Details

diff --git a/core/src/main/java/org/keycloak/representations/IDToken.java b/core/src/main/java/org/keycloak/representations/IDToken.java
index 975da61..784e409 100755
--- a/core/src/main/java/org/keycloak/representations/IDToken.java
+++ b/core/src/main/java/org/keycloak/representations/IDToken.java
@@ -1,96 +1,23 @@
 package org.keycloak.representations;
 
 import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.annotate.JsonUnwrapped;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
 public class IDToken extends JsonWebToken {
+
     @JsonProperty("nonce")
     protected String nonce;
 
-    @JsonProperty("name")
-    protected String name;
-
-    @JsonProperty("given_name")
-    protected String givenName;
-
-    @JsonProperty("family_name")
-    protected String familyName;
-
-    @JsonProperty("middle_name")
-    protected String middleName;
-
-    @JsonProperty("nickname")
-    protected String nickName;
-
-    @JsonProperty("preferred_username")
-    protected String preferredUsername;
-
-    @JsonProperty("profile")
-    protected String profile;
-
-    @JsonProperty("picture")
-    protected String picture;
-
-    @JsonProperty("website")
-    protected String website;
-
-    @JsonProperty("email")
-    protected String email;
-
-    @JsonProperty("email_verified")
-    protected Boolean emailVerified;
-
-    @JsonProperty("gender")
-    protected String gender;
-
-    @JsonProperty("birthdate")
-    protected String birthdate;
-
-    @JsonProperty("zoneinfo")
-    protected String zoneinfo;
-
-    @JsonProperty("locale")
-    protected String locale;
-
-    @JsonProperty("phone_number")
-    protected String phoneNumber;
-
-    @JsonProperty("phone_number_verified")
-    protected Boolean phoneNumberVerified;
-
-    @JsonProperty("address")
-    protected String address;
-
-    @JsonProperty("updated_at")
-    protected Long updatedAt;
-
-    @JsonProperty("formatted")
-    protected String formattedAddress;
-
-    @JsonProperty("street_address")
-    protected String streetAddress;
-
-    @JsonProperty("locality")
-    protected String locality;
-
-    @JsonProperty("region")
-    protected String region;
-
-    @JsonProperty("postal_code")
-    protected String postalCode;
-
-    @JsonProperty("country")
-    protected String country;
-
-    @JsonProperty("claims_locales")
-    protected String claimsLocales;
-
     @JsonProperty("session_state")
     protected String sessionState;
 
+    @JsonUnwrapped
+    protected UserClaimSet userClaimSet = new UserClaimSet();
+
     public String getNonce() {
         return nonce;
     }
@@ -99,214 +26,6 @@ public class IDToken extends JsonWebToken {
         this.nonce = nonce;
     }
 
-    public String getName() {
-        return name;
-    }
-
-    public void setName(String name) {
-        this.name = name;
-    }
-
-    public String getGivenName() {
-        return givenName;
-    }
-
-    public void setGivenName(String givenName) {
-        this.givenName = givenName;
-    }
-
-    public String getFamilyName() {
-        return familyName;
-    }
-
-    public void setFamilyName(String familyName) {
-        this.familyName = familyName;
-    }
-
-    public String getMiddleName() {
-        return middleName;
-    }
-
-    public void setMiddleName(String middleName) {
-        this.middleName = middleName;
-    }
-
-    public String getNickName() {
-        return nickName;
-    }
-
-    public void setNickName(String nickName) {
-        this.nickName = nickName;
-    }
-
-    public String getPreferredUsername() {
-        return preferredUsername;
-    }
-
-    public void setPreferredUsername(String preferredUsername) {
-        this.preferredUsername = preferredUsername;
-    }
-
-    public String getProfile() {
-        return profile;
-    }
-
-    public void setProfile(String profile) {
-        this.profile = profile;
-    }
-
-    public String getPicture() {
-        return picture;
-    }
-
-    public void setPicture(String picture) {
-        this.picture = picture;
-    }
-
-    public String getWebsite() {
-        return website;
-    }
-
-    public void setWebsite(String website) {
-        this.website = website;
-    }
-
-    public String getEmail() {
-        return email;
-    }
-
-    public void setEmail(String email) {
-        this.email = email;
-    }
-
-    public Boolean getEmailVerified() {
-        return emailVerified;
-    }
-
-    public void setEmailVerified(Boolean emailVerified) {
-        this.emailVerified = emailVerified;
-    }
-
-    public String getGender() {
-        return gender;
-    }
-
-    public void setGender(String gender) {
-        this.gender = gender;
-    }
-
-    public String getBirthdate() {
-        return birthdate;
-    }
-
-    public void setBirthdate(String birthdate) {
-        this.birthdate = birthdate;
-    }
-
-    public String getZoneinfo() {
-        return zoneinfo;
-    }
-
-    public void setZoneinfo(String zoneinfo) {
-        this.zoneinfo = zoneinfo;
-    }
-
-    public String getLocale() {
-        return locale;
-    }
-
-    public void setLocale(String locale) {
-        this.locale = locale;
-    }
-
-    public String getPhoneNumber() {
-        return phoneNumber;
-    }
-
-    public void setPhoneNumber(String phoneNumber) {
-        this.phoneNumber = phoneNumber;
-    }
-
-    public Boolean getPhoneNumberVerified() {
-        return phoneNumberVerified;
-    }
-
-    public void setPhoneNumberVerified(Boolean phoneNumberVerified) {
-        this.phoneNumberVerified = phoneNumberVerified;
-    }
-
-    public String getAddress() {
-        return address;
-    }
-
-    public void setAddress(String address) {
-        this.address = address;
-    }
-
-    public Long getUpdatedAt() {
-        return updatedAt;
-    }
-
-    public void setUpdatedAt(Long updatedAt) {
-        this.updatedAt = updatedAt;
-    }
-
-    public String getFormattedAddress() {
-        return formattedAddress;
-    }
-
-    public void setFormattedAddress(String formattedAddress) {
-        this.formattedAddress = formattedAddress;
-    }
-
-    public String getStreetAddress() {
-        return streetAddress;
-    }
-
-    public void setStreetAddress(String streetAddress) {
-        this.streetAddress = streetAddress;
-    }
-
-    public String getLocality() {
-        return locality;
-    }
-
-    public void setLocality(String locality) {
-        this.locality = locality;
-    }
-
-    public String getRegion() {
-        return region;
-    }
-
-    public void setRegion(String region) {
-        this.region = region;
-    }
-
-    public String getPostalCode() {
-        return postalCode;
-    }
-
-    public void setPostalCode(String postalCode) {
-        this.postalCode = postalCode;
-    }
-
-    public String getCountry() {
-        return country;
-    }
-
-    public void setCountry(String country) {
-        this.country = country;
-    }
-
-    public String getClaimsLocales() {
-        return claimsLocales;
-    }
-
-    public void setClaimsLocales(String claimsLocales) {
-        this.claimsLocales = claimsLocales;
-    }
-
     public String getSessionState() {
         return sessionState;
     }
@@ -314,4 +33,12 @@ public class IDToken extends JsonWebToken {
     public void setSessionState(String sessionState) {
         this.sessionState = sessionState;
     }
+
+    public UserClaimSet getUserClaimSet() {
+        return this.userClaimSet;
+    }
+
+    public void setUserClaimSet(UserClaimSet userClaimSet) {
+        this.userClaimSet = userClaimSet;
+    }
 }
diff --git a/core/src/main/java/org/keycloak/representations/JsonWebToken.java b/core/src/main/java/org/keycloak/representations/JsonWebToken.java
index 42e9328..d0a4e9d 100755
--- a/core/src/main/java/org/keycloak/representations/JsonWebToken.java
+++ b/core/src/main/java/org/keycloak/representations/JsonWebToken.java
@@ -127,6 +127,10 @@ public class JsonWebToken implements Serializable {
         return this;
     }
 
+    public void setSubject(String subject) {
+        this.subject = subject;
+    }
+
     public String getType() {
         return type;
     }
diff --git a/core/src/main/java/org/keycloak/representations/UserClaimSet.java b/core/src/main/java/org/keycloak/representations/UserClaimSet.java
new file mode 100644
index 0000000..c647269
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/UserClaimSet.java
@@ -0,0 +1,331 @@
+/*
+ * JBoss, Home of Professional Open Source
+ *
+ * Copyright 2013 Red Hat, Inc. and/or its affiliates.
+ *
+ * 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.representations;
+
+import org.codehaus.jackson.annotate.JsonProperty;
+
+/**
+ * @author pedroigor
+ */
+public class UserClaimSet {
+
+    @JsonProperty("sub")
+    protected String sub;
+
+    @JsonProperty("name")
+    protected String name;
+
+    @JsonProperty("given_name")
+    protected String givenName;
+
+    @JsonProperty("family_name")
+    protected String familyName;
+
+    @JsonProperty("middle_name")
+    protected String middleName;
+
+    @JsonProperty("nickname")
+    protected String nickName;
+
+    @JsonProperty("preferred_username")
+    protected String preferredUsername;
+
+    @JsonProperty("profile")
+    protected String profile;
+
+    @JsonProperty("picture")
+    protected String picture;
+
+    @JsonProperty("website")
+    protected String website;
+
+    @JsonProperty("email")
+    protected String email;
+
+    @JsonProperty("email_verified")
+    protected Boolean emailVerified;
+
+    @JsonProperty("gender")
+    protected String gender;
+
+    @JsonProperty("birthdate")
+    protected String birthdate;
+
+    @JsonProperty("zoneinfo")
+    protected String zoneinfo;
+
+    @JsonProperty("locale")
+    protected String locale;
+
+    @JsonProperty("phone_number")
+    protected String phoneNumber;
+
+    @JsonProperty("phone_number_verified")
+    protected Boolean phoneNumberVerified;
+
+    @JsonProperty("address")
+    protected String address;
+
+    @JsonProperty("updated_at")
+    protected Long updatedAt;
+
+    @JsonProperty("formatted")
+    protected String formattedAddress;
+
+    @JsonProperty("street_address")
+    protected String streetAddress;
+
+    @JsonProperty("locality")
+    protected String locality;
+
+    @JsonProperty("region")
+    protected String region;
+
+    @JsonProperty("postal_code")
+    protected String postalCode;
+
+    @JsonProperty("country")
+    protected String country;
+
+    @JsonProperty("claims_locales")
+    protected String claimsLocales;
+
+    public String getSubject() {
+        return this.sub;
+    }
+
+    public void setSubject(String subject) {
+        this.sub = subject;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getGivenName() {
+        return this.givenName;
+    }
+
+    public void setGivenName(String givenName) {
+        this.givenName = givenName;
+    }
+
+    public String getFamilyName() {
+        return this.familyName;
+    }
+
+    public void setFamilyName(String familyName) {
+        this.familyName = familyName;
+    }
+
+    public String getMiddleName() {
+        return this.middleName;
+    }
+
+    public void setMiddleName(String middleName) {
+        this.middleName = middleName;
+    }
+
+    public String getNickName() {
+        return this.nickName;
+    }
+
+    public void setNickName(String nickName) {
+        this.nickName = nickName;
+    }
+
+    public String getPreferredUsername() {
+        return this.preferredUsername;
+    }
+
+    public void setPreferredUsername(String preferredUsername) {
+        this.preferredUsername = preferredUsername;
+    }
+
+    public String getProfile() {
+        return this.profile;
+    }
+
+    public void setProfile(String profile) {
+        this.profile = profile;
+    }
+
+    public String getPicture() {
+        return this.picture;
+    }
+
+    public void setPicture(String picture) {
+        this.picture = picture;
+    }
+
+    public String getWebsite() {
+        return this.website;
+    }
+
+    public void setWebsite(String website) {
+        this.website = website;
+    }
+
+    public String getEmail() {
+        return this.email;
+    }
+
+    public void setEmail(String email) {
+        this.email = email;
+    }
+
+    public Boolean getEmailVerified() {
+        return this.emailVerified;
+    }
+
+    public void setEmailVerified(Boolean emailVerified) {
+        this.emailVerified = emailVerified;
+    }
+
+    public String getGender() {
+        return this.gender;
+    }
+
+    public void setGender(String gender) {
+        this.gender = gender;
+    }
+
+    public String getBirthdate() {
+        return this.birthdate;
+    }
+
+    public void setBirthdate(String birthdate) {
+        this.birthdate = birthdate;
+    }
+
+    public String getZoneinfo() {
+        return this.zoneinfo;
+    }
+
+    public void setZoneinfo(String zoneinfo) {
+        this.zoneinfo = zoneinfo;
+    }
+
+    public String getLocale() {
+        return this.locale;
+    }
+
+    public void setLocale(String locale) {
+        this.locale = locale;
+    }
+
+    public String getPhoneNumber() {
+        return this.phoneNumber;
+    }
+
+    public void setPhoneNumber(String phoneNumber) {
+        this.phoneNumber = phoneNumber;
+    }
+
+    public Boolean getPhoneNumberVerified() {
+        return this.phoneNumberVerified;
+    }
+
+    public void setPhoneNumberVerified(Boolean phoneNumberVerified) {
+        this.phoneNumberVerified = phoneNumberVerified;
+    }
+
+    public String getAddress() {
+        return this.address;
+    }
+
+    public void setAddress(String address) {
+        this.address = address;
+    }
+
+    public Long getUpdatedAt() {
+        return this.updatedAt;
+    }
+
+    public void setUpdatedAt(Long updatedAt) {
+        this.updatedAt = updatedAt;
+    }
+
+    public String getSub() {
+        return this.sub;
+    }
+
+    public void setSub(String sub) {
+        this.sub = sub;
+    }
+
+    public String getFormattedAddress() {
+        return this.formattedAddress;
+    }
+
+    public void setFormattedAddress(String formattedAddress) {
+        this.formattedAddress = formattedAddress;
+    }
+
+    public String getStreetAddress() {
+        return this.streetAddress;
+    }
+
+    public void setStreetAddress(String streetAddress) {
+        this.streetAddress = streetAddress;
+    }
+
+    public String getLocality() {
+        return this.locality;
+    }
+
+    public void setLocality(String locality) {
+        this.locality = locality;
+    }
+
+    public String getRegion() {
+        return this.region;
+    }
+
+    public void setRegion(String region) {
+        this.region = region;
+    }
+
+    public String getPostalCode() {
+        return this.postalCode;
+    }
+
+    public void setPostalCode(String postalCode) {
+        this.postalCode = postalCode;
+    }
+
+    public String getCountry() {
+        return this.country;
+    }
+
+    public void setCountry(String country) {
+        this.country = country;
+    }
+
+    public String getClaimsLocales() {
+        return this.claimsLocales;
+    }
+
+    public void setClaimsLocales(String claimsLocales) {
+        this.claimsLocales = claimsLocales;
+    }
+}
diff --git a/core/src/main/java/org/keycloak/representations/UserInfo.java b/core/src/main/java/org/keycloak/representations/UserInfo.java
new file mode 100644
index 0000000..3112981
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/UserInfo.java
@@ -0,0 +1,25 @@
+/*
+ * JBoss, Home of Professional Open Source
+ *
+ * Copyright 2013 Red Hat, Inc. and/or its affiliates.
+ *
+ * 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.representations;
+
+/**
+ * @author pedroigor
+ */
+public class UserInfo extends UserClaimSet {
+
+}
diff --git a/core/src/test/java/org/keycloak/SkeletonKeyTokenTest.java b/core/src/test/java/org/keycloak/SkeletonKeyTokenTest.java
index f2ec502..e5a332d 100755
--- a/core/src/test/java/org/keycloak/SkeletonKeyTokenTest.java
+++ b/core/src/test/java/org/keycloak/SkeletonKeyTokenTest.java
@@ -6,6 +6,7 @@ import org.keycloak.jose.jws.JWSBuilder;
 import org.keycloak.jose.jws.JWSInput;
 import org.keycloak.jose.jws.crypto.RSAProvider;
 import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.UserClaimSet;
 import org.keycloak.representations.IDToken;
 import org.keycloak.util.JsonSerialization;
 
@@ -58,7 +59,9 @@ public class SkeletonKeyTokenTest {
     public void testSerialization() throws Exception {
         AccessToken token = createSimpleToken();
         IDToken idToken = new IDToken();
-        idToken.setEmail("joe@email.cz");
+        UserClaimSet claimSet = idToken.getUserClaimSet();
+
+        claimSet.setEmail("joe@email.cz");
 
         KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
 
@@ -95,7 +98,7 @@ public class SkeletonKeyTokenTest {
         Assert.assertEquals("111", token.getId());
         Assert.assertTrue(token.getResourceAccess("foo").isUserInRole("admin"));
         Assert.assertTrue(token.getResourceAccess("bar").isUserInRole("user"));
-        Assert.assertEquals("joe@email.cz", idToken.getEmail());
+        Assert.assertEquals("joe@email.cz", claimSet.getEmail());
         Assert.assertEquals("acme", ctx.getRealm());
         ois.close();
     }
diff --git a/events/api/src/main/java/org/keycloak/events/EventType.java b/events/api/src/main/java/org/keycloak/events/EventType.java
index 681c87d..d292c4c 100755
--- a/events/api/src/main/java/org/keycloak/events/EventType.java
+++ b/events/api/src/main/java/org/keycloak/events/EventType.java
@@ -45,6 +45,8 @@ public enum EventType {
 
     INVALID_SIGNATURE_ERROR,
     REGISTER_NODE,
-    UNREGISTER_NODE
+    UNREGISTER_NODE,
 
+    USER_INFO_REQUEST,
+    USER_INFO_REQUEST_ERROR
 }
diff --git a/examples/demo-template/customer-app/src/main/webapp/customers/view.jsp b/examples/demo-template/customer-app/src/main/webapp/customers/view.jsp
index 04a54bb..2eb5e66 100755
--- a/examples/demo-template/customer-app/src/main/webapp/customers/view.jsp
+++ b/examples/demo-template/customer-app/src/main/webapp/customers/view.jsp
@@ -3,6 +3,7 @@
 <%@ page import="org.keycloak.constants.ServiceUrlConstants" %>
 <%@ page import="org.keycloak.example.CustomerDatabaseClient" %>
 <%@ page import="org.keycloak.representations.IDToken" %>
+<%@ page import="org.keycloak.representations.UserClaimSet" %>
 <%@ page import="org.keycloak.util.KeycloakUriBuilder" %>
 <%@ page session="false" %>
 <html>
@@ -16,17 +17,18 @@
     String acctUri = KeycloakUriBuilder.fromUri("/auth").path(ServiceUrlConstants.ACCOUNT_SERVICE_PATH)
             .queryParam("referrer", "customer-portal").build("demo").toString();
     IDToken idToken = CustomerDatabaseClient.getIDToken(request);
+    UserClaimSet claims = idToken.getUserClaimSet();
 %>
 <p>Goto: <a href="/product-portal">products</a> | <a href="<%=logoutUri%>">logout</a> | <a
         href="<%=acctUri%>">manage acct</a></p>
 Servlet User Principal <b><%=request.getUserPrincipal().getName()%>
 </b> made this request.
 <p><b>Caller IDToken values</b> (<i>You can specify what is returned in IDToken in the customer-portal claims page in the admin console</i>:</p>
-<p>Username: <%=idToken.getPreferredUsername()%></p>
-<p>Email: <%=idToken.getEmail()%></p>
-<p>Full Name: <%=idToken.getName()%></p>
-<p>First: <%=idToken.getGivenName()%></p>
-<p>Last: <%=idToken.getFamilyName()%></p>
+<p>Username: <%=claims.getPreferredUsername()%></p>
+<p>Email: <%=claims.getEmail()%></p>
+<p>Full Name: <%=claims.getName()%></p>
+<p>First: <%=claims.getGivenName()%></p>
+<p>Last: <%=claims.getFamilyName()%></p>
 <h2>Customer Listing</h2>
 <%
     java.util.List<String> list = null;
diff --git a/examples/demo-template/third-party/src/main/webapp/pull_data.jsp b/examples/demo-template/third-party/src/main/webapp/pull_data.jsp
index 9f102b4..6ed0478 100755
--- a/examples/demo-template/third-party/src/main/webapp/pull_data.jsp
+++ b/examples/demo-template/third-party/src/main/webapp/pull_data.jsp
@@ -2,6 +2,7 @@
 <%@ page import="org.keycloak.representations.AccessTokenResponse" %>
 <%@ page import="org.keycloak.representations.IDToken" %>
 <%@ page import="org.keycloak.servlet.ServletOAuthClient" %>
+<%@ page import="org.keycloak.representations.UserClaimSet" %>
 <%@ page language="java" contentType="text/html; charset=ISO-8859-1"
  pageEncoding="ISO-8859-1"%>
 <%@ page session="false" %>
@@ -16,15 +17,16 @@
         AccessTokenResponse tokenResponse = ProductDatabaseClient.getTokenResponse(request);
         if (tokenResponse.getIdToken() != null) {
             IDToken idToken = ServletOAuthClient.extractIdToken(tokenResponse.getIdToken());
+            UserClaimSet claimSet = idToken.getUserClaimSet();
             out.println("<p><i>Change client claims in admin console to view personal info of user</i></p>");
-            if (idToken.getPreferredUsername() != null) {
-                out.println("<p>Username: " + idToken.getPreferredUsername() + "</p>");
+            if (claimSet.getPreferredUsername() != null) {
+                out.println("<p>Username: " + claimSet.getPreferredUsername() + "</p>");
             }
-            if (idToken.getName() != null) {
-                out.println("<p>Full Name: " + idToken.getName() + "</p>");
+            if (claimSet.getName() != null) {
+                out.println("<p>Full Name: " + claimSet.getName() + "</p>");
             }
-            if (idToken.getEmail() != null) {
-                out.println("<p>Email: " + idToken.getEmail() + "</p>");
+            if (claimSet.getEmail() != null) {
+                out.println("<p>Email: " + claimSet.getEmail() + "</p>");
             }
         }
         list = ProductDatabaseClient.getProducts(request, tokenResponse.getToken());
diff --git a/examples/multi-tenant/src/main/java/org/keycloak/example/multitenant/boundary/ProtectedServlet.java b/examples/multi-tenant/src/main/java/org/keycloak/example/multitenant/boundary/ProtectedServlet.java
index 991169d..ad41327 100644
--- a/examples/multi-tenant/src/main/java/org/keycloak/example/multitenant/boundary/ProtectedServlet.java
+++ b/examples/multi-tenant/src/main/java/org/keycloak/example/multitenant/boundary/ProtectedServlet.java
@@ -16,14 +16,15 @@
  */
 package org.keycloak.example.multitenant.boundary;
 
-import java.io.IOException;
-import java.io.PrintWriter;
+import org.keycloak.KeycloakPrincipal;
+
 import javax.servlet.ServletException;
 import javax.servlet.annotation.WebServlet;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import org.keycloak.KeycloakPrincipal;
+import java.io.IOException;
+import java.io.PrintWriter;
 
 /**
  *
@@ -54,7 +55,7 @@ public class ProtectedServlet extends HttpServlet {
         writer.write(principal.getKeycloakSecurityContext().getIdToken().getIssuer());
 
         writer.write("<br/>User: ");
-        writer.write(principal.getKeycloakSecurityContext().getIdToken().getPreferredUsername());
+        writer.write(principal.getKeycloakSecurityContext().getIdToken().getUserClaimSet().getPreferredUsername());
 
         writer.write(String.format("<br/><a href=\"/multitenant/%s/logout\">Logout</a>", realm));
     }
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterUtils.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterUtils.java
index 632c66a..371696b 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterUtils.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterUtils.java
@@ -1,14 +1,15 @@
 package org.keycloak.adapters;
 
-import java.util.Collections;
-import java.util.Set;
-
 import org.jboss.logging.Logger;
 import org.keycloak.KeycloakPrincipal;
 import org.keycloak.KeycloakSecurityContext;
 import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.UserClaimSet;
 import org.keycloak.util.UriUtils;
 
+import java.util.Collections;
+import java.util.Set;
+
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  */
@@ -77,20 +78,22 @@ public class AdapterUtils {
         String attr = "sub";
         if (deployment.getPrincipalAttribute() != null) attr = deployment.getPrincipalAttribute();
         String name = null;
+        UserClaimSet claimSet = token.getUserClaimSet();
+
         if ("sub".equals(attr)) {
             name = token.getSubject();
         } else if ("email".equals(attr)) {
-            name = token.getEmail();
+            name = claimSet.getEmail();
         } else if ("preferred_username".equals(attr)) {
-            name = token.getPreferredUsername();
+            name = claimSet.getPreferredUsername();
         } else if ("name".equals(attr)) {
-            name = token.getName();
+            name = claimSet.getName();
         } else if ("given_name".equals(attr)) {
-            name = token.getGivenName();
+            name = claimSet.getGivenName();
         } else if ("family_name".equals(attr)) {
-            name = token.getFamilyName();
+            name = claimSet.getFamilyName();
         } else if ("nickname".equals(attr)) {
-            name = token.getNickName();
+            name = claimSet.getNickName();
         }
         if (name == null) name = token.getSubject();
         return name;
diff --git a/integration/js/src/main/resources/keycloak.js b/integration/js/src/main/resources/keycloak.js
index 697f118..5161cb1 100755
--- a/integration/js/src/main/resources/keycloak.js
+++ b/integration/js/src/main/resources/keycloak.js
@@ -204,6 +204,31 @@
             return promise.promise;
         }
 
+        kc.loadUserInfo = function() {
+            var url = getRealmUrl() + '/protocol/openid-connect/userinfo';
+            var req = new XMLHttpRequest();
+            req.open('GET', url, true);
+            req.setRequestHeader('Accept', 'application/json');
+            req.setRequestHeader('Authorization', 'bearer ' + kc.token);
+
+            var promise = createPromise();
+
+            req.onreadystatechange = function () {
+                if (req.readyState == 4) {
+                    if (req.status == 200) {
+                        kc.userInfo = JSON.parse(req.responseText);
+                        promise.setSuccess(kc.userInfo);
+                    } else {
+                        promise.setError();
+                    }
+                }
+            }
+
+            req.send();
+
+            return promise.promise;
+        }
+
         kc.isTokenExpired = function(minValidity) {
             if (!kc.tokenParsed || !kc.refreshToken) {
                 throw 'Not authenticated';
diff --git a/proxy/proxy-server/src/main/java/org/keycloak/proxy/ConstraintAuthorizationHandler.java b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ConstraintAuthorizationHandler.java
index 9889d51..c497565 100755
--- a/proxy/proxy-server/src/main/java/org/keycloak/proxy/ConstraintAuthorizationHandler.java
+++ b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ConstraintAuthorizationHandler.java
@@ -4,10 +4,9 @@ import io.undertow.server.HttpHandler;
 import io.undertow.server.HttpServerExchange;
 import io.undertow.util.HttpString;
 import org.keycloak.adapters.undertow.KeycloakUndertowAccount;
+import org.keycloak.representations.UserClaimSet;
 import org.keycloak.representations.IDToken;
 
-import java.util.Collection;
-
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
@@ -65,14 +64,17 @@ public class ConstraintAuthorizationHandler implements HttpHandler {
             if (idToken.getSubject() != null) {
                 exchange.getRequestHeaders().put(KEYCLOAK_SUBJECT, idToken.getSubject());
             }
-            if (idToken.getPreferredUsername() != null) {
-                exchange.getRequestHeaders().put(KEYCLOAK_USERNAME, idToken.getPreferredUsername());
+
+            UserClaimSet claimSet = idToken.getUserClaimSet();
+
+            if (claimSet.getPreferredUsername() != null) {
+                exchange.getRequestHeaders().put(KEYCLOAK_USERNAME, claimSet.getPreferredUsername());
             }
-            if (idToken.getEmail() != null) {
-                exchange.getRequestHeaders().put(KEYCLOAK_EMAIL, idToken.getEmail());
+            if (claimSet.getEmail() != null) {
+                exchange.getRequestHeaders().put(KEYCLOAK_EMAIL, claimSet.getEmail());
             }
-            if (idToken.getName() != null) {
-                exchange.getRequestHeaders().put(KEYCLOAK_NAME, idToken.getName());
+            if (claimSet.getName() != null) {
+                exchange.getRequestHeaders().put(KEYCLOAK_NAME, claimSet.getName());
             }
             if (sendAccessToken) {
                 exchange.getRequestHeaders().put(KEYCLOAK_ACCESS_TOKEN, account.getKeycloakSecurityContext().getTokenString());
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectService.java b/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectService.java
index b3129c6..9b9f8b9 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectService.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectService.java
@@ -8,6 +8,7 @@ import org.jboss.resteasy.spi.HttpRequest;
 import org.jboss.resteasy.spi.HttpResponse;
 import org.jboss.resteasy.spi.NotAcceptableException;
 import org.jboss.resteasy.spi.NotFoundException;
+import org.jboss.resteasy.spi.ResteasyProviderFactory;
 import org.jboss.resteasy.spi.UnauthorizedException;
 import org.keycloak.ClientConnection;
 import org.keycloak.Config;
@@ -683,6 +684,15 @@ public class OpenIDConnectService {
         return Cors.add(request, Response.ok(res)).auth().allowedOrigins(client).allowedMethods("POST").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build();
     }
 
+    @Path("userinfo")
+    public Object issueUserInfo() {
+        UserInfoService userInfoEndpoint = new UserInfoService(this);
+
+        ResteasyProviderFactory.getInstance().injectProperties(userInfoEndpoint);
+
+        return userInfoEndpoint;
+    }
+
     protected ClientModel authorizeClient(String authorizationHeader, MultivaluedMap<String, String> formData, EventBuilder event) {
         ClientModel client = authorizeClientBase(authorizationHeader, formData, event, realm);
 
@@ -1150,4 +1160,11 @@ public class OpenIDConnectService {
         return Response.status(status).entity(e).type("application/json").build();
     }
 
+    TokenManager getTokenManager() {
+        return this.tokenManager;
+    }
+
+    RealmModel getRealm() {
+        return this.realm;
+    }
 }
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
index 7dad225..a72aa1c 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
@@ -20,6 +20,7 @@ import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.AccessTokenResponse;
+import org.keycloak.representations.UserClaimSet;
 import org.keycloak.representations.IDToken;
 import org.keycloak.representations.RefreshToken;
 import org.keycloak.services.managers.AuthenticationManager;
@@ -203,21 +204,23 @@ public class TokenManager {
         }
     }
 
-    public void initClaims(IDToken token, ClientModel model, UserModel user) {
+    public void initClaims(UserClaimSet claimSet, ClientModel model, UserModel user) {
+        claimSet.setSubject(user.getId());
+
         if (ClaimMask.hasUsername(model.getAllowedClaimsMask())) {
-            token.setPreferredUsername(user.getUsername());
+            claimSet.setPreferredUsername(user.getUsername());
         }
         if (ClaimMask.hasEmail(model.getAllowedClaimsMask())) {
-            token.setEmail(user.getEmail());
-            token.setEmailVerified(user.isEmailVerified());
+            claimSet.setEmail(user.getEmail());
+            claimSet.setEmailVerified(user.isEmailVerified());
         }
         if (ClaimMask.hasName(model.getAllowedClaimsMask())) {
-            token.setFamilyName(user.getLastName());
-            token.setGivenName(user.getFirstName());
+            claimSet.setFamilyName(user.getLastName());
+            claimSet.setGivenName(user.getFirstName());
             StringBuilder fullName = new StringBuilder();
             if (user.getFirstName() != null) fullName.append(user.getFirstName()).append(" ");
             if (user.getLastName() != null) fullName.append(user.getLastName());
-            token.setName(fullName.toString());
+            claimSet.setName(fullName.toString());
         }
     }
 
@@ -232,7 +235,8 @@ public class TokenManager {
         if (realm.getAccessTokenLifespan() > 0) {
             token.expiration(Time.currentTime() + realm.getAccessTokenLifespan());
         }
-        initClaims(token, claimer, user);
+        UserClaimSet claimSet = token.getUserClaimSet();
+        initClaims(claimSet, claimer, user);
         return token;
     }
 
@@ -256,7 +260,8 @@ public class TokenManager {
         if (allowedOrigins != null) {
             token.setAllowedOrigins(allowedOrigins);
         }
-        initClaims(token, client, user);
+        UserClaimSet claimSet = token.getUserClaimSet();
+        initClaims(claimSet, client, user);
         return token;
     }
 
@@ -353,30 +358,30 @@ public class TokenManager {
             if (realm.getAccessTokenLifespan() > 0) {
                 idToken.expiration(Time.currentTime() + realm.getAccessTokenLifespan());
             }
-            idToken.setPreferredUsername(accessToken.getPreferredUsername());
-            idToken.setGivenName(accessToken.getGivenName());
-            idToken.setMiddleName(accessToken.getMiddleName());
-            idToken.setFamilyName(accessToken.getFamilyName());
-            idToken.setName(accessToken.getName());
-            idToken.setNickName(accessToken.getNickName());
-            idToken.setGender(accessToken.getGender());
-            idToken.setPicture(accessToken.getPicture());
-            idToken.setProfile(accessToken.getProfile());
-            idToken.setWebsite(accessToken.getWebsite());
-            idToken.setBirthdate(accessToken.getBirthdate());
-            idToken.setEmail(accessToken.getEmail());
-            idToken.setEmailVerified(accessToken.getEmailVerified());
-            idToken.setLocale(accessToken.getLocale());
-            idToken.setFormattedAddress(accessToken.getFormattedAddress());
-            idToken.setAddress(accessToken.getAddress());
-            idToken.setStreetAddress(accessToken.getStreetAddress());
-            idToken.setLocality(accessToken.getLocality());
-            idToken.setRegion(accessToken.getRegion());
-            idToken.setPostalCode(accessToken.getPostalCode());
-            idToken.setCountry(accessToken.getCountry());
-            idToken.setPhoneNumber(accessToken.getPhoneNumber());
-            idToken.setPhoneNumberVerified(accessToken.getPhoneNumberVerified());
-            idToken.setZoneinfo(accessToken.getZoneinfo());
+            idToken.getUserClaimSet().setPreferredUsername(accessToken.getUserClaimSet().getPreferredUsername());
+            idToken.getUserClaimSet().setGivenName(accessToken.getUserClaimSet().getGivenName());
+            idToken.getUserClaimSet().setMiddleName(accessToken.getUserClaimSet().getMiddleName());
+            idToken.getUserClaimSet().setFamilyName(accessToken.getUserClaimSet().getFamilyName());
+            idToken.getUserClaimSet().setName(accessToken.getUserClaimSet().getName());
+            idToken.getUserClaimSet().setNickName(accessToken.getUserClaimSet().getNickName());
+            idToken.getUserClaimSet().setGender(accessToken.getUserClaimSet().getGender());
+            idToken.getUserClaimSet().setPicture(accessToken.getUserClaimSet().getPicture());
+            idToken.getUserClaimSet().setProfile(accessToken.getUserClaimSet().getProfile());
+            idToken.getUserClaimSet().setWebsite(accessToken.getUserClaimSet().getWebsite());
+            idToken.getUserClaimSet().setBirthdate(accessToken.getUserClaimSet().getBirthdate());
+            idToken.getUserClaimSet().setEmail(accessToken.getUserClaimSet().getEmail());
+            idToken.getUserClaimSet().setEmailVerified(accessToken.getUserClaimSet().getEmailVerified());
+            idToken.getUserClaimSet().setLocale(accessToken.getUserClaimSet().getLocale());
+            idToken.getUserClaimSet().setFormattedAddress(accessToken.getUserClaimSet().getFormattedAddress());
+            idToken.getUserClaimSet().setAddress(accessToken.getUserClaimSet().getAddress());
+            idToken.getUserClaimSet().setStreetAddress(accessToken.getUserClaimSet().getStreetAddress());
+            idToken.getUserClaimSet().setLocality(accessToken.getUserClaimSet().getLocality());
+            idToken.getUserClaimSet().setRegion(accessToken.getUserClaimSet().getRegion());
+            idToken.getUserClaimSet().setPostalCode(accessToken.getUserClaimSet().getPostalCode());
+            idToken.getUserClaimSet().setCountry(accessToken.getUserClaimSet().getCountry());
+            idToken.getUserClaimSet().setPhoneNumber(accessToken.getUserClaimSet().getPhoneNumber());
+            idToken.getUserClaimSet().setPhoneNumberVerified(accessToken.getUserClaimSet().getPhoneNumberVerified());
+            idToken.getUserClaimSet().setZoneinfo(accessToken.getUserClaimSet().getZoneinfo());
             return this;
         }
 
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/UserInfoService.java b/services/src/main/java/org/keycloak/protocol/oidc/UserInfoService.java
new file mode 100644
index 0000000..5e69d05
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/UserInfoService.java
@@ -0,0 +1,150 @@
+/*
+ * JBoss, Home of Professional Open Source
+ *
+ * Copyright 2013 Red Hat, Inc. and/or its affiliates.
+ *
+ * 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.protocol.oidc;
+
+import org.jboss.resteasy.annotations.cache.NoCache;
+import org.jboss.resteasy.spi.HttpRequest;
+import org.jboss.resteasy.spi.HttpResponse;
+import org.jboss.resteasy.spi.UnauthorizedException;
+import org.keycloak.ClientConnection;
+import org.keycloak.events.Details;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.events.EventType;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.UserClaimSet;
+import org.keycloak.services.managers.AppAuthManager;
+import org.keycloak.services.managers.EventsManager;
+import org.keycloak.services.resources.Cors;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.GET;
+import javax.ws.rs.OPTIONS;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+/**
+ * @author pedroigor
+ */
+public class UserInfoService {
+
+    @Context
+    private HttpRequest request;
+
+    @Context
+    private HttpResponse response;
+
+    @Context
+    private KeycloakSession session;
+
+    @Context
+    private ClientConnection clientConnection;
+
+    private final TokenManager tokenManager;
+    private final AppAuthManager appAuthManager;
+    private final OpenIDConnectService openIdConnectService;
+    private final RealmModel realmModel;
+
+    public UserInfoService(OpenIDConnectService openIDConnectService) {
+        this.realmModel = openIDConnectService.getRealm();
+
+        if (this.realmModel == null) {
+            throw new RuntimeException("Null realm.");
+        }
+
+        this.tokenManager = openIDConnectService.getTokenManager();
+
+        if (this.tokenManager == null) {
+            throw new RuntimeException("Null token manager.");
+        }
+
+        this.openIdConnectService = openIDConnectService;
+        this.appAuthManager = new AppAuthManager();
+    }
+
+    @Path("/")
+    @OPTIONS
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response issueUserInfoPreflight() {
+        return Cors.add(this.request, Response.ok()).auth().preflight().build();
+    }
+
+    @Path("/")
+    @GET
+    @NoCache
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response issueUserInfoGet(@Context final HttpHeaders headers) {
+        String accessToken = this.appAuthManager.extractAuthorizationHeaderToken(headers);
+        return issueUserInfo(accessToken);
+    }
+
+    @Path("/")
+    @POST
+    @NoCache
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response issueUserInfoPost(@FormParam("access_token") String accessToken) {
+        return issueUserInfo(accessToken);
+    }
+
+    private Response issueUserInfo(String token) {
+        try {
+            EventBuilder event = new EventsManager(this.realmModel, this.session, this.clientConnection).createEventBuilder()
+                    .event(EventType.USER_INFO_REQUEST)
+                    .detail(Details.AUTH_METHOD, Details.VALIDATE_ACCESS_TOKEN);
+
+            Response validationResponse = this.openIdConnectService.validateAccessToken(token);
+
+            if (!AccessToken.class.isInstance(validationResponse.getEntity())) {
+                event.error(EventType.USER_INFO_REQUEST.name());
+                return Response.fromResponse(validationResponse).status(Status.FORBIDDEN).build();
+            }
+
+            AccessToken accessToken = (AccessToken) validationResponse.getEntity();
+            UserSessionModel userSession = session.sessions().getUserSession(realmModel, accessToken.getSessionState());
+            ClientModel clientModel = realmModel.findClient(accessToken.getIssuedFor());
+            UserModel userModel = userSession.getUser();
+            UserClaimSet userInfo = new UserClaimSet();
+
+            this.tokenManager.initClaims(userInfo, clientModel, userModel);
+
+            event
+                .detail(Details.USERNAME, userModel.getUsername())
+                .client(clientModel)
+                .session(userSession)
+                .user(userModel)
+                .success();
+
+            return Cors.add(request, Response.ok(userInfo)).auth().allowedOrigins(accessToken).build();
+        } catch (Exception e) {
+            throw new UnauthorizedException("Could not retrieve user info.", e);
+        }
+    }
+
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/MultiTenantServlet.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/MultiTenantServlet.java
index 5e04cf2..1501f04 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/MultiTenantServlet.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/MultiTenantServlet.java
@@ -16,13 +16,14 @@
  */
 package org.keycloak.testsuite.adapter;
 
-import java.io.IOException;
-import java.io.PrintWriter;
+import org.keycloak.KeycloakSecurityContext;
+
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import org.keycloak.KeycloakSecurityContext;
+import java.io.IOException;
+import java.io.PrintWriter;
 
 /**
  *
@@ -37,7 +38,7 @@ public class MultiTenantServlet extends HttpServlet {
         KeycloakSecurityContext context = (KeycloakSecurityContext)req.getAttribute(KeycloakSecurityContext.class.getName());
 
         pw.print("Username: ");
-        pw.println(context.getIdToken().getPreferredUsername());
+        pw.println(context.getIdToken().getUserClaimSet().getPreferredUsername());
 
         pw.print("<br/>Realm: ");
         pw.println(context.getRealm());
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oidc/UserInfoTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oidc/UserInfoTest.java
new file mode 100755
index 0000000..2828f8d
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oidc/UserInfoTest.java
@@ -0,0 +1,129 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.testsuite.oidc;
+
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.keycloak.models.RealmModel;
+import org.keycloak.protocol.oidc.OpenIDConnectService;
+import org.keycloak.representations.AccessTokenResponse;
+import org.keycloak.representations.UserInfo;
+import org.keycloak.testsuite.rule.KeycloakRule;
+import org.keycloak.testsuite.rule.WebResource;
+import org.keycloak.testsuite.rule.WebRule;
+import org.keycloak.util.BasicAuthHelper;
+import org.openqa.selenium.WebDriver;
+
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Form;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.UriBuilder;
+import java.net.URI;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+/**
+ * @author pedroigor
+ */
+public class UserInfoTest {
+
+    private static RealmModel realm;
+
+    @ClassRule
+    public static KeycloakRule keycloakRule = new KeycloakRule();
+
+    @Rule
+    public WebRule webRule = new WebRule(this);
+
+    @WebResource
+    protected WebDriver driver;
+
+    @Test
+    public void testSuccessfulUserInfoRequest() throws Exception {
+        Client client = ClientBuilder.newClient();
+        UriBuilder builder = UriBuilder.fromUri(org.keycloak.testsuite.Constants.AUTH_SERVER_ROOT);
+        URI grantUri = OpenIDConnectService.grantAccessTokenUrl(builder).build("test");
+        WebTarget grantTarget = client.target(grantUri);
+        AccessTokenResponse accessTokenResponse = executeGrantAccessTokenRequest(grantTarget);
+        Response response = executeUserInfoRequest(accessTokenResponse.getToken());
+
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+
+        UserInfo userInfo = response.readEntity(UserInfo.class);
+
+        response.close();
+
+        assertNotNull(userInfo);
+        assertNotNull(userInfo.getSubject());
+        assertEquals("test-user@localhost", userInfo.getEmail());
+        assertEquals("test-user@localhost", userInfo.getPreferredUsername());
+
+        client.close();
+    }
+
+    @Test
+    public void testUnsuccessfulUserInfoRequest() throws Exception {
+        Response response = executeUserInfoRequest("bad");
+
+        response.close();
+
+        assertEquals(Status.FORBIDDEN.getStatusCode(), response.getStatus());
+    }
+
+    private AccessTokenResponse executeGrantAccessTokenRequest(WebTarget grantTarget) {
+        String header = BasicAuthHelper.createHeader("test-app", "password");
+        Form form = new Form();
+        form.param("username", "test-user@localhost")
+                .param("password", "password");
+
+        Response response = grantTarget.request()
+                .header(HttpHeaders.AUTHORIZATION, header)
+                .post(Entity.form(form));
+
+        assertEquals(200, response.getStatus());
+
+        AccessTokenResponse accessTokenResponse = response.readEntity(AccessTokenResponse.class);
+
+        response.close();
+
+        return accessTokenResponse;
+    }
+
+    private Response executeUserInfoRequest(String accessToken) {
+        UriBuilder builder = UriBuilder.fromUri(org.keycloak.testsuite.Constants.AUTH_SERVER_ROOT);
+        UriBuilder uriBuilder = OpenIDConnectService.tokenServiceBaseUrl(builder);
+        URI userInfoUri = uriBuilder.path(OpenIDConnectService.class, "issueUserInfo").build("test");
+        Client client = ClientBuilder.newClient();
+        WebTarget userInfoTarget = client.target(userInfoUri);
+
+        return userInfoTarget.request()
+                .header(HttpHeaders.AUTHORIZATION, "bearer " + accessToken)
+                .get();
+    }
+}