You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@oltu.apache.org by si...@apache.org on 2013/09/17 14:37:29 UTC

svn commit: r1524010 - in /oltu/trunk/oauth-2.0/jwt/src: main/java/org/apache/oltu/oauth2/jwt/ test/java/org/apache/oltu/oauth2/jwt/

Author: simonetripodi
Date: Tue Sep 17 12:37:28 2013
New Revision: 1524010

URL: http://svn.apache.org/r1524010
Log:
OLTU-114 - JWT header/claimsset don't support custom fields
OLTU-115 - JWTUtils#parse() method does not support multi-lines token
OLTU-116 - JWTUtils does not have a method to serialize a JWT entity to a token

Added:
    oltu/trunk/oauth-2.0/jwt/src/main/java/org/apache/oltu/oauth2/jwt/JWTEntity.java   (with props)
Modified:
    oltu/trunk/oauth-2.0/jwt/src/main/java/org/apache/oltu/oauth2/jwt/ClaimsSet.java
    oltu/trunk/oauth-2.0/jwt/src/main/java/org/apache/oltu/oauth2/jwt/Header.java
    oltu/trunk/oauth-2.0/jwt/src/main/java/org/apache/oltu/oauth2/jwt/JWT.java
    oltu/trunk/oauth-2.0/jwt/src/main/java/org/apache/oltu/oauth2/jwt/JWTUtils.java
    oltu/trunk/oauth-2.0/jwt/src/test/java/org/apache/oltu/oauth2/jwt/JWTUtilsTest.java

Modified: oltu/trunk/oauth-2.0/jwt/src/main/java/org/apache/oltu/oauth2/jwt/ClaimsSet.java
URL: http://svn.apache.org/viewvc/oltu/trunk/oauth-2.0/jwt/src/main/java/org/apache/oltu/oauth2/jwt/ClaimsSet.java?rev=1524010&r1=1524009&r2=1524010&view=diff
==============================================================================
--- oltu/trunk/oauth-2.0/jwt/src/main/java/org/apache/oltu/oauth2/jwt/ClaimsSet.java (original)
+++ oltu/trunk/oauth-2.0/jwt/src/main/java/org/apache/oltu/oauth2/jwt/ClaimsSet.java Tue Sep 17 12:37:28 2013
@@ -18,12 +18,14 @@ package org.apache.oltu.oauth2.jwt;
 
 import static java.lang.String.format;
 
+import java.util.Map;
+
 /**
  * Represents the Claims Set as defined in the 6.1 section of the JWT specification.
  *
  * @see http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-06#section-6.1
  */
