starting to structure the project, impl to do
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..49ca981
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+.idea
+*.iml
+target
+
diff --git a/geronimo-jwt-auth-impl/pom.xml b/geronimo-jwt-auth-impl/pom.xml
new file mode 100644
index 0000000..d58ce23
--- /dev/null
+++ b/geronimo-jwt-auth-impl/pom.xml
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="
+ http://maven.apache.org/POM/4.0.0
+ http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>geronimo-jwt-auth</artifactId>
+ <groupId>org.apache.geronimo</groupId>
+ <version>1.0.0-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>geronimo-jwt-auth-impl</artifactId>
+ <name>Geronimo JWT Auth :: Impl</name>
+
+ <properties>
+ <tck.version>1.1-SNAPSHOT</tck.version>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>tomcat-el-api</artifactId>
+ <version>9.0.7</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.microprofile.config</groupId>
+ <artifactId>microprofile-config-api</artifactId>
+ <version>1.2</version>
+ <scope>provided</scope>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.geronimo</groupId>
+ <artifactId>geronimo-microprofile-jwt-auth-spec</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.geronimo.specs</groupId>
+ <artifactId>geronimo-jacc_1.1_spec</artifactId>
+ <version>1.0.2</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.arquillian.testng</groupId>
+ <artifactId>arquillian-testng-container</artifactId>
+ <version>1.1.13.Final</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.testng</groupId>
+ <artifactId>testng</artifactId>
+ <version>6.8.21</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.meecrowave</groupId>
+ <artifactId>meecrowave-arquillian</artifactId>
+ <version>1.2.1</version>
+ <scope>test</scope>
+ <exclusions>
+ <exclusion>
+ <groupId>javax.inject</groupId>
+ <artifactId>javax.inject</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.microprofile.jwt</groupId>
+ <artifactId>microprofile-jwt-auth-tck</artifactId>
+ <version>${tck.version}</version>
+ <scope>test</scope>
+ <exclusions>
+ <exclusion>
+ <groupId>javax.enterprise</groupId>
+ <artifactId>cdi-api</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.microprofile.jwt</groupId>
+ <artifactId>microprofile-jwt-auth-tck</artifactId>
+ <version>${tck.version}</version>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <version>2.21.0</version>
+ <configuration>
+ <suiteXmlFiles>
+ <suiteXmlFile>${project.basedir}/src/test/resources/tck.xml</suiteXmlFile>
+ </suiteXmlFiles>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/JwtException.java b/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/JwtException.java
new file mode 100644
index 0000000..7457d3f
--- /dev/null
+++ b/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/JwtException.java
@@ -0,0 +1,30 @@
+/*
+ * 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.geronimo.microprofile.impl.jwtauth;
+
+public class JwtException extends RuntimeException {
+ private final int status;
+
+ public JwtException(final String msg, final int status) {
+ super(msg);
+ this.status = status;
+ }
+
+ public int getStatus() {
+ return status;
+ }
+}
diff --git a/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/cdi/GeronimoJwtAuthExtension.java b/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/cdi/GeronimoJwtAuthExtension.java
new file mode 100644
index 0000000..8316b65
--- /dev/null
+++ b/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/cdi/GeronimoJwtAuthExtension.java
@@ -0,0 +1,378 @@
+/*
+ * 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.geronimo.microprofile.impl.jwtauth.cdi;
+
+import static java.util.Optional.empty;
+import static java.util.Optional.of;
+import static java.util.Optional.ofNullable;
+import static java.util.function.Function.identity;
+
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collector;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.context.Dependent;
+import javax.enterprise.event.Observes;
+import javax.enterprise.inject.Default;
+import javax.enterprise.inject.Instance;
+import javax.enterprise.inject.spi.AfterBeanDiscovery;
+import javax.enterprise.inject.spi.AfterDeploymentValidation;
+import javax.enterprise.inject.spi.BeforeBeanDiscovery;
+import javax.enterprise.inject.spi.Extension;
+import javax.enterprise.inject.spi.InjectionPoint;
+import javax.enterprise.inject.spi.ProcessAnnotatedType;
+import javax.enterprise.inject.spi.ProcessInjectionPoint;
+import javax.enterprise.util.AnnotationLiteral;
+import javax.enterprise.util.Nonbinding;
+import javax.inject.Provider;
+import javax.json.JsonArray;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonNumber;
+import javax.json.JsonObject;
+import javax.json.JsonString;
+import javax.json.JsonValue;
+import javax.json.spi.JsonProvider;
+import javax.servlet.ServletException;
+
+import org.apache.geronimo.microprofile.impl.jwtauth.servlet.JwtRequest;
+import org.eclipse.microprofile.jwt.Claim;
+import org.eclipse.microprofile.jwt.ClaimValue;
+import org.eclipse.microprofile.jwt.Claims;
+import org.eclipse.microprofile.jwt.JsonWebToken;
+
+public class GeronimoJwtAuthExtension implements Extension {
+ private final ThreadLocal<JwtRequest> request = new ThreadLocal<>();
+
+ private final Collection<Injection> injectionPoints = new HashSet<>(8);
+ private final Collection<Throwable> errors = new ArrayList<>();
+ private JsonProvider json;
+
+ void setClaimMethodsBinding(@Observes final BeforeBeanDiscovery beforeBeanDiscovery) {
+ beforeBeanDiscovery.configureQualifier(Claim.class)
+ .methods().forEach(m -> m.remove(it -> it.annotationType() == Nonbinding.class));
+ json = JsonProvider.provider();
+ }
+
+ void vetoDefaultClaimQualifier(@Observes final ProcessAnnotatedType<Claim> processAnnotatedType) {
+ processAnnotatedType.veto();
+ }
+
+ void captureInjections(@Observes final ProcessInjectionPoint<?, ?> processInjectionPoint) {
+ final InjectionPoint injectionPoint = processInjectionPoint.getInjectionPoint();
+ ofNullable(injectionPoint.getAnnotated().getAnnotation(Claim.class))
+ .flatMap(claim -> createInjection(claim, injectionPoint.getType()))
+ .ifPresent(injectionPoints::add);
+ }
+
+ void addClaimBeans(@Observes final AfterBeanDiscovery afterBeanDiscovery) {
+ afterBeanDiscovery.addBean()
+ .id(GeronimoJwtAuthExtension.class.getName() + "#" + JsonWebToken.class.getName())
+ .beanClass(JsonWebToken.class)
+ .types(JsonWebToken.class, Object.class)
+ .qualifiers(Default.Literal.INSTANCE)
+ .scope(ApplicationScoped.class)
+ .createWith(ctx -> {
+ final JwtRequest request = this.request.get();
+ if (request == null) {
+ throw new IllegalStateException("No JWT in this request");
+ }
+ return request.getToken();
+ });
+
+ injectionPoints.forEach(injection ->
+ afterBeanDiscovery.addBean()
+ .id(GeronimoJwtAuthExtension.class.getName() + "#" + injection.getId())
+ .beanClass(injection.findClass())
+ .qualifiers(injection.literal())
+ .scope(injection.findScope())
+ .types(injection.type, Object.class)
+ .createWith(ctx -> injection.createInstance(request.get())));
+
+ injectionPoints.clear();
+ }
+
+ void afterDeployment(@Observes final AfterDeploymentValidation afterDeploymentValidation) {
+ errors.forEach(afterDeploymentValidation::addDeploymentProblem);
+ }
+
+ private Optional<Injection> createInjection(final Claim claim, final Type type) {
+ if (ParameterizedType.class.isInstance(type)) {
+ final ParameterizedType pt = ParameterizedType.class.cast(type);
+ if (pt.getActualTypeArguments().length == 1) {
+ final Type raw = pt.getRawType();
+ final Type arg = pt.getActualTypeArguments()[0];
+
+ if (raw == Provider.class || raw == Instance.class) {
+ return createInjection(claim, arg);
+ }
+ if (raw == Optional.class) {
+ return createInjection(claim, arg)
+ .map(it -> new Injection(claim.value(), claim.standard(), type) {
+ @Override
+ Object createInstance(final JwtRequest jwtRequest) {
+ return ofNullable(it.createInstance(jwtRequest));
+ }
+ });
+ }
+ if (raw == ClaimValue.class) {
+ final String name = getClaimName(claim);
+ return createInjection(claim, arg)
+ .map(it -> new Injection(claim.value(), claim.standard(), type) {
+ @Override
+ Object createInstance(final JwtRequest jwtRequest) {
+ return new ClaimValue<Object>() {
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public Object getValue() {
+ return it.createInstance(jwtRequest);
+ }
+ };
+ }
+ });
+ }
+ if (Class.class.isInstance(raw) && Collection.class.isAssignableFrom(Class.class.cast(raw))) {
+ return of(new Injection(claim.value(), claim.standard(), type));
+ }
+ }
+ } else if (Class.class.isInstance(type)) {
+ final String name = getClaimName(claim);
+ final Class<?> clazz = Class.class.cast(type);
+ if (JsonValue.class.isAssignableFrom(clazz)) {
+ if (JsonString.class.isAssignableFrom(clazz)) {
+ return of(new Injection(claim.value(), claim.standard(), clazz) {
+ @Override
+ Object createInstance(final JwtRequest jwtRequest) {
+ final Object instance = super.createInstance(jwtRequest);
+ if (JsonString.class.isInstance(instance)) {
+ return instance;
+ }
+ return json.createValue(String.class.cast(instance));
+ }
+ });
+ }
+ if (JsonNumber.class.isAssignableFrom(clazz)) {
+ return of(new Injection(claim.value(), claim.standard(), clazz) {
+ @Override
+ Object createInstance(final JwtRequest jwtRequest) {
+ final Object instance = super.createInstance(jwtRequest);
+ if (JsonNumber.class.isInstance(instance)) {
+ return instance;
+ }
+ return json.createValue(Number.class.cast(instance).doubleValue());
+ }
+ });
+ }
+ if (JsonObject.class.isAssignableFrom(clazz)) {
+ return of(new Injection(claim.value(), claim.standard(), clazz));
+ }
+ if (JsonArray.class.isAssignableFrom(clazz)) {
+ return of(new Injection(claim.value(), claim.standard(), clazz) {
+ @Override
+ Object createInstance(final JwtRequest jwtRequest) {
+ final Object instance = super.createInstance(jwtRequest);
+ if (JsonArray.class.isInstance(instance)) {
+ return instance;
+ }
+ if (Set.class.isInstance(instance)) {
+ return ((Set<String>) instance).stream()
+ .collect(Collector.of(
+ json::createArrayBuilder,
+ JsonArrayBuilder::add,
+ JsonArrayBuilder::addAll,
+ JsonArrayBuilder::build));
+ }
+ throw new IllegalArgumentException("Unsupported value: " + instance);
+ }
+ });
+ }
+ } else {
+ final Class<?> objectType = wrapPrimitives(clazz);
+ if (CharSequence.class.isAssignableFrom(clazz) || Double.class.isAssignableFrom(objectType) ||
+ Long.class.isAssignableFrom(objectType) || Integer.class.isAssignableFrom(objectType)) {
+ return of(new Injection(claim.value(), claim.standard(), objectType));
+ }
+ }
+ }
+ errors.add(new IllegalArgumentException(type + " not supported by JWT-Auth implementation"));
+ return empty();
+ }
+
+ private Class<?> wrapPrimitives(final Class<?> type) {
+ if (long.class == type) {
+ return Long.class;
+ }
+ if (int.class == type) {
+ return Integer.class;
+ }
+ if (double.class == type) {
+ return Double.class;
+ }
+ return type;
+ }
+
+ private static String getClaimName(final Claim claim) {
+ return getClaimName(claim.value(), claim.standard());
+ }
+
+ private static String getClaimName(final String name, final Claims val) {
+ return of(name).filter(s -> !s.isEmpty()).orElse(val.name());
+ }
+
+ public void execute(final JwtRequest req, final ServletRunnable task) throws ServletException, IOException {
+ request.set(req); // we want to track it ourself to support propagation properly when needed
+ try {
+ task.run();
+ } finally {
+ request.remove();
+ }
+ }
+
+ @FunctionalInterface
+ public interface ServletRunnable {
+ void run() throws ServletException, IOException;
+ }
+
+ private static class Injection {
+ private final String name;
+ private final Claims claims;
+ private final Type type;
+ private final int hash;
+ private final Function<Object, Object> transformer;
+ private final String runtimeName;
+
+ private Injection(final String name, final Claims claims, final Type type) {
+ this.name = name;
+ this.claims = claims;
+ this.type = type;
+
+ Function<Object, Object> transformer;
+ try {
+ Claims.valueOf(getClaimName(name, claims));
+ transformer = identity();
+ } catch (final IllegalArgumentException iae) {
+ if (type == String.class) {
+ transformer = val -> JsonString.class.cast(val).getString();
+ } else if (type == Long.class) {
+ transformer = val -> JsonNumber.class.cast(val).longValue();
+ } else {
+ transformer = identity();
+ }
+ }
+ this.transformer = transformer;
+ this.runtimeName = getClaimName(name, claims);
+
+ {
+ int result = name.hashCode();
+ result = 31 * result + claims.hashCode();
+ hash = 31 * result + type.hashCode();
+ }
+ }
+
+ private String getId() {
+ return name + "/" + claims + "/" + type;
+ }
+
+ private Class<?> findClass() {
+ if (Class.class.isInstance(type)) {
+ return Class.class.cast(type);
+ }
+ if (ParameterizedType.class.isInstance(type)) {
+ ParameterizedType current = ParameterizedType.class.cast(type);
+ while (!Class.class.isInstance(current.getRawType())) {
+ current = ParameterizedType.class.cast(current.getRawType());
+ }
+ return Class.class.cast(current.getRawType());
+ }
+ throw new IllegalArgumentException("Can't find a class from " + type);
+ }
+
+ private Class<? extends Annotation> findScope() {
+ return Dependent.class;
+ }
+
+ private Annotation literal() {
+ return new ClaimLiteral(name, claims);
+ }
+
+ Object createInstance(final JwtRequest jwtRequest) {
+ return transformer.apply(jwtRequest.getToken().getClaim(runtimeName));
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final Injection injection = Injection.class.cast(o);
+ return name.equals(injection.name) && claims == injection.claims && type.equals(injection.type);
+ }
+
+ @Override
+ public int hashCode() {
+ return hash;
+ }
+
+ @Override
+ public String toString() {
+ return "Injection{" +
+ "claim='" + ofNullable(name).filter(n -> !n.isEmpty()).orElse(claims.name()) +
+ "', type=" + type +
+ '}';
+ }
+ }
+
+ private static class ClaimLiteral extends AnnotationLiteral<Claim> implements Claim {
+ private final String name;
+ private final Claims claims;
+
+ private ClaimLiteral(final String name, final Claims claims) {
+ this.name = name;
+ this.claims = claims;
+ }
+
+ @Override
+ public String value() {
+ return name;
+ }
+
+ @Override
+ public Claims standard() {
+ return claims;
+ }
+
+ @Override
+ public String toString() {
+ return super.toString().replace(", ", ", ");
+ }
+ }
+}
diff --git a/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/config/GeronimoJwtAuthConfig.java b/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/config/GeronimoJwtAuthConfig.java
new file mode 100644
index 0000000..2a79393
--- /dev/null
+++ b/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/config/GeronimoJwtAuthConfig.java
@@ -0,0 +1,30 @@
+/*
+ * 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.geronimo.microprofile.impl.jwtauth.config;
+
+@FunctionalInterface
+public interface GeronimoJwtAuthConfig {
+ String read(String value, String def);
+
+ static GeronimoJwtAuthConfig create() {
+ try {
+ return new JwtAuthConfigMpConfigImpl();
+ } catch (final NoClassDefFoundError | ExceptionInInitializerError cnfe) {
+ return System::getProperty;
+ }
+ }
+}
diff --git a/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/config/JwtAuthConfigMpConfigImpl.java b/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/config/JwtAuthConfigMpConfigImpl.java
new file mode 100644
index 0000000..6566277
--- /dev/null
+++ b/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/config/JwtAuthConfigMpConfigImpl.java
@@ -0,0 +1,33 @@
+/*
+ * 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.geronimo.microprofile.impl.jwtauth.config;
+
+import org.eclipse.microprofile.config.Config;
+import org.eclipse.microprofile.config.ConfigProvider;
+
+public class JwtAuthConfigMpConfigImpl implements GeronimoJwtAuthConfig {
+ private final Config config;
+
+ public JwtAuthConfigMpConfigImpl() {
+ config = ConfigProvider.getConfig();
+ }
+
+ @Override
+ public String read(final String key, final String def) {
+ return config.getOptionalValue(key, String.class).orElse(def);
+ }
+}
diff --git a/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/jwt/GeronimoJsonWebToken.java b/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/jwt/GeronimoJsonWebToken.java
new file mode 100644
index 0000000..bc009e2
--- /dev/null
+++ b/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/jwt/GeronimoJsonWebToken.java
@@ -0,0 +1,101 @@
+/*
+ * 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.geronimo.microprofile.impl.jwtauth.jwt;
+
+import static java.util.stream.Collectors.toSet;
+
+import java.util.Set;
+import java.util.stream.Stream;
+
+import javax.json.JsonArray;
+import javax.json.JsonNumber;
+import javax.json.JsonObject;
+import javax.json.JsonString;
+import javax.json.JsonValue;
+
+import org.eclipse.microprofile.jwt.Claims;
+import org.eclipse.microprofile.jwt.JsonWebToken;
+
+class GeronimoJsonWebToken implements JsonWebToken {
+ private final JsonObject delegate;
+ private final String raw;
+
+ GeronimoJsonWebToken(final String raw, final JsonObject delegate) {
+ this.raw = raw;
+ this.delegate = delegate;
+ }
+
+ @Override
+ public String getName() {
+ return getClaim(Claims.upn.name());
+ }
+
+ @Override
+ public Set<String> getClaimNames() {
+ return delegate.keySet();
+ }
+
+ @Override
+ public <T> T getClaim(final String claimName) {
+ try {
+ final Claims claim = Claims.valueOf(claimName);
+ if (claim == Claims.raw_token) {
+ return (T) raw;
+ }
+ if (claim.getType() == String.class) {
+ return (T) delegate.getString(claimName);
+ }
+ if (claim.getType() == Long.class) {
+ return (T) Long.valueOf(delegate.getJsonNumber(claimName).longValue());
+ }
+ if (claim.getType() == JsonObject.class) {
+ return (T) delegate.getJsonObject(claimName);
+ }
+ if (claim.getType() == Set.class) {
+ final JsonValue jsonValue = delegate.get(claimName);
+ if (jsonValue.getValueType() == JsonValue.ValueType.ARRAY) {
+ return (T) JsonArray.class.cast(jsonValue).stream()
+ .map(this::toString)
+ .collect(toSet());
+ }
+ if (jsonValue.getValueType() == JsonValue.ValueType.STRING) {
+ return (T) Stream.of(JsonString.class.cast(jsonValue).getString().split(","))
+ .collect(toSet());
+ }
+ return (T) jsonValue;
+ }
+ return (T) delegate.get(claimName);
+ } catch (final IllegalArgumentException iae) {
+ return (T) delegate.get(claimName);
+ }
+ }
+
+ private String toString(final Object value) {
+ if (JsonString.class.isInstance(value)) {
+ return JsonString.class.cast(value).getString();
+ }
+ if (JsonNumber.class.isInstance(value)) {
+ return String.valueOf(JsonNumber.class.cast(value).doubleValue());
+ }
+ return value.toString();
+ }
+
+ @Override
+ public String toString() {
+ return delegate.toString();
+ }
+}
diff --git a/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/jwt/JwtService.java b/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/jwt/JwtService.java
new file mode 100644
index 0000000..c0fc52b
--- /dev/null
+++ b/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/jwt/JwtService.java
@@ -0,0 +1,53 @@
+/*
+ * 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.geronimo.microprofile.impl.jwtauth.jwt;
+
+import static java.util.Collections.emptyMap;
+
+import java.io.ByteArrayInputStream;
+import java.util.Base64;
+
+import javax.annotation.PostConstruct;
+import javax.enterprise.context.ApplicationScoped;
+import javax.json.Json;
+import javax.json.JsonObject;
+import javax.json.JsonReaderFactory;
+
+import org.eclipse.microprofile.jwt.JsonWebToken;
+
+@ApplicationScoped
+public class JwtService {
+ private JsonReaderFactory readerFactory;
+
+ @PostConstruct
+ private void init() {
+ readerFactory = Json.createReaderFactory(emptyMap());
+ }
+
+ public JsonWebToken parse(final String jwt) {
+ // TODO
+ final String[] split = jwt.split("\\.");
+ if (split.length != 3) {
+ // fail
+ }
+ // sign, date validation etc but without lib please, use GeronimoJwtAuthConfig to read how in postconstruct
+
+ final byte[] token = Base64.getUrlDecoder().decode(split[1]);
+ final JsonObject json = readerFactory.createReader(new ByteArrayInputStream(token)).readObject();
+ return new GeronimoJsonWebToken(jwt, json);
+ }
+}
diff --git a/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/servlet/GeronimoJwtAuthFilter.java b/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/servlet/GeronimoJwtAuthFilter.java
new file mode 100644
index 0000000..cb21ab1
--- /dev/null
+++ b/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/servlet/GeronimoJwtAuthFilter.java
@@ -0,0 +1,83 @@
+/*
+ * 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.geronimo.microprofile.impl.jwtauth.servlet;
+
+import java.io.IOException;
+
+import javax.enterprise.inject.spi.CDI;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.geronimo.microprofile.impl.jwtauth.JwtException;
+import org.apache.geronimo.microprofile.impl.jwtauth.cdi.GeronimoJwtAuthExtension;
+import org.apache.geronimo.microprofile.impl.jwtauth.config.GeronimoJwtAuthConfig;
+import org.apache.geronimo.microprofile.impl.jwtauth.jwt.JwtService;
+
+public class GeronimoJwtAuthFilter implements Filter {
+ private String headerName;
+ private String prefix;
+ private JwtService service;
+ private GeronimoJwtAuthExtension extension;
+
+ @Override
+ public void init(final FilterConfig filterConfig) throws ServletException {
+ final GeronimoJwtAuthConfig config = GeronimoJwtAuthConfig.class.cast(filterConfig.getServletContext().getAttribute(GeronimoJwtAuthConfig.class.getName()));
+ headerName = config.read("geronimo.jwt-auth.header.name", "Authorization");
+ prefix = config.read("geronimo.jwt-auth.header.prefix", "bearer") + " ";
+
+ final CDI<Object> current = CDI.current();
+ service = current.select(JwtService.class).get();
+ extension = current.select(GeronimoJwtAuthExtension.class).get();
+ }
+
+ @Override
+ public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
+ if (!HttpServletRequest.class.isInstance(request)) {
+ chain.doFilter(request, response);
+ return;
+ }
+ try {
+ final JwtRequest req = new JwtRequest(service, headerName, prefix, HttpServletRequest.class.cast(request));
+ extension.execute(req, () -> chain.doFilter(req, response));
+ } catch (final Exception e) { // when not used with JAX-RS but directly Servlet
+ Throwable current = e;
+ while (current != null) {
+ if (JwtException.class.isInstance(current)) {
+ final JwtException ex = JwtException.class.cast(current);
+ HttpServletResponse.class.cast(response).sendError(ex.getStatus(), ex.getMessage());
+ return;
+ }
+ if (current == current.getCause()) {
+ break;
+ }
+ current = current.getCause();
+ }
+ throw e;
+ }
+ }
+
+ @Override
+ public void destroy() {
+ // no-op
+ }
+}
diff --git a/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/servlet/GeronimoJwtAuthInitializer.java b/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/servlet/GeronimoJwtAuthInitializer.java
new file mode 100644
index 0000000..6941f78
--- /dev/null
+++ b/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/servlet/GeronimoJwtAuthInitializer.java
@@ -0,0 +1,55 @@
+/*
+ * 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.geronimo.microprofile.impl.jwtauth.servlet;
+
+import static java.util.Optional.ofNullable;
+
+import java.util.Comparator;
+import java.util.EnumSet;
+import java.util.Set;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.FilterRegistration;
+import javax.servlet.ServletContainerInitializer;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.annotation.HandlesTypes;
+import javax.ws.rs.core.Application;
+
+import org.apache.geronimo.microprofile.impl.jwtauth.config.GeronimoJwtAuthConfig;
+import org.eclipse.microprofile.auth.LoginConfig;
+
+@HandlesTypes(LoginConfig.class)
+public class GeronimoJwtAuthInitializer implements ServletContainerInitializer {
+ @Override
+ public void onStartup(final Set<Class<?>> classes, final ServletContext ctx) throws ServletException {
+ final GeronimoJwtAuthConfig appConfig = GeronimoJwtAuthConfig.create();
+ ctx.setAttribute(GeronimoJwtAuthConfig.class.getName(), appConfig);
+
+ ofNullable(classes).filter(c -> !c.isEmpty()).ifPresent(marked -> marked.stream()
+ .filter(Application.class::isAssignableFrom) // needed?
+ .map(it -> it.getAnnotation(LoginConfig.class))
+ .filter(it -> "MP-JWT".equalsIgnoreCase(it.authMethod()))
+ .sorted(Comparator.comparing(LoginConfig::realmName)) // to be deterministic
+ .findFirst()
+ .ifPresent(config -> {
+ final FilterRegistration.Dynamic filter = ctx.addFilter("geronimo-microprofile-jwt-auth-filter", GeronimoJwtAuthFilter.class);
+ filter.setAsyncSupported(true);
+ filter.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), false, appConfig.read("geronimo.jwt-auth.servlet.filter.mapping", "/*"));
+ }));
+ }
+}
diff --git a/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/servlet/JwtRequest.java b/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/servlet/JwtRequest.java
new file mode 100644
index 0000000..a151c06
--- /dev/null
+++ b/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/servlet/JwtRequest.java
@@ -0,0 +1,100 @@
+/*
+ * 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.geronimo.microprofile.impl.jwtauth.servlet;
+
+import static java.util.Collections.emptySet;
+import static java.util.stream.Collectors.toList;
+
+import java.security.Principal;
+import java.util.LinkedHashSet;
+import java.util.Locale;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.function.Supplier;
+
+import javax.security.auth.Subject;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.geronimo.microprofile.impl.jwtauth.JwtException;
+import org.apache.geronimo.microprofile.impl.jwtauth.jwt.JwtService;
+import org.eclipse.microprofile.jwt.JsonWebToken;
+
+public class JwtRequest extends HttpServletRequestWrapper {
+ private final HttpServletRequest delegate;
+ private final Supplier<JsonWebToken> tokenExtractor;
+ private volatile JsonWebToken token; // cache for perf reasons
+
+ JwtRequest(final JwtService service, final String header, final String prefix, final HttpServletRequest request) {
+ super(request);
+ this.delegate = request;
+
+ this.tokenExtractor = () -> {
+ if (token != null) {
+ return token;
+ }
+
+ synchronized (this) {
+ if (token != null) {
+ return token;
+ }
+
+ final String auth = delegate.getHeader(header);
+ if (auth == null || auth.isEmpty()) {
+ throw new JwtException("No " + header + " header", HttpServletResponse.SC_UNAUTHORIZED);
+ }
+ if (!auth.toLowerCase(Locale.ROOT).startsWith(prefix)) {
+ throw new JwtException("No prefix " + prefix + " in header " + header, HttpServletResponse.SC_UNAUTHORIZED);
+ }
+ token = service.parse(auth.substring(prefix.length()));
+ setAttribute(JsonWebToken.class.getName(), token);
+ return token;
+ }
+ };
+
+ // integration hook if needed
+ setAttribute(JsonWebToken.class.getName() + ".supplier", tokenExtractor);
+ // not portable but used by some servers like tomee
+ setAttribute("javax.security.auth.subject.callable", (Callable<Subject>) () -> {
+ final Set<Principal> principals = new LinkedHashSet<>();
+ final JsonWebToken namePrincipal = tokenExtractor.get();
+ principals.add(namePrincipal);
+ principals.addAll(namePrincipal.getGroups().stream().map(role -> (Principal) () -> role).collect(toList()));
+ return new Subject(true, principals, emptySet(), emptySet());
+ });
+ }
+
+ public JsonWebToken getToken() {
+ return tokenExtractor.get();
+ }
+
+ @Override
+ public Principal getUserPrincipal() {
+ return tokenExtractor.get();
+ }
+
+ @Override
+ public boolean isUserInRole(final String role) {
+ return tokenExtractor.get().getGroups().contains(role);
+ }
+
+ @Override
+ public String getAuthType() {
+ return "MP-JWT";
+ }
+}
diff --git a/geronimo-jwt-auth-impl/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension b/geronimo-jwt-auth-impl/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
new file mode 100644
index 0000000..1e16141
--- /dev/null
+++ b/geronimo-jwt-auth-impl/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
@@ -0,0 +1 @@
+org.apache.geronimo.microprofile.impl.jwtauth.cdi.GeronimoJwtAuthExtension
diff --git a/geronimo-jwt-auth-impl/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer b/geronimo-jwt-auth-impl/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer
new file mode 100644
index 0000000..a929cc4
--- /dev/null
+++ b/geronimo-jwt-auth-impl/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer
@@ -0,0 +1 @@
+org.apache.geronimo.microprofile.impl.jwtauth.servlet.GeronimoJwtAuthInitializer
diff --git a/geronimo-jwt-auth-impl/src/test/resources/META-INF/openwebbeans/openwebbeans.properties b/geronimo-jwt-auth-impl/src/test/resources/META-INF/openwebbeans/openwebbeans.properties
new file mode 100644
index 0000000..9cd2756
--- /dev/null
+++ b/geronimo-jwt-auth-impl/src/test/resources/META-INF/openwebbeans/openwebbeans.properties
@@ -0,0 +1,2 @@
+# todo: OWB default is wrong
+org.apache.webbeans.container.InjectionResolver.fastMatching = false
diff --git a/geronimo-jwt-auth-impl/src/test/resources/dev.xml b/geronimo-jwt-auth-impl/src/test/resources/dev.xml
new file mode 100644
index 0000000..b0e1289
--- /dev/null
+++ b/geronimo-jwt-auth-impl/src/test/resources/dev.xml
@@ -0,0 +1,25 @@
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
+<!--
+ Licensed under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<suite name="Microprofile-jwt-auth-TCK (dev)" verbose="1" configfailurepolicy="continue" >
+ <test name="Tests">
+ <classes>
+ <class name="org.eclipse.microprofile.jwt.tck.container.jaxrs.PrimitiveInjectionTest">
+ <methods>
+ <include name="verifyInjectedAudience"/>
+ <include name="verifyInjectedGroups"/>
+ </methods>
+ </class>
+ </classes>
+ </test>
+</suite>
diff --git a/geronimo-jwt-auth-impl/src/test/resources/tck.xml b/geronimo-jwt-auth-impl/src/test/resources/tck.xml
new file mode 100644
index 0000000..b928eff
--- /dev/null
+++ b/geronimo-jwt-auth-impl/src/test/resources/tck.xml
@@ -0,0 +1,47 @@
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
+<!--
+ Licensed under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<suite name="Microprofile-jwt-auth-TCK" verbose="1" configfailurepolicy="continue" >
+ <test name="Tests">
+ <groups>
+ <define name="base-groups">
+ <include name="arquillian" description="Arquillian internal"/>
+ <include name="utils" description="Utility tests"/>
+ <include name="jwt" description="Base JsonWebToken tests"/>
+ <include name="jaxrs" description="JAX-RS invocation tests"/>
+ <include name="cdi" description="Base CDI injection of ClaimValues"/>
+ <include name="cdi-json" description="CDI injection of JSON-P values"/>
+ <include name="cdi-provider" description="CDI injection of javax.inject.Provider values"/>
+ </define>
+ <define name="excludes">
+ <include name="debug" description="Internal debugging tests" />
+ </define>
+ <run>
+ <include name="base-groups" />
+ <exclude name="excludes" />
+ </run>
+ </groups>
+ <classes>
+ <class name="org.eclipse.microprofile.jwt.tck.util.TokenUtilsTest" />
+ <class name="org.eclipse.microprofile.jwt.tck.container.jaxrs.UnsecuredPingTest" />
+ <class name="org.eclipse.microprofile.jwt.tck.container.jaxrs.RequiredClaimsTest" />
+ <class name="org.eclipse.microprofile.jwt.tck.container.jaxrs.ClaimValueInjectionTest" />
+ <class name="org.eclipse.microprofile.jwt.tck.container.jaxrs.JsonValueInjectionTest" />
+ <class name="org.eclipse.microprofile.jwt.tck.container.jaxrs.ProviderInjectionTest" />
+ <class name="org.eclipse.microprofile.jwt.tck.container.jaxrs.RolesAllowedTest" />
+ <class name="org.eclipse.microprofile.jwt.tck.container.jaxrs.InvalidTokenTest" />
+ <class name="org.eclipse.microprofile.jwt.tck.container.jaxrs.PrimitiveInjectionTest" />
+ <class name="org.eclipse.microprofile.jwt.tck.container.jaxrs.PrincipalInjectionTest" />
+ </classes>
+ </test>
+</suite>
diff --git a/geronimo-microprofile-jwt-auth-spec/pom.xml b/geronimo-microprofile-jwt-auth-spec/pom.xml
new file mode 100644
index 0000000..c6622bf
--- /dev/null
+++ b/geronimo-microprofile-jwt-auth-spec/pom.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="
+ http://maven.apache.org/POM/4.0.0
+ http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>geronimo-jwt-auth</artifactId>
+ <groupId>org.apache.geronimo</groupId>
+ <version>1.0.0-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>geronimo-microprofile-jwt-auth-spec</artifactId>
+ <name>Geronimo JWT Auth :: Spec 1.1</name>
+</project>
diff --git a/geronimo-microprofile-jwt-auth-spec/src/main/java/org/eclipse/microprofile/auth/LoginConfig.java b/geronimo-microprofile-jwt-auth-spec/src/main/java/org/eclipse/microprofile/auth/LoginConfig.java
new file mode 100644
index 0000000..c71e6cd
--- /dev/null
+++ b/geronimo-microprofile-jwt-auth-spec/src/main/java/org/eclipse/microprofile/auth/LoginConfig.java
@@ -0,0 +1,34 @@
+/*
+ * 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.eclipse.microprofile.auth;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Inherited
+@Documented
+@Target(TYPE)
+@Retention(RUNTIME)
+public @interface LoginConfig {
+ String authMethod();
+ String realmName() default "";
+}
diff --git a/geronimo-microprofile-jwt-auth-spec/src/main/java/org/eclipse/microprofile/jwt/Claim.java b/geronimo-microprofile-jwt-auth-spec/src/main/java/org/eclipse/microprofile/jwt/Claim.java
new file mode 100644
index 0000000..e17b2c2
--- /dev/null
+++ b/geronimo-microprofile-jwt-auth-spec/src/main/java/org/eclipse/microprofile/jwt/Claim.java
@@ -0,0 +1,40 @@
+/*
+ * 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.eclipse.microprofile.jwt;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import javax.enterprise.util.Nonbinding;
+import javax.inject.Qualifier;
+
+@Qualifier
+@Retention(RUNTIME)
+@Target({FIELD, METHOD, PARAMETER, TYPE})
+public @interface Claim {
+ @Nonbinding
+ String value() default "";
+
+ @Nonbinding
+ Claims standard() default Claims.UNKNOWN;
+}
diff --git a/geronimo-microprofile-jwt-auth-spec/src/main/java/org/eclipse/microprofile/jwt/ClaimValue.java b/geronimo-microprofile-jwt-auth-spec/src/main/java/org/eclipse/microprofile/jwt/ClaimValue.java
new file mode 100644
index 0000000..dcd3c89
--- /dev/null
+++ b/geronimo-microprofile-jwt-auth-spec/src/main/java/org/eclipse/microprofile/jwt/ClaimValue.java
@@ -0,0 +1,25 @@
+/*
+ * 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.eclipse.microprofile.jwt;
+
+import java.security.Principal;
+
+public interface ClaimValue<TYPE> extends Principal {
+ @Override
+ String getName();
+ TYPE getValue();
+}
diff --git a/geronimo-microprofile-jwt-auth-spec/src/main/java/org/eclipse/microprofile/jwt/Claims.java b/geronimo-microprofile-jwt-auth-spec/src/main/java/org/eclipse/microprofile/jwt/Claims.java
new file mode 100644
index 0000000..db1ee1e
--- /dev/null
+++ b/geronimo-microprofile-jwt-auth-spec/src/main/java/org/eclipse/microprofile/jwt/Claims.java
@@ -0,0 +1,89 @@
+/*
+ * 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.eclipse.microprofile.jwt;
+
+import java.util.Set;
+
+import javax.json.JsonObject;
+
+// important: ensure to respect the spec order
+public enum Claims {
+ iss("Issuer", String.class),
+ sub("Subject", String.class),
+ exp("Expiration Time", Long.class),
+ iat("Issued At Time", Long.class),
+ jti("JWT ID", String.class),
+ upn("MP-JWT specific unique principal name", String.class),
+ groups("MP-JWT specific groups permission grant", Set.class),
+ raw_token("MP-JWT specific original bearer token", String.class),
+ aud("Audience", Set.class),
+ nbf("Not Before", Long.class),
+ auth_time("Time when the authentication occurred", Long.class),
+ updated_at("Time the information was last updated", Long.class),
+ azp("Authorized party - the party to which the ID Token was issued", String.class),
+ nonce("Value used to associate a Client session with an ID Token", String.class),
+ at_hash("Access Token hash value", Long.class),
+ c_hash("Code hash value", Long.class),
+ full_name("Full name", String.class),
+ family_name("Surname(s) or last name(s)", String.class),
+ middle_name("Middle name(s)", String.class),
+ nickname("Casual name", String.class),
+ given_name("Given name(s) or first name(s)", String.class),
+ preferred_username("Shorthand name by which the End-User wishes to be referred to", String.class),
+ email("Preferred e-mail address", String.class),
+ email_verified("True if the e-mail address has been verified; otherwise false", Boolean.class),
+ gender("Gender", String.class),
+ birthdate("Birthday", String.class),
+ zoneinfo("Time zone", String.class),
+ locale("Locale", String.class),
+ phone_number("Preferred telephone number", String.class),
+ phone_number_verified("True if the phone number has been verified; otherwise false", Boolean.class),
+ address("Preferred postal address", JsonObject.class),
+ acr("Authentication Context Class Reference", String.class),
+ amr("Authentication Methods References", String.class),
+ sub_jwk("Public key used to check the signature of an ID Token", JsonObject.class),
+ cnf("Confirmation", String.class),
+ sip_from_tag("SIP From tag header field parameter value", String.class),
+ sip_date("SIP Date header field value", String.class),
+ sip_callid("SIP Call-Id header field value", String.class),
+ sip_cseq_num("SIP CSeq numeric header field parameter value", String.class),
+ sip_via_branch("SIP Via branch header field parameter value", String.class),
+ orig("Originating Identity String", String.class),
+ dest("Destination Identity String", String.class),
+ mky("Media Key Fingerprint String", String.class),
+ jwk("JSON Web Key Representing Public Key", JsonObject.class),
+ jwe("Encrypted JSON Web Key", String.class),
+ kid("Key identifier", String.class),
+ jku("JWK Set URL", String.class),
+ UNKNOWN("A catch all for any unknown claim", Void.class);
+
+ private final String description;
+ private final Class<?> type;
+
+ Claims(String description, Class<?> type) {
+ this.description = description;
+ this.type = type;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public Class<?> getType() {
+ return type;
+ }
+}
diff --git a/geronimo-microprofile-jwt-auth-spec/src/main/java/org/eclipse/microprofile/jwt/JsonWebToken.java b/geronimo-microprofile-jwt-auth-spec/src/main/java/org/eclipse/microprofile/jwt/JsonWebToken.java
new file mode 100644
index 0000000..37a292a
--- /dev/null
+++ b/geronimo-microprofile-jwt-auth-spec/src/main/java/org/eclipse/microprofile/jwt/JsonWebToken.java
@@ -0,0 +1,70 @@
+/*
+ * 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.eclipse.microprofile.jwt;
+
+import java.security.Principal;
+import java.util.Optional;
+import java.util.Set;
+
+public interface JsonWebToken extends Principal {
+ @Override
+ String getName();
+
+ default String getRawToken() {
+ return getClaim(Claims.raw_token.name());
+ }
+
+ default String getIssuer() {
+ return getClaim(Claims.iss.name());
+ }
+
+ default Set<String> getAudience() {
+ return getClaim(Claims.aud.name());
+ }
+
+ default String getSubject() {
+ return getClaim(Claims.sub.name());
+ }
+
+ default String getTokenID() {
+ return getClaim(Claims.jti.name());
+ }
+
+ default long getExpirationTime() {
+ return getClaim(Claims.exp.name());
+ }
+
+ default long getIssuedAtTime() {
+ return getClaim(Claims.iat.name());
+ }
+
+ default Set<String> getGroups() {
+ return getClaim(Claims.groups.name());
+ }
+
+ Set<String> getClaimNames();
+
+ default boolean containsClaim(String claimName) {
+ return claim(claimName).isPresent();
+ }
+
+ <T> T getClaim(String claimName);
+
+ default <T> Optional<T> claim(String claimName) {
+ return Optional.ofNullable(getClaim(claimName));
+ }
+}
diff --git a/geronimo-microprofile-jwt-auth-spec/src/main/java/org/eclipse/microprofile/jwt/config/Names.java b/geronimo-microprofile-jwt-auth-spec/src/main/java/org/eclipse/microprofile/jwt/config/Names.java
new file mode 100644
index 0000000..bdae43b
--- /dev/null
+++ b/geronimo-microprofile-jwt-auth-spec/src/main/java/org/eclipse/microprofile/jwt/config/Names.java
@@ -0,0 +1,19 @@
+package org.eclipse.microprofile.jwt.config;
+
+public class Names {
+ public final static String VERIFIER_PUBLIC_KEY = "org.eclipse.microprofile.authentication.JWT.verifierPublicKey";
+
+ public final static String ISSUER = "org.eclipse.microprofile.authentication.JWT.issuer";
+
+ public final static String ISSUERS = "org.eclipse.microprofile.authentication.JWT.issuers";
+
+ public final static String CLOCK_SKEW = "org.eclipse.microprofile.authentication.JWT.clockSkew";
+
+ public final static String VERIFIER_JWKS_URI = "org.eclipse.microprofile.authentication.JWT.VERIFIER_JWKS_URI";
+
+ public final static String VERIFIER_JWKS_REFRESH_INTERVAL = "org.eclipse.microprofile.authentication.JWT.VERIFIER_JWKS_REFRESH_INTERVAL";
+
+ private Names() {
+ throw new AssertionError("don't call me");
+ }
+}
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..0ea6dcf
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="
+ http://maven.apache.org/POM/4.0.0
+ http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <modules>
+ <module>geronimo-microprofile-jwt-auth-spec</module>
+ <module>geronimo-jwt-auth-impl</module>
+ </modules>
+
+ <parent>
+ <groupId>org.apache</groupId>
+ <artifactId>apache</artifactId>
+ <version>18</version>
+ </parent>
+
+ <groupId>org.apache.geronimo</groupId>
+ <artifactId>geronimo-jwt-auth</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ <packaging>pom</packaging>
+ <name>Geronimo JWT Auth</name>
+
+ <description>
+ Apache Geronimo implementation of the Microprofile JWT Auth Specification
+ </description>
+ <url>http://geronimo.apache.org/maven/geronimo-jwt-auth/${project.version}</url>
+
+ <distributionManagement>
+ <site>
+ <id>apache-website</id>
+ <url>${site.deploy.url}/maven/geronimo-jwt-auth/${project.version}</url>
+ </site>
+ </distributionManagement>
+
+ <scm>
+ <connection>scm:git:https://gitbox.apache.org/repos/asf/geronimo-jwt-auth.git</connection>
+ <developerConnection>scm:git:https://gitbox.apache.org/repos/asf/geronimo-jwt-auth.git</developerConnection>
+ <url>https://gitbox.apache.org/repos/asf/geronimo-jwt-auth.git</url>
+ </scm>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>tomcat-servlet-api</artifactId>
+ <version>9.0.7</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.geronimo.specs</groupId>
+ <artifactId>geronimo-jaxrs_2.0_spec</artifactId>
+ <version>1.0-alpha-1</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.geronimo.specs</groupId>
+ <artifactId>geronimo-json_1.1_spec</artifactId>
+ <version>1.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.geronimo.specs</groupId>
+ <artifactId>geronimo-interceptor_1.2_spec</artifactId>
+ <version>1.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.geronimo.specs</groupId>
+ <artifactId>geronimo-atinject_1.0_spec</artifactId>
+ <version>1.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.geronimo.specs</groupId>
+ <artifactId>geronimo-jcdi_2.0_spec</artifactId>
+ <version>1.0.1</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.geronimo.specs</groupId>
+ <artifactId>geronimo-annotation_1.3_spec</artifactId>
+ <version>1.0</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>3.7.0</version>
+ <configuration>
+ <source>1.8</source>
+ <target>1.8</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>