You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tomee.apache.org by jl...@apache.org on 2018/04/16 22:45:17 UTC

[13/38] tomee git commit: Get some code from https://github.com/MicroProfileJWT/microprofile-jwt-auth-prototype

Get some code from https://github.com/MicroProfileJWT/microprofile-jwt-auth-prototype


Project: http://git-wip-us.apache.org/repos/asf/tomee/repo
Commit: http://git-wip-us.apache.org/repos/asf/tomee/commit/c5b38b37
Tree: http://git-wip-us.apache.org/repos/asf/tomee/tree/c5b38b37
Diff: http://git-wip-us.apache.org/repos/asf/tomee/diff/c5b38b37

Branch: refs/heads/master
Commit: c5b38b37142af58fe771d7a397b388cec847f734
Parents: d987d3a
Author: Jean-Louis Monteiro <je...@gmail.com>
Authored: Thu Feb 22 22:19:49 2018 +0100
Committer: Jean-Louis Monteiro <je...@gmail.com>
Committed: Thu Feb 22 22:19:49 2018 +0100

----------------------------------------------------------------------
 tck/mp-jwt-embedded/pom.xml                     |  15 +
 .../jwt/DefaultJWTCallerPrincipal.java          | 305 ++++++++++++++++++-
 .../jwt/DefaultJWTCallerPrincipalFactory.java   |  86 ++++++
 .../microprofile/jwt/JWTAuthContextInfo.java    |  66 ++++
 .../microprofile/jwt/JWTCallerPrincipal.java    |   9 +-
 .../jwt/JWTCallerPrincipalFactory.java          | 124 ++++++++
 .../tomee/microprofile/jwt/MPJWTContext.java    |   3 -
 .../tomee/microprofile/jwt/MPJWTFilter.java     |  10 +-
 .../tomee/microprofile/jwt/ParseException.java  |  32 ++
 ...file.jwt.principal.JWTCallerPrincipalFactory |   1 +
 10 files changed, 632 insertions(+), 19 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tomee/blob/c5b38b37/tck/mp-jwt-embedded/pom.xml
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/pom.xml b/tck/mp-jwt-embedded/pom.xml
index 361fd50..fac5766 100644
--- a/tck/mp-jwt-embedded/pom.xml
+++ b/tck/mp-jwt-embedded/pom.xml
@@ -86,6 +86,21 @@
       <version>${version.arquillian}</version>
     </dependency>
 
+    <dependency>
+      <groupId>org.bitbucket.b_c</groupId>
+      <artifactId>jose4j</artifactId>
+      <version>0.6.0</version>
+    </dependency>
+    <dependency>
+      <groupId>javax.json.bind</groupId>
+      <artifactId>javax.json.bind-api</artifactId>
+      <version>1.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.glassfish</groupId>
+      <artifactId>javax.json</artifactId>
+      <version>1.0.4</version>
+    </dependency>
   </dependencies>
 
   <build>

http://git-wip-us.apache.org/repos/asf/tomee/blob/c5b38b37/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/DefaultJWTCallerPrincipal.java
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/DefaultJWTCallerPrincipal.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/DefaultJWTCallerPrincipal.java
index d141a5f..2559b1e 100644
--- a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/DefaultJWTCallerPrincipal.java
+++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/DefaultJWTCallerPrincipal.java
@@ -16,36 +16,319 @@
  */
 package org.apache.tomee.microprofile.jwt;
 
