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:21 UTC
[17/38] tomee git commit: some refactoring
http://git-wip-us.apache.org/repos/asf/tomee/blob/c5964e07/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/ClaimValueProducer.java
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/ClaimValueProducer.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/ClaimValueProducer.java
new file mode 100644
index 0000000..d69df02
--- /dev/null
+++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/ClaimValueProducer.java
@@ -0,0 +1,75 @@
+/*
+ * 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.cdi;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Optional;
+
+import javax.enterprise.inject.Produces;
+import javax.enterprise.inject.spi.InjectionPoint;
+
+import org.eclipse.microprofile.jwt.Claim;
+import org.eclipse.microprofile.jwt.ClaimValue;
+import org.eclipse.microprofile.jwt.Claims;
+
+/**
+ * A producer for the ClaimValue<T> wrapper injection sites.
+ * @param <T> the raw claim type
+ */
+public class ClaimValueProducer<T> {
+
+ @Produces
+ @Claim("")
+ ClaimValue<T> produce(InjectionPoint ip) {
+ String name = getName(ip);
+ ClaimValue<Optional<T>> cv = MPJWTProducer.generalClaimValueProducer(name);
+ ClaimValue<T> returnValue = (ClaimValue<T>) cv;
+ Optional<T> value = cv.getValue();
+ // Pull out the ClaimValue<T> T type,
+ Type matchType = ip.getType();
+ Type actualType = Object.class;
+ boolean isOptional = false;
+ if (matchType instanceof ParameterizedType) {
+ actualType = ((ParameterizedType) matchType).getActualTypeArguments()[0];
+ isOptional = matchType.getTypeName().equals(Optional.class.getTypeName());
+ if (isOptional) {
+ actualType = ((ParameterizedType) matchType).getActualTypeArguments()[0];
+ }
+ }
+
+ if (!actualType.getTypeName().startsWith(Optional.class.getTypeName())) {
+ T nestedValue = value.orElse(null);
+ ClaimValueWrapper<T> wrapper = new ClaimValueWrapper<>(cv.getName());
+ wrapper.setValue(nestedValue);
+ returnValue = wrapper;
+ }
+ return returnValue;
+ }
+
+ String getName(InjectionPoint ip) {
+ String name = null;
+ for (Annotation ann : ip.getQualifiers()) {
+ if (ann instanceof Claim) {
+ Claim claim = (Claim) ann;
+ name = claim.standard() == Claims.UNKNOWN ? claim.value() : claim.standard().name();
+ }
+ }
+ return name;
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tomee/blob/c5964e07/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/ClaimValueWrapper.java
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/ClaimValueWrapper.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/ClaimValueWrapper.java
new file mode 100644
index 0000000..6776191
--- /dev/null
+++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/ClaimValueWrapper.java
@@ -0,0 +1,54 @@
+/*
+ * 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.cdi;
+
+import org.eclipse.microprofile.jwt.ClaimValue;
+
+/**
+ * An implementation of the ClaimValue interface
+ *
+ * @param <T> the claim value type
+ */
+public class ClaimValueWrapper<T> implements ClaimValue<T> {
+ private String name;
+
+ private T value;
+
+ public ClaimValueWrapper(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public T getValue() {
+ return value;
+ }
+
+ public void setValue(T value) {
+ this.value = value;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("ClaimValueWrapper[@%s], name=%s, value[%s]=%s", Integer.toHexString(hashCode()),
+ name, value.getClass(), value);
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tomee/blob/c5964e07/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/JsonValueProducer.java
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/JsonValueProducer.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/JsonValueProducer.java
new file mode 100644
index 0000000..2f991b2
--- /dev/null
+++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/JsonValueProducer.java
@@ -0,0 +1,111 @@
+/*
+ * 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.cdi;
+
+import org.eclipse.microprofile.jwt.Claim;
+import org.eclipse.microprofile.jwt.Claims;
+
+import javax.enterprise.inject.Produces;
+import javax.enterprise.inject.spi.InjectionPoint;
+import javax.json.JsonArray;
+import javax.json.JsonNumber;
+import javax.json.JsonObject;
+import javax.json.JsonString;
+import javax.json.JsonValue;
+import java.lang.annotation.Annotation;
+import java.util.Optional;
+import java.util.logging.Logger;
+
+/**
+ * A producer for JsonValue injection types
+ */
+public class JsonValueProducer {
+ private static Logger log = Logger.getLogger(JsonValueProducer.class.getName());
+
+ @Produces
+ @Claim("")
+ public JsonString getJsonString(InjectionPoint ip) {
+ return getValue(ip);
+ }
+
+ @Produces
+ @Claim("")
+ public Optional<JsonString> getOptionalJsonString(InjectionPoint ip) {
+ return getOptionalValue(ip);
+ }
+
+ @Produces
+ @Claim("")
+ public JsonNumber getJsonNumber(InjectionPoint ip) {
+ return getValue(ip);
+ }
+
+ @Produces
+ @Claim("")
+ public Optional<JsonNumber> getOptionalJsonNumber(InjectionPoint ip) {
+ return getOptionalValue(ip);
+ }
+
+ @Produces
+ @Claim("")
+ public JsonArray getJsonArray(InjectionPoint ip) {
+ return getValue(ip);
+ }
+
+ @Produces
+ @Claim("")
+ public Optional<JsonArray> getOptionalJsonArray(InjectionPoint ip) {
+ return getOptionalValue(ip);
+ }
+
+ @Produces
+ @Claim("")
+ public JsonObject getJsonObject(InjectionPoint ip) {
+ return getValue(ip);
+ }
+
+ @Produces
+ @Claim("")
+ public Optional<JsonObject> getOptionalJsonObject(InjectionPoint ip) {
+ return getOptionalValue(ip);
+ }
+
+ public <T extends JsonValue> T getValue(InjectionPoint ip) {
+ log.fine(String.format("JsonValueProducer(%s).produce", ip));
+ String name = getName(ip);
+ T jsonValue = (T) MPJWTProducer.generalJsonValueProducer(name);
+ return jsonValue;
+ }
+
+ public <T extends JsonValue> Optional<T> getOptionalValue(InjectionPoint ip) {
+ log.fine(String.format("JsonValueProducer(%s).produce", ip));
+ String name = getName(ip);
+ T jsonValue = (T) MPJWTProducer.generalJsonValueProducer(name);
+ return Optional.ofNullable(jsonValue);
+ }
+
+ String getName(InjectionPoint ip) {
+ String name = null;
+ for (Annotation ann : ip.getQualifiers()) {
+ if (ann instanceof Claim) {
+ Claim claim = (Claim) ann;
+ name = claim.standard() == Claims.UNKNOWN ? claim.value() : claim.standard().name();
+ }
+ }
+ return name;
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tomee/blob/c5964e07/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/MPJWTCDIExtension.java
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/MPJWTCDIExtension.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/MPJWTCDIExtension.java
new file mode 100644
index 0000000..95e1aea
--- /dev/null
+++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/MPJWTCDIExtension.java
@@ -0,0 +1,395 @@
+/*
+ * 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.cdi;
+
+import org.apache.tomee.microprofile.jwt.config.JWTAuthContextInfoProvider;
+import org.apache.tomee.microprofile.jwt.MPJWTFilter;
+import org.apache.tomee.microprofile.jwt.MPJWTInitializer;
+import org.apache.tomee.microprofile.jwt.TCKTokenParser;
+import org.eclipse.microprofile.jwt.Claim;
+import org.eclipse.microprofile.jwt.Claims;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.context.SessionScoped;
+import javax.enterprise.event.Observes;
+import javax.enterprise.inject.spi.AfterBeanDiscovery;
+import javax.enterprise.inject.spi.AfterDeploymentValidation;
+import javax.enterprise.inject.spi.BeanAttributes;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.enterprise.inject.spi.BeforeBeanDiscovery;
+import javax.enterprise.inject.spi.DeploymentException;
+import javax.enterprise.inject.spi.Extension;
+import javax.enterprise.inject.spi.InjectionPoint;
+import javax.enterprise.inject.spi.ProcessBeanAttributes;
+import javax.enterprise.inject.spi.ProcessInjectionPoint;
+import javax.enterprise.inject.spi.ProcessProducer;
+import javax.inject.Provider;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
+import java.util.logging.Logger;
+
+/**
+ * A CDI extension that provides a producer for the current authenticated JsonWebToken based on a thread
+ * local value that is managed by the {@link JWTAuthMechanism} request
+ * authentication handler.
+ * <p>
+ * This also installs the producer methods for the discovered:
+ * <ul>
+ * <li>@Claim ClaimValue<T> injection sites.</li>
+ * <li>@Claim raw type<T> injection sites.</li>
+ * <li>@Claim JsonValue injection sites.</li>
+ * </ul>
+ *
+ * @see JWTAuthMechanism
+ */
+public class MPJWTCDIExtension implements Extension {
+ private static Logger log = Logger.getLogger(MPJWTCDIExtension.class.getName());
+
+ /**
+ * Register the MPJWTProducer JsonWebToken producer bean
+ *
+ * @param bbd before discovery event
+ * @param beanManager cdi bean manager
+ */
+ public void observeBeforeBeanDiscovery(@Observes BeforeBeanDiscovery bbd, BeanManager beanManager) {
+ log.fine("MPJWTExtension(), added JWTPrincipalProducer");
+ bbd.addAnnotatedType(beanManager.createAnnotatedType(TCKTokenParser.class));
+ bbd.addAnnotatedType(beanManager.createAnnotatedType(MPJWTFilter.class));
+ bbd.addAnnotatedType(beanManager.createAnnotatedType(MPJWTInitializer.class));
+ bbd.addAnnotatedType(beanManager.createAnnotatedType(JWTAuthContextInfoProvider.class));
+ bbd.addAnnotatedType(beanManager.createAnnotatedType(MPJWTProducer.class));
+ bbd.addAnnotatedType(beanManager.createAnnotatedType(RawClaimTypeProducer.class));
+ bbd.addAnnotatedType(beanManager.createAnnotatedType(ClaimValueProducer.class));
+ bbd.addAnnotatedType(beanManager.createAnnotatedType(JsonValueProducer.class));
+ }
+
+ /**
+ * Replace the general producer method BeanAttributes with one bound to the collected injection site
+ * types to properly reflect all of the type locations the producer method applies to.
+ *
+ * @param pba the ProcessBeanAttributes
+ * @see ClaimProviderBeanAttributes
+ */
+ public void addTypeToClaimProducer(@Observes ProcessBeanAttributes pba) {
+ if (pba.getAnnotated().isAnnotationPresent(Claim.class)) {
+ Claim claim = pba.getAnnotated().getAnnotation(Claim.class);
+ if (claim.value().length() == 0 && claim.standard() == Claims.UNKNOWN) {
+ log.fine(String.format("addTypeToClaimProducer: %s\n", pba.getAnnotated()));
+ BeanAttributes delegate = pba.getBeanAttributes();
+ String name = delegate.getName();
+ if (delegate.getTypes().contains(Optional.class)) {
+ if (providerOptionalTypes.size() == 0) {
+ providerOptionalTypes.add(Optional.class);
+ }
+ pba.setBeanAttributes(new ClaimProviderBeanAttributes(delegate, providerOptionalTypes, providerQualifiers));
+ // This is
+ } else if (name != null && name.startsWith("RawClaimTypeProducer#")) {
+ if (rawTypes.size() == 0) {
+ rawTypes.add(Object.class);
+ }
+ pba.setBeanAttributes(new ClaimProviderBeanAttributes(delegate, rawTypes, rawTypeQualifiers));
+ log.fine(String.format("Setup RawClaimTypeProducer BeanAttributes"));
+ }
+ }
+ }
+ }
+
+ public void afterDeploymentValidation(@Observes AfterDeploymentValidation event, BeanManager beanManager) {
+ }
+
+ void doProcessProducers(@Observes ProcessProducer pp) {
+ }
+
+ /**
+ * Handle the non-{@linkplain Provider}, {@linkplain org.eclipse.microprofile.jwt.ClaimValue}, and
+ * {@linkplain javax.json.JsonValue} claim injection types.
+ *
+ * @param pip - the injection point event information
+ * @see RawClaimTypeProducer
+ */
+ void processClaimInjections(@Observes ProcessInjectionPoint pip) {
+ log.fine(String.format("pipRaw: %s", pip.getInjectionPoint()));
+ InjectionPoint ip = pip.getInjectionPoint();
+ if (ip.getAnnotated().isAnnotationPresent(Claim.class)) {
+ Claim claim = ip.getAnnotated().getAnnotation(Claim.class);
+ if (ip.getType() instanceof Class) {
+ Class rawClass = (Class) ip.getType();
+ // Primative types
+ if (Modifier.isFinal(rawClass.getModifiers())) {
+ rawTypes.add(ip.getType());
+ rawTypeQualifiers.add(claim);
+ log.fine(String.format("+++ Added Claim raw type: %s", ip.getType()));
+ Class declaringClass = ip.getMember().getDeclaringClass();
+ Annotation[] appScoped = declaringClass.getAnnotationsByType(ApplicationScoped.class);
+ Annotation[] sessionScoped = declaringClass.getAnnotationsByType(SessionScoped.class);
+ if ((appScoped != null && appScoped.length > 0) || (sessionScoped != null && sessionScoped.length > 0)) {
+ String err = String.format("A raw type cannot be injected into application/session scope: IP=%s", ip);
+ pip.addDefinitionError(new DeploymentException(err));
+ }
+ }
+ // This handles collections of primative types
+ } else if (isRawParameterizedType(ip.getType())) {
+ log.fine(String.format("+++ Added Claim ParameterizedType: %s", ip.getType()));
+ rawTypes.add(ip.getType());
+ rawTypeQualifiers.add(claim);
+ }
+ } else {
+ log.fine(String.format("Skipping pip: %s, type: %s/%s", ip, ip.getType(), ip.getType().getClass()));
+ }
+ }
+
+ /**
+ * Collect the types of all {@linkplain Provider} injection points annotated with {@linkplain Claim}.
+ *
+ * @param pip - the injection point event information
+ */
+ void processClaimProviderInjections(@Observes ProcessInjectionPoint<?, ? extends Provider> pip) {
+ log.fine(String.format("pip: %s", pip.getInjectionPoint()));
+ final InjectionPoint ip = pip.getInjectionPoint();
+ if (ip.getAnnotated().isAnnotationPresent(Claim.class)) {
+ Claim claim = ip.getAnnotated().getAnnotation(Claim.class);
+ if (claim.value().length() == 0 && claim.standard() == Claims.UNKNOWN) {
+ pip.addDefinitionError(new DeploymentException("@Claim at: " + ip + " has no name or valid standard enum setting"));
+ }
+ boolean usesEnum = claim.standard() != Claims.UNKNOWN;
+ final String claimName = usesEnum ? claim.standard().name() : claim.value();
+ log.fine(String.format("Checking Provider Claim(%s), ip: %s", claimName, ip));
+ ClaimIP claimIP = claims.get(claimName);
+ Type matchType = ip.getType();
+ // The T from the Provider<T> injection site
+ Type actualType = ((ParameterizedType) matchType).getActualTypeArguments()[0];
+ // Don't add Optional or JsonValue as this is handled specially
+ if (!optionalOrJsonValue(actualType)) {
+ rawTypes.add(actualType);
+ } else if (!actualType.getTypeName().startsWith("javax.json.Json")) {
+ // Validate that this is not an Optional<JsonValue>
+ Type innerType = ((ParameterizedType) actualType).getActualTypeArguments()[0];
+ if (!innerType.getTypeName().startsWith("javax.json.Json")) {
+ providerOptionalTypes.add(actualType);
+ providerQualifiers.add(claim);
+ }
+ }
+ rawTypeQualifiers.add(claim);
+ ClaimIPType key = new ClaimIPType(claimName, actualType);
+ if (claimIP == null) {
+ claimIP = new ClaimIP(actualType, actualType, false, claim);
+ claimIP.setProviderSite(true);
+ claims.put(key, claimIP);
+ }
+ claimIP.getInjectionPoints().add(ip);
+ log.fine(String.format("+++ Added Provider Claim(%s) ip: %s", claimName, ip));
+
+ }
+ }
+
+ /**
+ * Create producer methods for each ClaimValue injection site
+ *
+ * @param event - AfterBeanDiscovery
+ * @param beanManager - CDI bean manager
+ */
+ void observesAfterBeanDiscovery(@Observes final AfterBeanDiscovery event, final BeanManager beanManager) {
+ log.fine(String.format("observesAfterBeanDiscovery, %s", claims));
+ installClaimValueProducerMethodsViaSyntheticBeans(event, beanManager);
+
+ //installClaimValueProducesViaTemplateType(event, beanManager);
+ }
+
+ /**
+ * Create a synthetic bean with a custom Producer for the non-Provider injection sites.
+ *
+ * @param event - AfterBeanDiscovery
+ * @param beanManager - CDI bean manager
+ */
+ private void installClaimValueProducerMethodsViaSyntheticBeans(final AfterBeanDiscovery event, final BeanManager beanManager) {
+
+ }
+
+ private boolean optionalOrJsonValue(Type type) {
+ boolean isOptionOrJson = type.getTypeName().startsWith(Optional.class.getTypeName())
+ | type.getTypeName().startsWith("javax.json.Json");
+ return isOptionOrJson;
+ }
+
+ private boolean isRawParameterizedType(Type type) {
+ boolean isRawParameterizedType = false;
+ if (type instanceof ParameterizedType) {
+ ParameterizedType ptype = ParameterizedType.class.cast(type);
+ Type rawType = ptype.getRawType();
+ String rawTypeName = rawType.getTypeName();
+ isRawParameterizedType = !rawTypeName.startsWith("org.eclipse.microprofile.jwt");
+ }
+ return isRawParameterizedType;
+ }
+
+ /**
+ * A map of claim,type pairs to the injection site information
+ */
+ private HashMap<ClaimIPType, ClaimIP> claims = new HashMap<>();
+
+ private Set<Type> providerOptionalTypes = new HashSet<>();
+
+ private Set<Type> providerTypes = new HashSet<>();
+
+ private Set<Type> rawTypes = new HashSet<>();
+
+ private Set<Annotation> rawTypeQualifiers = new HashSet<>();
+
+ private Set<Annotation> providerQualifiers = new HashSet<>();
+
+ /**
+ * A key for a claim,injection site type pair
+ */
+ public static class ClaimIPType implements Comparable<ClaimIPType> {
+ public ClaimIPType(String claimName, Type ipType) {
+ this.claimName = claimName;
+ this.ipType = ipType;
+ }
+
+ /**
+ * Order the @Claim ClaimValue<T> on the @Claim.value and then T type name
+ *
+ * @param o - ClaimIP to compare to
+ * @return the ordering of this claim relative to o
+ */
+ @Override
+ public int compareTo(ClaimIPType o) {
+ int compareTo = claimName.compareTo(o.claimName);
+ if (compareTo == 0) {
+ compareTo = ipType.getTypeName().compareTo(o.ipType.getTypeName());
+ }
+ return compareTo;
+ }
+
+ private String claimName;
+
+ private Type ipType;
+ }
+
+ /**
+ * The representation of an @Claim annotated injection site
+ */
+ public static class ClaimIP {
+ /**
+ * Create a ClaimIP from the injection site information
+ *
+ * @param matchType - the outer type of the injection site
+ * @param valueType - the parameterized type of the injection site
+ * @param isOptional - is the injection site an Optional
+ * @param claim - the Claim qualifier
+ */
+ public ClaimIP(Type matchType, Type valueType, boolean isOptional, Claim claim) {
+ this.matchType = matchType;
+ this.valueType = valueType;
+ this.claim = claim;
+ }
+
+ public Type getMatchType() {
+ return matchType;
+ }
+
+ public String getClaimName() {
+ return claim.standard() == Claims.UNKNOWN ? claim.value() : claim.standard().name();
+ }
+
+ public Claim getClaim() {
+ return claim;
+ }
+
+ public Type getValueType() {
+ return valueType;
+ }
+
+ public boolean isOptional() {
+ return isOptional;
+ }
+
+ public boolean isProviderSite() {
+ return isProviderSite;
+ }
+
+ public void setProviderSite(boolean providerSite) {
+ this.isProviderSite = providerSite;
+ }
+
+ public boolean isNonStandard() {
+ return isNonStandard;
+ }
+
+ public void setNonStandard(boolean nonStandard) {
+ isNonStandard = nonStandard;
+ }
+
+ public boolean isJsonValue() {
+ return isJsonValue;
+ }
+
+ public void setJsonValue(boolean jsonValue) {
+ isJsonValue = jsonValue;
+ }
+
+ public Set<InjectionPoint> getInjectionPoints() {
+ return injectionPoints;
+ }
+
+ @Override
+ public String toString() {
+ return "ClaimIP{" +
+ "type=" + matchType +
+ ", claim=" + claim +
+ ", ips=" + injectionPoints +
+ '}';
+ }
+
+ /**
+ * The injection site value type
+ */
+ private Type matchType;
+
+ /**
+ * The actual type of of the ParameterizedType matchType
+ */
+ private Type valueType;
+
+ /**
+ * Is valueType actually wrapped in an Optional
+ */
+ private boolean isOptional;
+
+ private boolean isProviderSite;
+
+ private boolean isNonStandard;
+
+ private boolean isJsonValue;
+
+ /**
+ * The injection site @Claim annotation value
+ */
+ private Claim claim;
+
+ /**
+ * The location that share the @Claim/type combination
+ */
+ private HashSet<InjectionPoint> injectionPoints = new HashSet<>();
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tomee/blob/c5964e07/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/MPJWTProducer.java
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/MPJWTProducer.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/MPJWTProducer.java
new file mode 100644
index 0000000..f267437
--- /dev/null
+++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/MPJWTProducer.java
@@ -0,0 +1,196 @@
+/*
+ * 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.cdi;
+
+import org.eclipse.microprofile.jwt.ClaimValue;
+import org.eclipse.microprofile.jwt.JsonWebToken;
+
+import javax.annotation.PostConstruct;
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.context.Destroyed;
+import javax.enterprise.context.Initialized;
+import javax.enterprise.context.RequestScoped;
+import javax.enterprise.event.Observes;
+import javax.enterprise.inject.Produces;
+import javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonValue;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.logging.Logger;
+
+/**
+ * A class that tracks the current validated MP-JWT and associated JsonWebToken via a thread
+ * local to provide a @RequestScoped JsonWebToken producer method.
+ * <p>
+ * It also provides utility methods for access the current JsonWebToken claim values.
+ */
+@ApplicationScoped
+public class MPJWTProducer {
+ private static Logger log = Logger.getLogger(MPJWTProducer.class.getName());
+ private static final String TMP = "tmp";
+ private static ThreadLocal<JsonWebToken> currentPrincipal = new ThreadLocal<>();
+
+ public static void setJWTPrincipal(JsonWebToken principal) {
+ currentPrincipal.set(principal);
+ }
+
+ public static JsonWebToken getJWTPrincpal() {
+ return currentPrincipal.get();
+ }
+
+ @PostConstruct
+ void init() {
+ log.fine("MPJWTProducer initialized");
+ }
+
+ void observeRequestInitialized(@Observes @Initialized(RequestScoped.class) Object event) {
+ log.finest(String.format("observeRequestInitialized, event=%s", event));
+ }
+
+ void observeRequestDestroyed(@Observes @Destroyed(RequestScoped.class) Object event) {
+ log.finest(String.format("observeRequestDestroyed, event=%s", event));
+ }
+
+ /**
+ * The @RequestScoped producer method for the current JsonWebToken
+ *
+ * @return
+ */
+ @Produces
+ @RequestScoped
+ JsonWebToken currentPrincipalOrNull() {
+ return currentPrincipal.get();
+ }
+
+ /**
+ * A utility method for accessing a claim from the current JsonWebToken as a ClaimValue<Optional<T>> object.
+ *
+ * @param name - name of the claim
+ * @param <T> expected actual type of the claim
+ * @return the claim value wrapper object
+ */
+ static <T> ClaimValue<Optional<T>> generalClaimValueProducer(String name) {
+ ClaimValueWrapper<Optional<T>> wrapper = new ClaimValueWrapper<>(name);
+ T value = getValue(name, false);
+ Optional<T> optValue = Optional.ofNullable(value);
+ wrapper.setValue(optValue);
+ return wrapper;
+ }
+
+ /**
+ * Return the indicated claim value as a JsonValue
+ *
+ * @param name - name of the claim
+ * @return a JsonValue wrapper
+ */
+ static JsonValue generalJsonValueProducer(String name) {
+ Object value = getValue(name, false);
+ JsonValue jsonValue = wrapValue(value);
+ return jsonValue;
+ }
+
+ public static <T> T getValue(String name, boolean isOptional) {
+ JsonWebToken jwt = getJWTPrincpal();
+ if (jwt == null) {
+ log.fine(String.format("getValue(%s), null JsonWebToken", name));
+ return null;
+ }
+
+ Optional<T> claimValue = jwt.claim(name);
+ if (!isOptional && !claimValue.isPresent()) {
+ log.fine(String.format("Failed to find Claim for: %s", name));
+ }
+ log.fine(String.format("getValue(%s), isOptional=%s, claimValue=%s", name, isOptional, claimValue));
+ return claimValue.orElse(null);
+ }
+
+ static 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();
+ }
+
+ static JsonValue wrapValue(Object value) {
+ JsonValue jsonValue = null;
+ if (value instanceof JsonValue) {
+ // This may already be a JsonValue
+ jsonValue = (JsonValue) value;
+ } else if (value instanceof String) {
+ jsonValue = Json.createObjectBuilder()
+ .add(TMP, value.toString())
+ .build()
+ .getJsonString(TMP);
+ } else 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 Collection) {
+ JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
+ Collection list = (Collection) value;
+ for (Object element : list) {
+ if (element instanceof String) {
+ arrayBuilder.add(element.toString());
+ } else {
+ JsonValue jvalue = wrapValue(element);
+ arrayBuilder.add(jvalue);
+ }
+ }
+ jsonValue = arrayBuilder.build();
+ } else if (value instanceof Map) {
+ jsonValue = replaceMap((Map) value);
+ }
+ return jsonValue;
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tomee/blob/c5964e07/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/RawClaimTypeProducer.java
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/RawClaimTypeProducer.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/RawClaimTypeProducer.java
new file mode 100644
index 0000000..1ab817e
--- /dev/null
+++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/RawClaimTypeProducer.java
@@ -0,0 +1,69 @@
+/*
+ * 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.cdi;
+
+import org.eclipse.microprofile.jwt.Claim;
+import org.eclipse.microprofile.jwt.ClaimValue;
+import org.eclipse.microprofile.jwt.Claims;
+
+import javax.enterprise.inject.Produces;
+import javax.enterprise.inject.spi.InjectionPoint;
+import javax.inject.Named;
+import java.lang.annotation.Annotation;
+import java.util.Optional;
+import java.util.logging.Logger;
+
+/**
+ *
+ */
+public class RawClaimTypeProducer {
+ private static Logger log = Logger.getLogger(RawClaimTypeProducer.class.getName());
+
+ @Produces
+ @Claim("")
+ @Named("RawClaimTypeProducer#getValue")
+ public Object getValue(InjectionPoint ip) {
+ log.fine(String.format("getValue(%s)", ip));
+ String name = getName(ip);
+ ClaimValue<Optional<Object>> cv = MPJWTProducer.generalClaimValueProducer(name);
+ Optional<Object> value = cv.getValue();
+ Object returnValue = value.orElse(null);
+ return returnValue;
+ }
+
+ @Produces
+ @Claim("")
+ @Named("RawClaimTypeProducer#getOptionalValue")
+ public Optional getOptionalValue(InjectionPoint ip) {
+ log.fine(String.format("getOptionalValue(%s)", ip));
+ String name = getName(ip);
+ ClaimValue<Optional<Object>> cv = MPJWTProducer.generalClaimValueProducer(name);
+ Optional<Object> value = cv.getValue();
+ return value;
+ }
+
+ String getName(InjectionPoint ip) {
+ String name = null;
+ for (Annotation ann : ip.getQualifiers()) {
+ if (ann instanceof Claim) {
+ Claim claim = (Claim) ann;
+ name = claim.standard() == Claims.UNKNOWN ? claim.value() : claim.standard().name();
+ }
+ }
+ return name;
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tomee/blob/c5964e07/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthContextInfo.java
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthContextInfo.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthContextInfo.java
new file mode 100644
index 0000000..ef8c0b0
--- /dev/null
+++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/config/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.config;
+
+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/c5964e07/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthContextInfoProvider.java
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthContextInfoProvider.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthContextInfoProvider.java
new file mode 100644
index 0000000..b810fcf
--- /dev/null
+++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthContextInfoProvider.java
@@ -0,0 +1,61 @@
+/*
+ * 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.config;
+
+import org.apache.tomee.microprofile.jwt.KeyUtils;
+
+import javax.enterprise.context.Dependent;
+import javax.enterprise.inject.Produces;
+import java.security.interfaces.RSAPublicKey;
+import java.util.Optional;
+
+@Dependent
+public class JWTAuthContextInfoProvider {
+
+ @Produces
+ Optional<JWTAuthContextInfo> getOptionalContextInfo() {
+ JWTAuthContextInfo contextInfo = new JWTAuthContextInfo();
+
+ // todo use MP Config to load the configuration
+ contextInfo.setIssuedBy("https://server.example.com");
+ RSAPublicKey pk = null;
+ try {
+ pk = (RSAPublicKey) KeyUtils.decodePublicKey("-----BEGIN RSA PUBLIC KEY-----\n" +
+ "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlivFI8qB4D0y2jy0CfEq\n" +
+ "Fyy46R0o7S8TKpsx5xbHKoU1VWg6QkQm+ntyIv1p4kE1sPEQO73+HY8+Bzs75XwR\n" +
+ "TYL1BmR1w8J5hmjVWjc6R2BTBGAYRPFRhor3kpM6ni2SPmNNhurEAHw7TaqszP5e\n" +
+ "UF/F9+KEBWkwVta+PZ37bwqSE4sCb1soZFrVz/UT/LF4tYpuVYt3YbqToZ3pZOZ9\n" +
+ "AX2o1GCG3xwOjkc4x0W7ezbQZdC9iftPxVHR8irOijJRRjcPDtA6vPKpzLl6CyYn\n" +
+ "sIYPd99ltwxTHjr3npfv/3Lw50bAkbT4HeLFxTx4flEoZLKO/g0bAoV2uqBhkA9x\n" +
+ "nQIDAQAB\n" +
+ "-----END RSA PUBLIC KEY-----\n");
+
+ } catch (final Exception e) {
+ e.printStackTrace();
+ // todo better handling
+ throw new RuntimeException(e);
+ }
+ contextInfo.setSignerKey(pk);
+
+ return Optional.of(contextInfo);
+ }
+
+ @Produces
+ JWTAuthContextInfo getContextInfo() {
+ return getOptionalContextInfo().get();
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tomee/blob/c5964e07/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/principal/DefaultJWTCallerPrincipal.java
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/principal/DefaultJWTCallerPrincipal.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/principal/DefaultJWTCallerPrincipal.java
new file mode 100644
index 0000000..120058d
--- /dev/null
+++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/principal/DefaultJWTCallerPrincipal.java
@@ -0,0 +1,334 @@
+/*
+ * 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.principal;
+
+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 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 the DefaultJWTCallerPrincipal from the parsed JWT token and the extracted principal 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(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 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 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 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/c5964e07/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/principal/DefaultJWTCallerPrincipalFactory.java
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/principal/DefaultJWTCallerPrincipalFactory.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/principal/DefaultJWTCallerPrincipalFactory.java
new file mode 100644
index 0000000..a420dde
--- /dev/null
+++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/principal/DefaultJWTCallerPrincipalFactory.java
@@ -0,0 +1,88 @@
+/*
+ * 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.principal;
+
+import org.apache.tomee.microprofile.jwt.config.JWTAuthContextInfo;
+import org.apache.tomee.microprofile.jwt.ParseException;
+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/c5964e07/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/principal/JWTCallerPrincipal.java
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/principal/JWTCallerPrincipal.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/principal/JWTCallerPrincipal.java
new file mode 100644
index 0000000..26d9406
--- /dev/null
+++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/principal/JWTCallerPrincipal.java
@@ -0,0 +1,59 @@
+/*
+ * 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.principal;
+
+
+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;
+
+ /**
+ * Create a JWTCallerPrincipal with the caller's name
+ *
+ * @param name - caller's name
+ */
+ public JWTCallerPrincipal(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Generate a human readable version of the caller principal and associated JWT.
+ *
+ * @param showAll - should all claims associated with the JWT be displayed or should only those defined in the
+ * JsonWebToken interface be displayed.
+ * @return human readable presentation of the caller principal and associated JWT.
+ */
+ public abstract String toString(boolean showAll);
+
+ 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/c5964e07/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/principal/JWTCallerPrincipalFactory.java
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/principal/JWTCallerPrincipalFactory.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/principal/JWTCallerPrincipalFactory.java
new file mode 100644
index 0000000..3aec7f0
--- /dev/null
+++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/principal/JWTCallerPrincipalFactory.java
@@ -0,0 +1,127 @@
+/*
+ * 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.principal;
+
+import org.apache.tomee.microprofile.jwt.config.JWTAuthContextInfo;
+import org.apache.tomee.microprofile.jwt.ParseException;
+
+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.apache.tomee.microprofile.jwt.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/c5964e07/tck/mp-jwt-embedded/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension b/tck/mp-jwt-embedded/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
index 5e3bccc..d5eea47 100644
--- a/tck/mp-jwt-embedded/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
+++ b/tck/mp-jwt-embedded/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
@@ -1 +1 @@
-org.apache.tomee.microprofile.jwt.MPJWTCDIExtension
\ No newline at end of file
+org.apache.tomee.microprofile.jwt.cdi.MPJWTCDIExtension
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tomee/blob/c5964e07/tck/mp-jwt-embedded/src/main/resources/META-INF/services/org.apache.tomee.microprofile.jwt.JWTCallerPrincipalFactory
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/resources/META-INF/services/org.apache.tomee.microprofile.jwt.JWTCallerPrincipalFactory b/tck/mp-jwt-embedded/src/main/resources/META-INF/services/org.apache.tomee.microprofile.jwt.JWTCallerPrincipalFactory
deleted file mode 100644
index 67f39db..0000000
--- a/tck/mp-jwt-embedded/src/main/resources/META-INF/services/org.apache.tomee.microprofile.jwt.JWTCallerPrincipalFactory
+++ /dev/null
@@ -1 +0,0 @@
-org.apache.tomee.microprofile.jwt.DefaultJWTCallerPrincipalFactory
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tomee/blob/c5964e07/tck/mp-jwt-embedded/src/main/resources/META-INF/services/org.apache.tomee.microprofile.jwt.principal.JWTCallerPrincipalFactory
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/resources/META-INF/services/org.apache.tomee.microprofile.jwt.principal.JWTCallerPrincipalFactory b/tck/mp-jwt-embedded/src/main/resources/META-INF/services/org.apache.tomee.microprofile.jwt.principal.JWTCallerPrincipalFactory
new file mode 100644
index 0000000..21c9831
--- /dev/null
+++ b/tck/mp-jwt-embedded/src/main/resources/META-INF/services/org.apache.tomee.microprofile.jwt.principal.JWTCallerPrincipalFactory
@@ -0,0 +1 @@
+org.apache.tomee.microprofile.jwt.principal.DefaultJWTCallerPrincipalFactory
\ No newline at end of file