-public final class ClaimsSet {
+public final class ClaimsSet extends JWTEntity {
 
     /**
      * The {@code iss} JWT Claims Set parameter.
@@ -72,7 +74,9 @@ public final class ClaimsSet {
               String notBefore,
               long issuedAt,
               String jwdId,
-              String type) {
+              String type,
+              Map<String, Object> customFields) {
+        super(customFields);
         this.issuer = issuer;
         this.subject = subject;
         this.audience = audience;
@@ -157,8 +161,8 @@ public final class ClaimsSet {
 
     @Override
     public String toString() {
-        return format("{\"iss\": \"%s\", \"sub\": \"%s\", \"aud\": \"%s\", \"exp\": %s, \"nbf\": \"%s\", \"iat\": %s, \"jti\": \"%s\", \"typ\": \"%s\" }",
-                      issuer, subject, audience, expirationTime, notBefore, issuedAt, jwdId, type);
+        return format("{\"iss\": \"%s\", \"sub\": \"%s\", \"aud\": \"%s\", \"exp\": %s, \"nbf\": \"%s\", \"iat\": %s, \"jti\": \"%s\", \"typ\": \"%s\", %s }",
+                      issuer, subject, audience, expirationTime, notBefore, issuedAt, jwdId, type, super.toString());
     }
 
 }

Modified: oltu/trunk/oauth-2.0/jwt/src/main/java/org/apache/oltu/oauth2/jwt/Header.java
URL: http://svn.apache.org/viewvc/oltu/trunk/oauth-2.0/jwt/src/main/java/org/apache/oltu/oauth2/jwt/Header.java?rev=1524010&r1=1524009&r2=1524010&view=diff
==============================================================================
--- oltu/trunk/oauth-2.0/jwt/src/main/java/org/apache/oltu/oauth2/jwt/Header.java (original)
+++ oltu/trunk/oauth-2.0/jwt/src/main/java/org/apache/oltu/oauth2/jwt/Header.java Tue Sep 17 12:37:28 2013
@@ -18,12 +18,14 @@ package org.apache.oltu.oauth2.jwt;
 
 import static java.lang.String.format;
 
+import java.util.Map;
+
 /**
  * Represents the Header as defined in the 6.1 section of the JWT specification.
  *
  * @see http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-06#section-6.1
  */
-public final class Header {
+public final class Header extends JWTEntity {
 
     /**
      * The {@code typ} JWT Header parameter.
@@ -40,7 +42,11 @@ public final class Header {
      */
     private final String contentType;
 
-    Header(String type, String algorithm, String contentType) {
+    Header(String type,
+           String algorithm,
+           String contentType,
+           Map<String, Object> customFields) {
+        super(customFields);
         this.type = type;
         this.algorithm = algorithm;
         this.contentType = contentType;
@@ -75,7 +81,7 @@ public final class Header {
 
     @Override
     public String toString() {
-        return format("{\"typ\": \"%s\", \"alg\": \"%s\", \"cty\": \"%s\"}", type, algorithm, contentType);
+        return format("{\"typ\": \"%s\", \"alg\": \"%s\", \"cty\": \"%s\", %s}", type, algorithm, contentType, super.toString());
     }
 
 }

Modified: oltu/trunk/oauth-2.0/jwt/src/main/java/org/apache/oltu/oauth2/jwt/JWT.java
URL: http://svn.apache.org/viewvc/oltu/trunk/oauth-2.0/jwt/src/main/java/org/apache/oltu/oauth2/jwt/JWT.java?rev=1524010&r1=1524009&r2=1524010&view=diff
==============================================================================
--- oltu/trunk/oauth-2.0/jwt/src/main/java/org/apache/oltu/oauth2/jwt/JWT.java (original)
+++ oltu/trunk/oauth-2.0/jwt/src/main/java/org/apache/oltu/oauth2/jwt/JWT.java Tue Sep 17 12:37:28 2013
@@ -18,6 +18,9 @@ package org.apache.oltu.oauth2.jwt;
 
 import static java.lang.String.format;
 
+import java.util.LinkedHashMap;
+import java.util.Map;
+
 /**
  * This class contains constants used in the JWT implementation.
  *
@@ -121,6 +124,11 @@ public class JWT {
         private String headerContentType;
 
         /**
+         * The JWT Header custom fields.
+         */
+        private final Map<String, Object> headerCustomFields = new LinkedHashMap<String, Object>();
+
+        /**
          * The {@code iss} JWT Claims Set parameter.
          */
         private String claimsSetIssuer;
@@ -161,6 +169,11 @@ public class JWT {
         private String claimsSetType;
 
         /**
+         * The JWT Header custom fields.
+         */
+        private final Map<String, Object> claimsSetCustomFields = new LinkedHashMap<String, Object>();
+
+        /**
          * The JWT Signature.
          */
         private String signature;
@@ -207,6 +220,23 @@ public class JWT {
         }
 
         /**
+         * Set the JWT Header custom field.
+         *
+         * @param key the custom field name.
+         * @param value value the custom field value.
+         * @return this builder instance.
+         */
+        public Builder setHeaderCustomField(String key, String value) {
+            if (key == null) {
+                throw new IllegalArgumentException("Null key not allowed");
+            }
+            if (value != null) {
+                headerCustomFields.put(key, value);
+            }
+            return this;
+        }
+
+        /**
          * Sets the JWT Claims Set {@code iss}.
          *
          * @param claimsSetIssuer the JWT Claims Set {@code iss}.
@@ -295,6 +325,23 @@ public class JWT {
         }
 
         /**
+         * Set the JWT Claims Set custom field.
+         *
+         * @param key the custom field name.
+         * @param value value the custom field value.
+         * @return this builder instance.
+         */
+        public Builder setClaimsSetCustomField(String key, String value) {
+            if (key == null) {
+                throw new IllegalArgumentException("Null key not allowed");
+            }
+            if (value != null) {
+                claimsSetCustomFields.put(key, value);
+            }
+            return this;
+        }
+
+        /**
          * Sets the JWT signature.
          *
          * @param signature
@@ -312,7 +359,7 @@ public class JWT {
          */
         public JWT build() {
             return new JWT(rawString,
-                           new Header(headerType, headerAlgorithm, headerContentType),
+                           new Header(headerType, headerAlgorithm, headerContentType, headerCustomFields),
                            new ClaimsSet(claimsSetIssuer,
                                          claimsSetSubject,
                                          claimsSetAudience,
@@ -320,7 +367,8 @@ public class JWT {
                                          claimsSetNotBefore,
                                          claimsSetIssuedAt,
                                          claimsSetJwdId,
-                                         claimsSetType),
+                                         claimsSetType,
+                                         claimsSetCustomFields),
                            signature);
         }
 

Added: oltu/trunk/oauth-2.0/jwt/src/main/java/org/apache/oltu/oauth2/jwt/JWTEntity.java
URL: http://svn.apache.org/viewvc/oltu/trunk/oauth-2.0/jwt/src/main/java/org/apache/oltu/oauth2/jwt/JWTEntity.java?rev=1524010&view=auto
==============================================================================
--- oltu/trunk/oauth-2.0/jwt/src/main/java/org/apache/oltu/oauth2/jwt/JWTEntity.java (added)
+++ oltu/trunk/oauth-2.0/jwt/src/main/java/org/apache/oltu/oauth2/jwt/JWTEntity.java Tue Sep 17 12:37:28 2013
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.oltu.oauth2.jwt;
+
+import java.util.Formatter;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * Abstract representation of a JWT entity which can contain custom fields.
+ */
+abstract class JWTEntity {
+
+    /**
+     * The registry that keeps the custom fields.
+     */
+    private final Map<String, Object> customFields;
+
+    public JWTEntity(Map<String, Object> customFields) {
+        this.customFields = customFields;
+    }
+
+    /**
+     * Return the specified custom field value,
+     * {@code null} if the custom field is not present.
+     *
+     * @param name the custom field name, it cannot be null.
+     * @return the specified custom field value,
+     *         {@code null} if the custom field is not present.
+     */
+    public <T> T getCustomField(String name, Class<T> type) {
+        if (name == null) {
+            throw new IllegalArgumentException("Null custom field name not present in the registry.");
+        }
+
+        Object value = customFields.get(name);
+
+        if (value != null) {
+            return type.cast(value);
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns the custom fields stored in the entity.
+     *
+     * @return the custom fields stored in the entity.
+     */
+    public Set<Entry<String, Object>> getCustomFields() {
+        return customFields.entrySet();
+    }
+
+    @Override
+    public String toString() {
+        Formatter formatter = new Formatter();
+
+        int counter = 0;
+        for (Entry<String, Object> customField : customFields.entrySet()) {
+            if (counter++ > 0) {
+                formatter.format(", ");
+            }
+
+            formatter.format("\"%s\": \"%s\"", customField.getKey(), customField.getValue());
+        }
+
+        formatter.close();
+
+        return formatter.toString();
+    }
+
+}

Propchange: oltu/trunk/oauth-2.0/jwt/src/main/java/org/apache/oltu/oauth2/jwt/JWTEntity.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: oltu/trunk/oauth-2.0/jwt/src/main/java/org/apache/oltu/oauth2/jwt/JWTEntity.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Propchange: oltu/trunk/oauth-2.0/jwt/src/main/java/org/apache/oltu/oauth2/jwt/JWTEntity.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: oltu/trunk/oauth-2.0/jwt/src/main/java/org/apache/oltu/oauth2/jwt/JWTUtils.java
URL: http://svn.apache.org/viewvc/oltu/trunk/oauth-2.0/jwt/src/main/java/org/apache/oltu/oauth2/jwt/JWTUtils.java?rev=1524010&r1=1524009&r2=1524010&view=diff
==============================================================================
--- oltu/trunk/oauth-2.0/jwt/src/main/java/org/apache/oltu/oauth2/jwt/JWTUtils.java (original)
+++ oltu/trunk/oauth-2.0/jwt/src/main/java/org/apache/oltu/oauth2/jwt/JWTUtils.java Tue Sep 17 12:37:28 2013
@@ -18,8 +18,13 @@ package org.apache.oltu.oauth2.jwt;
 
 import static java.lang.String.format;
 
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
 import java.io.StringWriter;
 import java.nio.charset.Charset;
+import java.util.Iterator;
+import java.util.Map.Entry;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -112,36 +117,86 @@ public class JWTUtils {
      * @param base64jsonString a Base64 encoded JSON Web Token.
      * @return a JWT instance.
      */
+    @SuppressWarnings("unchecked") // it is known that JSON keys are strings
     public static JWT parseJWT(String base64jsonString) {
         if (base64jsonString == null || base64jsonString.isEmpty()) {
             throw new IllegalArgumentException("Impossible to obtain a JWT from a null or empty string");
         }
 
-        Matcher matcher = BASE64_JWT_PATTERN.matcher(base64jsonString);
+        // TODO improve multi-line tokens
+        StringBuilder buffer = new StringBuilder();
+        BufferedReader reader = new BufferedReader(new StringReader(base64jsonString));
+        String line = null;
+        try {
+            while ((line = reader.readLine()) != null) {
+                buffer.append(line);
+            }
+        } catch (IOException e) {
+            // it cannot happen
+        } finally {
+            try {
+                reader.close();
+            } catch (IOException e) {
+                // swallow it
+            }
+        }
+
+        Matcher matcher = BASE64_JWT_PATTERN.matcher(buffer.toString());
         if (!matcher.matches()) {
             throw new IllegalArgumentException(base64jsonString
-                                               + "is not avalid JSON Web Token, it does not match with the pattern: "
+                                               + "is not a valid JSON Web Token, it does not match with the pattern: "
                                                + BASE64_JWT_PATTERN.pattern());
         }
 
-        JSONObject headerObject = decodeJSON(matcher.group(1));
-        JSONObject claimsSetObject = decodeJSON(matcher.group(2));
+        JWT.Builder jwtBuilder = new JWT.Builder(base64jsonString);
+
+        String header = matcher.group(1);
+        JSONObject headerObject = decodeJSON(header);
+
+        for (Iterator<String> keys = headerObject.keys(); keys.hasNext();) {
+            String key = keys.next();
+
+            if (ALGORITHM.equals(key)) {
+                jwtBuilder.setHeaderAlgorithm(getString(headerObject, ALGORITHM));
+            } else if (TYPE.equals(key)) {
+                jwtBuilder.setHeaderType(getString(headerObject, TYPE));
+            } else if (CONTENT_TYPE.equals(key)) {
+                jwtBuilder.setHeaderContentType(getString(headerObject, CONTENT_TYPE));
+            } else {
+                jwtBuilder.setHeaderCustomField(key, getString(headerObject, key));
+            }
+        }
+
+        String claimsSet = matcher.group(2);
+        JSONObject claimsSetObject = decodeJSON(claimsSet);
+
+        for (Iterator<String> keys = claimsSetObject.keys(); keys.hasNext();) {
+            String key = keys.next();
+
+            if (AUDIENCE.equals(key)) {
+                jwtBuilder.setClaimsSetAudience(getString(claimsSetObject, AUDIENCE));
+            } else if (EXPIRATION_TIME.equals(key)) {
+                jwtBuilder.setClaimsSetExpirationTime(getLong(claimsSetObject, EXPIRATION_TIME));
+            } else if (ISSUED_AT.equals(key)) {
+                jwtBuilder.setClaimsSetIssuedAt(getLong(claimsSetObject, ISSUED_AT));
+            } else if (ISSUER.equals(key)) {
+                jwtBuilder.setClaimsSetIssuer(getString(claimsSetObject, ISSUER));
+            } else if (JWT_ID.equals(key)) {
+                jwtBuilder.setClaimsSetJwdId(getString(claimsSetObject, JWT_ID));
+            } else if (NOT_BEFORE.equals(key)) {
+                jwtBuilder.setClaimsSetNotBefore(getString(claimsSetObject, NOT_BEFORE));
+            } else if (SUBJECT.equals(key)) {
+                jwtBuilder.setClaimsSetSubject(getString(claimsSetObject, SUBJECT));
+            } else if (TYPE.equals(key)) {
+                jwtBuilder.setClaimsSetType(getString(claimsSetObject, TYPE));
+            } else {
+                jwtBuilder.setClaimsSetCustomField(key, getString(claimsSetObject, key));
+            }
+        }
+
         String signature = matcher.group(3);
 
-        return new JWT.Builder(base64jsonString)
-                      .setHeaderAlgorithm(getString(headerObject, ALGORITHM))
-                      .setHeaderContentType(getString(headerObject, CONTENT_TYPE))
-                      .setHeaderType(getString(headerObject, CONTENT_TYPE))
-                      .setClaimsSetAudience(getString(claimsSetObject, AUDIENCE))
-                      .setClaimsSetExpirationTime(getLong(claimsSetObject, EXPIRATION_TIME))
-                      .setClaimsSetIssuedAt(getLong(claimsSetObject, ISSUED_AT))
-                      .setClaimsSetIssuer(getString(claimsSetObject, ISSUER))
-                      .setClaimsSetJwdId(getString(claimsSetObject, JWT_ID))
-                      .setClaimsSetNotBefore(getString(claimsSetObject, NOT_BEFORE))
-                      .setClaimsSetSubject(getString(claimsSetObject, SUBJECT))
-                      .setClaimsSetType(getString(claimsSetObject, TYPE))
-                      .setSignature(signature)
-                      .build();
+        return jwtBuilder.setSignature(signature).build();
     }
 
     private static JSONObject decodeJSON(String base64jsonString) {
@@ -173,13 +228,42 @@ public class JWTUtils {
 
     // serialization
 
+    public static String toBase64JsonString(JWT jwt) {
+        if (jwt == null) {
+            throw new IllegalArgumentException("Impossible to build a Token from a null JWT representation.");
+        }
+
+        String header = toJsonString(jwt.getHeader());
+        String encodedHeader = encodeJson(header);
+
+        String claimsSet = toJsonString(jwt.getClaimsSet());
+        String encodedClaimsSet = encodeJson(claimsSet);
+
+        String signature = jwt.getSignature();
+        if (signature == null) {
+            signature = "";
+        }
+
+        return new StringBuilder()
+               .append(encodedHeader)
+               .append('.')
+               .append('\r')
+               .append('\n')
+               .append(encodedClaimsSet)
+               .append('.')
+               .append('\r')
+               .append('\n')
+               .append(signature)
+               .toString();
+    }
+
     /**
      * Serializes the input JWT Header to its correct JSON representation.
      *
      * @param header the JWT Header has to be serialized.
      * @return the JSON string that represents the JWT Header.
      */
-    public static String toJsonString(Header header) {
+    private static String toJsonString(Header header) {
         if (header == null) {
             throw new IllegalArgumentException("Null JWT Header cannot be serialized to JSON representation.");
         }
@@ -188,7 +272,7 @@ public class JWTUtils {
         setString(object, ALGORITHM, header.getAlgorithm());
         setString(object, CONTENT_TYPE, header.getContentType());
         setString(object, TYPE, header.getType());
-        return toJsonString(object);
+        return toJsonString(header, object);
     }
 
     /**
@@ -197,7 +281,7 @@ public class JWTUtils {
      * @param claimsSet the JWT Claims Set has to be serialized.
      * @return the JSON string that represents the JWT Claims Set.
      */
-    public static String toJsonString(ClaimsSet claimsSet) {
+    private static String toJsonString(ClaimsSet claimsSet) {
         if (claimsSet == null) {
             throw new IllegalArgumentException("Null JWT Claims Set cannot be serialized to JSON representation.");
         }
@@ -211,10 +295,14 @@ public class JWTUtils {
         setString(object, TYPE, claimsSet.getType());
         setLong(object, EXPIRATION_TIME, claimsSet.getExpirationTime());
         setLong(object, ISSUED_AT, claimsSet.getIssuedAt());
-        return toJsonString(object);
+        return toJsonString(claimsSet, object);
     }
 
-    private static String toJsonString(JSONObject object) {
+    private static String toJsonString(JWTEntity entity, JSONObject object) {
+        for (Entry<String, Object> customField : entity.getCustomFields()) {
+            setObject(object, customField.getKey(), customField.getValue());
+        }
+
         StringWriter writer = new StringWriter();
         try {
             object.write(writer);
@@ -224,6 +312,10 @@ public class JWTUtils {
         return writer.toString();
     }
 
+    private static String encodeJson(String jsonString) {
+        return new String(new Base64(true).encode(jsonString.getBytes(UTF_8)), UTF_8);
+    }
+
     private static void setString(JSONObject object, String key, String value) {
         if (value != null) {
             try {
@@ -244,4 +336,14 @@ public class JWTUtils {
         }
     }
 
+    private static void setObject(JSONObject object, String key, Object value) {
+        if (value != null) {
+            try {
+                object.put(key, value);
+            } catch (JSONException e) {
+                // swallow it, null values are already guarded
+            }
+        }
+    }
+
 }

Modified: oltu/trunk/oauth-2.0/jwt/src/test/java/org/apache/oltu/oauth2/jwt/JWTUtilsTest.java
URL: http://svn.apache.org/viewvc/oltu/trunk/oauth-2.0/jwt/src/test/java/org/apache/oltu/oauth2/jwt/JWTUtilsTest.java?rev=1524010&r1=1524009&r2=1524010&view=diff
==============================================================================
--- oltu/trunk/oauth-2.0/jwt/src/test/java/org/apache/oltu/oauth2/jwt/JWTUtilsTest.java (original)
+++ oltu/trunk/oauth-2.0/jwt/src/test/java/org/apache/oltu/oauth2/jwt/JWTUtilsTest.java Tue Sep 17 12:37:28 2013
@@ -16,37 +16,85 @@
  */
 package org.apache.oltu.oauth2.jwt;
 
+import org.junit.After;
 import org.junit.Assert;
+import org.junit.Before;
 import org.junit.Test;
 
 public class JWTUtilsTest extends Assert {
 
-	private final String JWT = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImJlMWRhMGIzNTY3YmQyNjVhMjUwOThmYmNjMmIwOWYyMTM0NWIzYTIifQ.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwiaWQiOiIxMDY0MjI0NTMwODI0Nzk5OTg0MjkiLCJzdWIiOiIxMDY0MjI0NTMwODI0Nzk5OTg0MjkiLCJ2ZXJpZmllZF9lbWFpbCI6InRydWUiLCJlbWFpbF92ZXJpZmllZCI6InRydWUiLCJhdWQiOiI3ODg3MzIzNzIwNzguYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJjaWQiOiI3ODg3MzIzNzIwNzguYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhenAiOiI3ODg3MzIzNzIwNzguYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJlbWFpbCI6ImFudG9uaW8uc2Fuc29AZ21haWwuY29tIiwidG9rZW5faGFzaCI6IkwySTc3Z2lCTGswUlNzMHpRMVN2Q0EiLCJhdF9oYXNoIjoiTDJJNzdnaUJMazBSU3MwelExU3ZDQSIsImlhdCI6MTM2NjcyNjMxNywiZXhwIjoxMzY2NzMwMjE3fQ.XWYi5Zj1YWAMGIml_ftoAwmvW1Y7oeybLCpzQrJVuWJpS8L8Vd2TL-RTIOEVG03VA7e0_-_frNuw7MxUgVEgh8G-Nnbk_baJ6k_3w5c1SKFamFiHHDoKLFhrt1Y8JKSuGwE02V-px4Cn0dRAQAc1IN5CU6wqCrYK0p-fv_fvy28";
-
-	@Test
-	public void testJWT() throws Exception {
-		assertEquals(JWT, JWTUtils.parseJWT(JWT).getRawString());
-	}
-
-	@Test
-	public void testHeader() throws Exception {
-		Header header = JWTUtils.parseJWT(JWT).getHeader();
-		assertEquals("{\"alg\":\"RS256\"}", JWTUtils.toJsonString(header));
-		assertEquals("RS256", header.getAlgorithm());
-	}
-
-	@Test
-	public void testClaimsSet() throws Exception {
-		ClaimsSet claimsSet = JWTUtils.parseJWT(JWT).getClaimsSet();
-		assertEquals(
-				"{\"aud\":\"788732372078.apps.googleusercontent.com\",\"iss\":\"accounts.google.com\",\"sub\":\"106422453082479998429\",\"exp\":1366730217,\"iat\":1366726317}",
-				JWTUtils.toJsonString(claimsSet));
-		assertEquals("788732372078.apps.googleusercontent.com",
-				claimsSet.getAudience());
-		assertEquals("accounts.google.com", claimsSet.getIssuer());
-		assertEquals("106422453082479998429", claimsSet.getSubject());
-		assertEquals(1366730217, claimsSet.getExpirationTime());
-		assertEquals(1366726317, claimsSet.getIssuedAt());
-	}
+    private final String JWT = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImJlMWRhMGIzNTY3YmQyNjVhMjUwOThmYmNjMmIwOWYyMTM0\r\n"
+                             + "NWIzYTIifQ\r\n"
+                             + ".\r\n"
+                             + "eyJhdWQiOiI3ODg3MzIzNzIwNzguYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJpc3MiOiJh\r\n"
+                             + "Y2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTA2NDIyNDUzMDgyNDc5OTk4NDI5IiwiZXhwIjox\r\n"
+                             + "MzY2NzMwMjE3LCJpYXQiOjEzNjY3MjYzMTcsImlkIjoiMTA2NDIyNDUzMDgyNDc5OTk4NDI5Iiwi\r\n"
+                             + "dmVyaWZpZWRfZW1haWwiOiJ0cnVlIiwiZW1haWxfdmVyaWZpZWQiOiJ0cnVlIiwiY2lkIjoiNzg4\r\n"
+                             + "NzMyMzcyMDc4LmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiYXpwIjoiNzg4NzMyMzcyMDc4\r\n"
+                             + "LmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiZW1haWwiOiJhbnRvbmlvLnNhbnNvQGdtYWls\r\n"
+                             + "LmNvbSIsInRva2VuX2hhc2giOiJMMkk3N2dpQkxrMFJTczB6UTFTdkNBIiwiYXRfaGFzaCI6Ikwy\r\n"
+                             + "STc3Z2lCTGswUlNzMHpRMVN2Q0EifQ\r\n"
+                             + ".\r\n"
+                             + "XWYi5Zj1YWAMGIml_ftoAwmvW1Y7oeybLCpzQrJVuWJpS8L8Vd2TL-RTIOEVG03VA7e0_-_frNuw7MxUgVEgh8G-Nnbk_baJ6k_3w5c1SKFamFiHHDoKLFhrt1Y8JKSuGwE02V-px4Cn0dRAQAc1IN5CU6wqCrYK0p-fv_fvy28";
+
+    private JWT jwt;
+
+    @Before
+    public void setUp() throws Exception {
+        jwt = JWTUtils.parseJWT(JWT);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        jwt = null;
+    }
+
+    @Test
+    public void testJWT() throws Exception {
+        assertEquals(JWT, jwt.getRawString());
+    }
+
+    @Test
+    public void testHeader() throws Exception {
+        Header header = jwt.getHeader();
+        assertEquals("RS256", header.getAlgorithm());
+    }
+
+    @Test
+    public void testClaimsSet() throws Exception {
+        ClaimsSet claimsSet = jwt.getClaimsSet();
+        assertEquals("788732372078.apps.googleusercontent.com", claimsSet.getAudience());
+        assertEquals("accounts.google.com", claimsSet.getIssuer());
+        assertEquals("106422453082479998429", claimsSet.getSubject());
+        assertEquals(1366730217, claimsSet.getExpirationTime());
+        assertEquals(1366726317, claimsSet.getIssuedAt());
+    }
+
+    @Test
+    public void serialization() throws Exception {
+        JWT jwt = new JWT.Builder()
+                         // header
+                         .setHeaderAlgorithm("RS256")
+                         .setHeaderCustomField("kid", "be1da0b3567bd265a25098fbcc2b09f21345b3a2")
+                         // claimset
+                         .setClaimsSetAudience("788732372078.apps.googleusercontent.com")
+                         .setClaimsSetIssuer("accounts.google.com")
+                         .setClaimsSetSubject("106422453082479998429")
+                         .setClaimsSetExpirationTime(1366730217)
+                         .setClaimsSetIssuedAt(1366726317)
+                         .setClaimsSetCustomField("id", "106422453082479998429")
+                         .setClaimsSetCustomField("verified_email", "true")
+                         .setClaimsSetCustomField("email_verified", "true")
+                         .setClaimsSetCustomField("cid", "788732372078.apps.googleusercontent.com")
+                         .setClaimsSetCustomField("azp", "788732372078.apps.googleusercontent.com")
+                         .setClaimsSetCustomField("email", "antonio.sanso@gmail.com")
+                         .setClaimsSetCustomField("token_hash", "L2I77giBLk0RSs0zQ1SvCA")
+                         .setClaimsSetCustomField("at_hash", "L2I77giBLk0RSs0zQ1SvCA")
+                         // signature
+                         .setSignature("XWYi5Zj1YWAMGIml_ftoAwmvW1Y7oeybLCpzQrJVuWJpS8L8Vd2TL-RTIOEVG03VA7e0_-_frNuw7MxUgVEgh8G-Nnbk_baJ6k_3w5c1SKFamFiHHDoKLFhrt1Y8JKSuGwE02V-px4Cn0dRAQAc1IN5CU6wqCrYK0p-fv_fvy28")
+                         .build();
+        String encodedJWT = JWTUtils.toBase64JsonString(jwt);
+        assertEquals(JWT, encodedJWT);
+    }
 
 }