+import org.eclipse.microprofile.jwt.Claims;
+import org.jose4j.jwt.JwtClaims;
+import org.jose4j.jwt.MalformedClaimException;
+
+import javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonNumber;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonValue;
+import javax.security.auth.Subject;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
 import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 
 /**
- * A default implementation of JWTCallerPrincipal that wraps Nimbus library used by default here
- * We could create another implementation using plain JSON-P
- *
+ * A default implementation of JWTCallerPrincipal using jose4j
+ * Another implementation could use nimbus and another plain JSON-P
  */
 public class DefaultJWTCallerPrincipal extends JWTCallerPrincipal {
 
+    private static Logger logger = Logger.getLogger(DefaultJWTCallerPrincipal.class.getName());
+    private String jwt;
+    private String type;
+    private JwtClaims claimsSet;
+
     /**
-     * Create a JWTCallerPrincipal with the caller's name
+     * Create the DefaultJWTCallerPrincipal from the parsed JWT token and the extracted principal name
      *
-     * @param name - caller's name
+     * @param jwt  - the parsed JWT token representation
+     * @param name - the extracted unqiue name to use as the principal name; from "upn", "preferred_username" or "sub" claim
      */
-    public DefaultJWTCallerPrincipal(final String name) {
+    public DefaultJWTCallerPrincipal(String jwt, String type, JwtClaims claimsSet, String name) {
         super(name);
+        this.jwt = jwt;
+        this.type = type;
+        this.claimsSet = claimsSet;
+        fixJoseTypes();
+    }
+
+    @Override
+    public Set<String> getAudience() {
+        Set<String> audSet = new HashSet<>();
+        try {
+            List<String> audList = claimsSet.getStringListClaimValue("aud");
+            if (audList != null) {
+                audSet.addAll(audList);
+            }
+        } catch (MalformedClaimException e) {
+            try {
+                String aud = claimsSet.getStringClaimValue("aud");
+                audSet.add(aud);
+            } catch (MalformedClaimException e1) {
+            }
+        }
+        return audSet;
     }
 
     @Override
-    public String toString(final boolean showAll) {
-        return null;
+    public Set<String> getGroups() {
+        HashSet<String> groups = new HashSet<>();
+        try {
+            List<String> globalGroups = claimsSet.getStringListClaimValue("groups");
+            if (globalGroups != null) {
+                groups.addAll(globalGroups);
+            }
+        } catch (MalformedClaimException e) {
+            e.printStackTrace();
+        }
+        return groups;
     }
 
+
     @Override
     public Set<String> getClaimNames() {
-        return null;
+        return new HashSet<>(claimsSet.getClaimNames());
+    }
+
+    @Override
+    public Object getClaim(String claimName) {
+        Claims claimType = Claims.UNKNOWN;
+        Object claim = null;
+        try {
+            claimType = Claims.valueOf(claimName);
+        } catch (IllegalArgumentException e) {
+        }
+        // Handle the jose4j NumericDate types and
+        switch (claimType) {
+            case exp:
+            case iat:
+            case auth_time:
+            case nbf:
+            case updated_at:
+                try {
+                    claim = claimsSet.getClaimValue(claimType.name(), Long.class);
+                    if (claim == null) {
+                        claim = new Long(0);
+                    }
+                } catch (MalformedClaimException e) {
+                }
+                break;
+            case groups:
+                claim = getGroups();
+                break;
+            case aud:
+                claim = getAudience();
+                break;
+            case UNKNOWN:
+                claim = claimsSet.getClaimValue(claimName);
+                break;
+            default:
+                claim = claimsSet.getClaimValue(claimType.name());
+        }
+        return claim;
     }
 
     @Override
-    public <T> T getClaim(final String claimName) {
-        return null;
+    public boolean implies(Subject subject) {
+        return false;
+    }
+
+    public String toString() {
+        return toString(false);
     }
+
+    /**
+     * TODO: showAll is ignored and currently assumed true
+     *
+     * @param showAll - should all claims associated with the JWT be displayed or should only those defined in the
+     *                JsonWebToken interface be displayed.
+     * @return JWTCallerPrincipal string view
+     */
+    @Override
+    public String toString(boolean showAll) {
+        String toString = "DefaultJWTCallerPrincipal{" +
+                "id='" + getTokenID() + '\'' +
+                ", name='" + getName() + '\'' +
+                ", expiration=" + getExpirationTime() +
+                ", notBefore=" + getClaim(Claims.nbf.name()) +
+                ", issuedAt=" + getIssuedAtTime() +
+                ", issuer='" + getIssuer() + '\'' +
+                ", audience=" + getAudience() +
+                ", subject='" + getSubject() + '\'' +
+                ", type='" + type + '\'' +
+                ", issuedFor='" + getClaim("azp") + '\'' +
+                ", authTime=" + getClaim("auth_time") +
+                ", givenName='" + getClaim("given_name") + '\'' +
+                ", familyName='" + getClaim("family_name") + '\'' +
+                ", middleName='" + getClaim("middle_name") + '\'' +
+                ", nickName='" + getClaim("nickname") + '\'' +
+                ", preferredUsername='" + getClaim("preferred_username") + '\'' +
+                ", email='" + getClaim("email") + '\'' +
+                ", emailVerified=" + getClaim(Claims.email_verified.name()) +
+                ", allowedOrigins=" + getClaim("allowedOrigins") +
+                ", updatedAt=" + getClaim("updated_at") +
+                ", acr='" + getClaim("acr") + '\'';
+        StringBuilder tmp = new StringBuilder(toString);
+        tmp.append(", groups=[");
+        for (String group : getGroups()) {
+            tmp.append(group);
+            tmp.append(',');
+        }
+        tmp.setLength(tmp.length() - 1);
+        tmp.append("]}");
+        return tmp.toString();
+    }
+
+    /**
+     * Convert the types jose4j uses for address, sub_jwk, and jwk
+     */
+    private void fixJoseTypes() {
+        if (claimsSet.hasClaim(Claims.address.name())) {
+            replaceMap(Claims.address.name());
+        }
+        if (claimsSet.hasClaim(Claims.jwk.name())) {
+            replaceMap(Claims.jwk.name());
+        }
+        if (claimsSet.hasClaim(Claims.sub_jwk.name())) {
+            replaceMap(Claims.sub_jwk.name());
+        }
+        // Handle custom claims
+        Set<String> customClaimNames = filterCustomClaimNames(claimsSet.getClaimNames());
+        for (String name : customClaimNames) {
+            Object claimValue = claimsSet.getClaimValue(name);
+            Class claimType = claimValue.getClass();
+            if (claimValue instanceof List) {
+                replaceList(name);
+            } else if (claimValue instanceof Map) {
+                replaceMap(name);
+            } else if (claimValue instanceof Number) {
+                replaceNumber(name);
+            }
+        }
+    }
+
+    /**
+     * Determine the custom claims in the set
+     *
+     * @param claimNames - the current set of claim names in this token
+     * @return the possibly empty set of names for non-Claims claims
+     */
+    private Set<String> filterCustomClaimNames(Collection<String> claimNames) {
+        HashSet<String> customNames = new HashSet<>(claimNames);
+        for (Claims claim : Claims.values()) {
+            customNames.remove(claim.name());
+        }
+        return customNames;
+    }
+
+    /**
+     * Replace the jose4j Map<String,Object> with a JsonObject
+     *
+     * @param name - claim name
+     */
+    private void replaceMap(String name) {
+        try {
+            Map<String, Object> map = claimsSet.getClaimValue(name, Map.class);
+            JsonObject jsonObject = replaceMap(map);
+            claimsSet.setClaim(name, jsonObject);
+        } catch (MalformedClaimException e) {
+            logger.log(Level.WARNING, "replaceMap failure for: " + name, e);
+        }
+    }
+
+    private JsonObject replaceMap(Map<String, Object> map) {
+        JsonObjectBuilder builder = Json.createObjectBuilder();
+        for (Map.Entry<String, Object> entry : map.entrySet()) {
+            Object entryValue = entry.getValue();
+            if (entryValue instanceof Map) {
+                JsonObject entryJsonObject = replaceMap((Map<String, Object>) entryValue);
+                builder.add(entry.getKey(), entryJsonObject);
+            } else if (entryValue instanceof List) {
+                JsonArray array = (JsonArray) wrapValue(entryValue);
+                builder.add(entry.getKey(), array);
+            } else if (entryValue instanceof Long || entryValue instanceof Integer) {
+                long lvalue = ((Number) entryValue).longValue();
+                builder.add(entry.getKey(), lvalue);
+            } else if (entryValue instanceof Double || entryValue instanceof Float) {
+                double dvalue = ((Number) entryValue).doubleValue();
+                builder.add(entry.getKey(), dvalue);
+            } else if (entryValue instanceof Boolean) {
+                boolean flag = ((Boolean) entryValue).booleanValue();
+                builder.add(entry.getKey(), flag);
+            } else if (entryValue instanceof String) {
+                builder.add(entry.getKey(), entryValue.toString());
+            }
+        }
+        return builder.build();
+    }
+
+    private JsonValue wrapValue(Object value) {
+        JsonValue jsonValue = null;
+        if (value instanceof Number) {
+            Number number = (Number) value;
+            if ((number instanceof Long) || (number instanceof Integer)) {
+                jsonValue = Json.createObjectBuilder()
+                        .add("tmp", number.longValue())
+                        .build()
+                        .getJsonNumber("tmp");
+            } else {
+                jsonValue = Json.createObjectBuilder()
+                        .add("tmp", number.doubleValue())
+                        .build()
+                        .getJsonNumber("tmp");
+            }
+        } else if (value instanceof Boolean) {
+            Boolean flag = (Boolean) value;
+            jsonValue = flag ? JsonValue.TRUE : JsonValue.FALSE;
+        } else if (value instanceof List) {
+            JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
+            List list = (List) value;
+            for (Object element : list) {
+                if (element instanceof String) {
+                    arrayBuilder.add(element.toString());
+                } else {
+                    JsonValue jvalue = wrapValue(element);
+                    arrayBuilder.add(jvalue);
+                }
+            }
+            jsonValue = arrayBuilder.build();
+        }
+        return jsonValue;
+    }
+
+
+    /**
+     * Replace the jose4j List<?> with a JsonArray
+     *
+     * @param name - claim name
+     */
+    private void replaceList(String name) {
+        try {
+            List list = claimsSet.getClaimValue(name, List.class);
+            JsonArray array = (JsonArray) wrapValue(list);
+            claimsSet.setClaim(name, array);
+        } catch (MalformedClaimException e) {
+            logger.log(Level.WARNING, "replaceList failure for: " + name, e);
+        }
+    }
+
+    private void replaceNumber(String name) {
+        try {
+            Number number = claimsSet.getClaimValue(name, Number.class);
+            JsonNumber jsonNumber = (JsonNumber) wrapValue(number);
+            claimsSet.setClaim(name, jsonNumber);
+        } catch (MalformedClaimException e) {
+            logger.log(Level.WARNING, "replaceNumber failure for: " + name, e);
+        }
+    }
+
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tomee/blob/c5b38b37/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/DefaultJWTCallerPrincipalFactory.java
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/DefaultJWTCallerPrincipalFactory.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/DefaultJWTCallerPrincipalFactory.java
new file mode 100644
index 0000000..9c0a870
--- /dev/null
+++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/DefaultJWTCallerPrincipalFactory.java
@@ -0,0 +1,86 @@
+/*
+ *     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.tomee.microprofile.jwt;
+
+import org.eclipse.microprofile.jwt.Claims;
+import org.jose4j.jwa.AlgorithmConstraints;
+import org.jose4j.jws.AlgorithmIdentifiers;
+import org.jose4j.jwt.JwtClaims;
+import org.jose4j.jwt.MalformedClaimException;
+import org.jose4j.jwt.NumericDate;
+import org.jose4j.jwt.consumer.InvalidJwtException;
+import org.jose4j.jwt.consumer.JwtConsumer;
+import org.jose4j.jwt.consumer.JwtConsumerBuilder;
+import org.jose4j.jwt.consumer.JwtContext;
+
+/**
+ * A default implementation of the abstract JWTCallerPrincipalFactory that uses the Keycloak token parsing classes.
+ */
+public class DefaultJWTCallerPrincipalFactory extends JWTCallerPrincipalFactory {
+
+    /**
+     * Tries to load the JWTAuthContextInfo from CDI if the class level authContextInfo has not been set.
+     */
+    public DefaultJWTCallerPrincipalFactory() {
+    }
+
+    @Override
+    public JWTCallerPrincipal parse(final String token, final JWTAuthContextInfo authContextInfo) throws ParseException {
+        JWTCallerPrincipal principal = null;
+
+        try {
+            JwtConsumerBuilder builder = new JwtConsumerBuilder()
+                    .setRequireExpirationTime()
+                    .setRequireSubject()
+                    .setSkipDefaultAudienceValidation()
+                    .setExpectedIssuer(authContextInfo.getIssuedBy())
+                    .setVerificationKey(authContextInfo.getSignerKey())
+                    .setJwsAlgorithmConstraints(
+                            new AlgorithmConstraints(AlgorithmConstraints.ConstraintType.WHITELIST,
+                                    AlgorithmIdentifiers.RSA_USING_SHA256));
+            if (authContextInfo.getExpGracePeriodSecs() > 0) {
+                builder.setAllowedClockSkewInSeconds(authContextInfo.getExpGracePeriodSecs());
+            } else {
+                builder.setEvaluationTime(NumericDate.fromSeconds(0));
+            }
+
+            JwtConsumer jwtConsumer = builder.build();
+            JwtContext jwtContext = jwtConsumer.process(token);
+            String type = jwtContext.getJoseObjects().get(0).getHeader("typ");
+            //  Validate the JWT and process it to the Claims
+            jwtConsumer.processContext(jwtContext);
+            JwtClaims claimsSet = jwtContext.getJwtClaims();
+
+            // We have to determine the unique name to use as the principal name. It comes from upn, preferred_username, sub in that order
+            String principalName = claimsSet.getClaimValue("upn", String.class);
+            if (principalName == null) {
+                principalName = claimsSet.getClaimValue("preferred_username", String.class);
+                if (principalName == null) {
+                    principalName = claimsSet.getSubject();
+                }
+            }
+            claimsSet.setClaim(Claims.raw_token.name(), token);
+            principal = new DefaultJWTCallerPrincipal(token, type, claimsSet, principalName);
+        } catch (InvalidJwtException e) {
+            throw new ParseException("Failed to verify token", e);
+        } catch (MalformedClaimException e) {
+            throw new ParseException("Failed to verify token claims", e);
+        }
+
+        return principal;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tomee/blob/c5b38b37/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/JWTAuthContextInfo.java
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/JWTAuthContextInfo.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/JWTAuthContextInfo.java
new file mode 100644
index 0000000..a93e2cc
--- /dev/null
+++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/JWTAuthContextInfo.java
@@ -0,0 +1,66 @@
+/*
+ *     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.tomee.microprofile.jwt;
+
+import java.security.interfaces.RSAPublicKey;
+
+/**
+ * The public key and expected issuer needed to validate a token.
+ */
+public class JWTAuthContextInfo {
+    private RSAPublicKey signerKey;
+    private String issuedBy;
+    private int expGracePeriodSecs = 60;
+
+    public JWTAuthContextInfo() {
+    }
+
+    public JWTAuthContextInfo(RSAPublicKey signerKey, String issuedBy) {
+        this.signerKey = signerKey;
+        this.issuedBy = issuedBy;
+    }
+
+    public JWTAuthContextInfo(JWTAuthContextInfo orig) {
+        this.signerKey = orig.signerKey;
+        this.issuedBy = orig.issuedBy;
+        this.expGracePeriodSecs = orig.expGracePeriodSecs;
+    }
+
+    public RSAPublicKey getSignerKey() {
+        return signerKey;
+    }
+
+    public void setSignerKey(RSAPublicKey signerKey) {
+        this.signerKey = signerKey;
+    }
+
+    public String getIssuedBy() {
+        return issuedBy;
+    }
+
+    public void setIssuedBy(String issuedBy) {
+        this.issuedBy = issuedBy;
+    }
+
+    public int getExpGracePeriodSecs() {
+        return expGracePeriodSecs;
+    }
+
+    public void setExpGracePeriodSecs(int expGracePeriodSecs) {
+        this.expGracePeriodSecs = expGracePeriodSecs;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tomee/blob/c5b38b37/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/JWTCallerPrincipal.java
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/JWTCallerPrincipal.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/JWTCallerPrincipal.java
index 3eca47b..e130213 100644
--- a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/JWTCallerPrincipal.java
+++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/JWTCallerPrincipal.java
@@ -14,18 +14,19 @@
  *     See the License for the specific language governing permissions and
  *     limitations under the License.
  */
-
 package org.apache.tomee.microprofile.jwt;
 
 
-import java.util.Optional;
 import org.eclipse.microprofile.jwt.JsonWebToken;
 
+import java.util.Optional;
+
 /**
  * An abstract CallerPrincipal implementation that provides access to the JWT claims that are required by
  * the microprofile token.
  */
 public abstract class JWTCallerPrincipal implements JsonWebToken {
+
     private String name;
 
     /**
@@ -51,8 +52,8 @@ public abstract class JWTCallerPrincipal implements JsonWebToken {
      */
     public abstract String toString(boolean showAll);
 
-    public <T> Optional<T> claim(String claimName) {
-        T claim = (T) getClaim(claimName);
+    public <T> Optional<T> claim(final String claimName) {
+        final T claim = (T) getClaim(claimName);
         return Optional.ofNullable(claim);
     }
 }

http://git-wip-us.apache.org/repos/asf/tomee/blob/c5b38b37/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/JWTCallerPrincipalFactory.java
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/JWTCallerPrincipalFactory.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/JWTCallerPrincipalFactory.java
new file mode 100644
index 0000000..a64f95a
--- /dev/null
+++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/JWTCallerPrincipalFactory.java
@@ -0,0 +1,124 @@
+/*
+ *     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.tomee.microprofile.jwt;
+
+import java.net.URL;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ServiceLoader;
+
+/**
+ * The factory class that provides the token string to JWTCallerPrincipal parsing for a given implementation.
+ */
+public abstract class JWTCallerPrincipalFactory {
+    private static JWTCallerPrincipalFactory instance;
+
+    /**
+     * Obtain the JWTCallerPrincipalFactory that has been set or by using the ServiceLoader pattern.
+     *
+     * @return the factory instance
+     * @see #setInstance(JWTCallerPrincipalFactory)
+     */
+    public static JWTCallerPrincipalFactory instance() {
+        if (instance == null) {
+            synchronized (JWTCallerPrincipalFactory.class) {
+                if (instance != null) {
+                    return instance;
+                }
+
+                ClassLoader cl = AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
+                    @Override
+                    public ClassLoader run() {
+                        return Thread.currentThread().getContextClassLoader();
+                    }
+                });
+                if (cl == null) {
+                    cl = JWTCallerPrincipalFactory.class.getClassLoader();
+                }
+
+                JWTCallerPrincipalFactory newInstance = loadSpi(cl);
+
+                if (newInstance == null && cl != JWTCallerPrincipalFactory.class.getClassLoader()) {
+                    cl = JWTCallerPrincipalFactory.class.getClassLoader();
+                    newInstance = loadSpi(cl);
+                }
+                if (newInstance == null) {
+                    throw new IllegalStateException("No JWTCallerPrincipalFactory implementation found!");
+                }
+
+                instance = newInstance;
+            }
+        }
+
+        return instance;
+    }
+
+    /**
+     * Look for a JWTCallerPrincipalFactory service implementation using the ServiceLoader.
+     *
+     * @param cl - the ClassLoader to pass into the {@link ServiceLoader#load(Class, ClassLoader)} method.
+     * @return the JWTCallerPrincipalFactory if found, null otherwise
+     */
+    private static JWTCallerPrincipalFactory loadSpi(ClassLoader cl) {
+        if (cl == null) {
+            return null;
+        }
+
+        // start from the root CL and go back down to the TCCL
+        JWTCallerPrincipalFactory instance = loadSpi(cl.getParent());
+
+        if (instance == null) {
+            ServiceLoader<JWTCallerPrincipalFactory> sl = ServiceLoader.load(JWTCallerPrincipalFactory.class, cl);
+            URL u = cl.getResource("/META-INF/services/org.eclipse.microprofile.jwt.principal.JWTCallerPrincipalFactory");
+            System.out.printf("JWTCallerPrincipalFactory, cl=%s, u=%s, sl=%s\n", cl, u, sl);
+            try {
+                for (JWTCallerPrincipalFactory spi : sl) {
+                    if (instance != null) {
+                        throw new IllegalStateException(
+                                "Multiple JWTCallerPrincipalFactory implementations found: "
+                                        + spi.getClass().getName() + " and "
+                                        + instance.getClass().getName());
+                    } else {
+                        System.out.printf("sl=%s, loaded=%s\n", sl, spi);
+                        instance = spi;
+                    }
+                }
+            } catch (Throwable e) {
+                System.err.printf("Warning: %s\n", e.getMessage());
+            }
+        }
+        return instance;
+    }
+
+    /**
+     * Set the instance. It is used by OSGi environment where service loader pattern is not supported.
+     *
+     * @param resolver the instance to use.
+     */
+    public static void setInstance(JWTCallerPrincipalFactory resolver) {
+        instance = resolver;
+    }
+
+    /**
+     * Parse the given bearer token string into a JWTCallerPrincipal instance.
+     *
+     * @param token - the bearer token provided for authorization
+     * @return A JWTCallerPrincipal representation for the token.
+     * @throws ParseException on parse or verification failure.
+     */
+    public abstract JWTCallerPrincipal parse(String token, JWTAuthContextInfo authContextInfo) throws ParseException;
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tomee/blob/c5b38b37/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTContext.java
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTContext.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTContext.java
index 2197745..bffccf2 100644
--- a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTContext.java
+++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTContext.java
@@ -16,10 +16,7 @@
  */
 package org.apache.tomee.microprofile.jwt;
 
-import org.eclipse.microprofile.auth.LoginConfig;
-
 import javax.enterprise.context.ApplicationScoped;
-import java.net.URI;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;

http://git-wip-us.apache.org/repos/asf/tomee/blob/c5b38b37/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java
index cb235fa..092ad9d 100644
--- a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java
+++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java
@@ -40,6 +40,9 @@ public class MPJWTFilter implements Filter {
     @Inject
     private MPJWTContext context;
 
+    @Inject
+    private JWTAuthContextInfo authContextInfo;
+
     @Override
     public void init(final FilterConfig filterConfig) throws ServletException {
         // get configuration
@@ -60,7 +63,7 @@ public class MPJWTFilter implements Filter {
         // todo get JWT and do validation
         // todo not sure what to do with the realm
 
-        final JsonWebToken jsonWebToken = new DefaultJWTCallerPrincipal("bla"); // will be build during validation
+        final JsonWebToken jsonWebToken = null; // will be build during validation
 
         // now wrap the httpServletRequest and override the principal so CXF can propagate into the SecurityContext
         chain.doFilter(new HttpServletRequestWrapper(httpServletRequest) {
@@ -89,4 +92,9 @@ public class MPJWTFilter implements Filter {
 
     }
 
+    protected JsonWebToken validate(String bearerToken) throws ParseException {
+        JWTCallerPrincipalFactory factory = JWTCallerPrincipalFactory.instance();
+        return factory.parse(bearerToken, authContextInfo);
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/tomee/blob/c5b38b37/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/ParseException.java
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/ParseException.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/ParseException.java
new file mode 100644
index 0000000..60269b0
--- /dev/null
+++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/ParseException.java
@@ -0,0 +1,32 @@
+/*
+ *     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.tomee.microprofile.jwt;
+
+/**
+ * The exception thrown when
+ */
+public class ParseException extends Exception {
+    private static final long serialVersionUID = 1L;
+
+    public ParseException(String message) {
+        super(message);
+    }
+
+    public ParseException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tomee/blob/c5b38b37/tck/mp-jwt-embedded/src/main/resources/META-INF/services/org.eclipse.microprofile.jwt.principal.JWTCallerPrincipalFactory
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/resources/META-INF/services/org.eclipse.microprofile.jwt.principal.JWTCallerPrincipalFactory b/tck/mp-jwt-embedded/src/main/resources/META-INF/services/org.eclipse.microprofile.jwt.principal.JWTCallerPrincipalFactory
new file mode 100644
index 0000000..67f39db
--- /dev/null
+++ b/tck/mp-jwt-embedded/src/main/resources/META-INF/services/org.eclipse.microprofile.jwt.principal.JWTCallerPrincipalFactory
@@ -0,0 +1 @@
+org.apache.tomee.microprofile.jwt.DefaultJWTCallerPrincipalFactory
\ No newline at end of file