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:32 UTC
[28/38] tomee git commit: cleanup and implement EJB integration
cleanup and implement EJB integration
Project: http://git-wip-us.apache.org/repos/asf/tomee/repo
Commit: http://git-wip-us.apache.org/repos/asf/tomee/commit/893525f8
Tree: http://git-wip-us.apache.org/repos/asf/tomee/tree/893525f8
Diff: http://git-wip-us.apache.org/repos/asf/tomee/diff/893525f8
Branch: refs/heads/master
Commit: 893525f860c485b5c5d0a79cb1621de3f56e8013
Parents: 7b0c434
Author: Jean-Louis Monteiro <je...@gmail.com>
Authored: Tue Mar 6 10:13:04 2018 +0100
Committer: Jean-Louis Monteiro <je...@gmail.com>
Committed: Tue Mar 6 10:13:04 2018 +0100
----------------------------------------------------------------------
.../core/security/AbstractSecurityService.java | 9 +-
tck/mp-jwt-embedded/pom.xml | 14 +-
.../tomee/microprofile/jwt/MPJWTContext.java | 129 -----------
.../tomee/microprofile/jwt/MPJWTFilter.java | 230 +++++++++++++++----
.../microprofile/jwt/MPJWTInitializer.java | 15 +-
.../tomee/microprofile/jwt/cdi/ClaimBean.java | 46 ++--
.../microprofile/jwt/cdi/ClaimValueWrapper.java | 10 +-
.../microprofile/jwt/cdi/JsonbProducer.java | 1 +
.../microprofile/jwt/cdi/MPJWTProducer.java | 28 ++-
.../jwt/jaxrs/MPJWPProviderRegistration.java | 46 ++++
.../MPJWTSecurityAnnotationsInterceptor.java | 54 +++++
...TSecurityAnnotationsInterceptorsFeature.java | 144 ++++++++++++
.../META-INF/org.apache.openejb.extension | 1 +
tck/mp-jwt-embedded/src/test/resources/dev.xml | 13 +-
.../tomee/catalina/TomcatSecurityService.java | 20 ++
15 files changed, 532 insertions(+), 228 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/tomee/blob/893525f8/container/openejb-core/src/main/java/org/apache/openejb/core/security/AbstractSecurityService.java
----------------------------------------------------------------------
diff --git a/container/openejb-core/src/main/java/org/apache/openejb/core/security/AbstractSecurityService.java b/container/openejb-core/src/main/java/org/apache/openejb/core/security/AbstractSecurityService.java
index 57e2c9c..514847b 100644
--- a/container/openejb-core/src/main/java/org/apache/openejb/core/security/AbstractSecurityService.java
+++ b/container/openejb-core/src/main/java/org/apache/openejb/core/security/AbstractSecurityService.java
@@ -53,6 +53,7 @@ import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.UUID;
+import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
/**
@@ -153,13 +154,13 @@ public abstract class AbstractSecurityService implements DestroyableResource, Se
final ProvidedSecurityContext providedSecurityContext = newContext.get(ProvidedSecurityContext.class);
SecurityContext securityContext = oldContext != null ? oldContext.get(SecurityContext.class) :
- (providedSecurityContext != null ? providedSecurityContext.context : null);
+ (providedSecurityContext != null ? providedSecurityContext.context : null);
if (providedSecurityContext == null && (securityContext == null || securityContext == defaultContext)) {
final Identity identity = clientIdentity.get();
if (identity != null) {
securityContext = new SecurityContext(identity.subject);
} else {
- securityContext = defaultContext;
+ securityContext = getDefaultContext();
}
}
@@ -398,6 +399,10 @@ public abstract class AbstractSecurityService implements DestroyableResource, Se
}
}
+ protected SecurityContext getDefaultContext() {
+ return defaultContext;
+ }
+
public static final class ProvidedSecurityContext {
public final SecurityContext context;
http://git-wip-us.apache.org/repos/asf/tomee/blob/893525f8/tck/mp-jwt-embedded/pom.xml
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/pom.xml b/tck/mp-jwt-embedded/pom.xml
index 5055ac4..2af8f28 100644
--- a/tck/mp-jwt-embedded/pom.xml
+++ b/tck/mp-jwt-embedded/pom.xml
@@ -40,7 +40,13 @@
<groupId>${project.groupId}</groupId>
<artifactId>openejb-core</artifactId>
<version>${project.version}</version>
- <scope>test</scope>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>openejb-cxf-rs</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
@@ -112,6 +118,12 @@
<artifactId>johnzon-jsonb</artifactId>
<version>1.1.2</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>tomcat-catalina</artifactId>
+ <version>${tomcat.version}</version>
+ <scope>provided</scope>
+ </dependency>
</dependencies>
<build>
http://git-wip-us.apache.org/repos/asf/tomee/blob/893525f8/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTContext.java
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTContext.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTContext.java
deleted file mode 100644
index 50b7d1e..0000000
--- a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTContext.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.tomee.microprofile.jwt;
-
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.function.Predicate;
-
-/**
- * Responsible for holding the runtime model
- */
-public class MPJWTContext {
-
- private static final ConcurrentMap<MPJWTConfigKey, MPJWTConfigValue> configuration = new ConcurrentHashMap<>();
-
- public static MPJWTConfigValue addMapping(final MPJWTConfigKey key, final MPJWTConfigValue value) {
- Objects.requireNonNull(key, "MP JWT Key is required");
- Objects.requireNonNull(value, "MP JWT Value is required");
-
- final MPJWTConfigValue oldValue = configuration.putIfAbsent(key, value);
- if (oldValue != null) {
- throw new IllegalStateException("A mapping has already been defined for the key " + key);
- }
-
- return value;
- }
-
- public static Optional<MPJWTConfigValue> get(final MPJWTConfigKey key) {
- Objects.requireNonNull(key, "MP JWT Key is required to retrieve the configuration");
- return Optional.ofNullable(configuration.get(key));
- }
-
- public static Optional<Map.Entry<MPJWTConfigKey, MPJWTConfigValue>> findFirst(final String path) {
- return configuration.entrySet()
- .stream()
- .filter(new Predicate<ConcurrentMap.Entry<MPJWTConfigKey, MPJWTConfigValue>>() {
- @Override
- public boolean test(final ConcurrentMap.Entry<MPJWTConfigKey, MPJWTConfigValue> e) {
- return path.startsWith(e.getKey().toURI());
- }
- })
- .findFirst();
- }
-
-
- public static class MPJWTConfigValue {
- private final String authMethod;
- private final String realm;
-
- public MPJWTConfigValue(final String authMethod, final String realm) {
- this.authMethod = authMethod;
- this.realm = realm;
- }
-
- public String getAuthMethod() {
- return authMethod;
- }
-
- public String getRealm() {
- return realm;
- }
- }
-
- public static class MPJWTConfigKey {
- private final String contextPath;
- private final String applicationPath;
-
- public MPJWTConfigKey(final String contextPath, final String applicationPath) {
- this.contextPath = contextPath;
- this.applicationPath = applicationPath;
- }
-
- public String getApplicationPath() {
- return applicationPath;
- }
-
- public String getContextPath() {
- return contextPath;
- }
-
- public String toURI() {
- return ("/" + contextPath + "/" + applicationPath).replaceAll("//", "/");
- }
-
- @Override
- public boolean equals(final Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
-
- final MPJWTConfigKey that = (MPJWTConfigKey) o;
-
- if (contextPath != null ? !contextPath.equals(that.contextPath) : that.contextPath != null) return false;
- return !(applicationPath != null ? !applicationPath.equals(that.applicationPath) : that.applicationPath != null);
-
- }
-
- @Override
- public int hashCode() {
- int result = contextPath != null ? contextPath.hashCode() : 0;
- result = 31 * result + (applicationPath != null ? applicationPath.hashCode() : 0);
- return result;
- }
-
- @Override
- public String toString() {
- return "MPJWTConfigKey{" +
- "applicationPath='" + applicationPath + '\'' +
- ", contextPath='" + contextPath + '\'' +
- '}';
- }
- }
-}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tomee/blob/893525f8/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java
index 36e53cf..87ab714 100644
--- a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java
+++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java
@@ -16,13 +16,12 @@
*/
package org.apache.tomee.microprofile.jwt;
-import org.apache.tomee.microprofile.jwt.cdi.MPJWTProducer;
import org.apache.tomee.microprofile.jwt.config.JWTAuthContextInfo;
-import org.apache.tomee.microprofile.jwt.principal.DefaultJWTCallerPrincipalFactory;
import org.apache.tomee.microprofile.jwt.principal.JWTCallerPrincipalFactory;
import org.eclipse.microprofile.jwt.JsonWebToken;
import javax.inject.Inject;
+import javax.security.auth.Subject;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
@@ -33,11 +32,18 @@ import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
import java.io.IOException;
import java.security.Principal;
+import java.util.Collections;
+import java.util.LinkedHashSet;
import java.util.Locale;
-import java.util.Map;
-import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.function.Function;
+import java.util.stream.Collectors;
// async is supported because we only need to do work on the way in
@WebFilter(asyncSupported = true, urlPatterns = "/*")
@@ -56,74 +62,200 @@ public class MPJWTFilter implements Filter {
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
- final Optional<Map.Entry<MPJWTContext.MPJWTConfigKey, MPJWTContext.MPJWTConfigValue>> first =
- MPJWTContext.findFirst(httpServletRequest.getRequestURI());
- if (!first.isPresent()) { // nothing found in the context
- chain.doFilter(request, response);
- return;
- }
+ // now wrap the httpServletRequest and override the principal so CXF can propagate into the SecurityContext
+ try {
+ chain.doFilter(new MPJWTServletRequestWrapper(httpServletRequest, authContextInfo), response);
- // todo get JWT and do validation
- // todo not sure what to do with the realm
+ } catch (final Exception e) {
+ // this is an alternative to the @Provider bellow which requires registration on the fly
+ // or users to add it into their webapp for scanning or into the Application itself
+ if (MPJWTException.class.isInstance(e)) {
+ final MPJWTException jwtException = MPJWTException.class.cast(e);
+ HttpServletResponse.class.cast(response).sendError(jwtException.getStatus(), jwtException.getMessage());
+ }
- final String authorizationHeader = ((HttpServletRequest) request).getHeader("Authorization");
- if (authorizationHeader == null || authorizationHeader.isEmpty()) {
- HttpServletResponse.class.cast(response).sendError(HttpServletResponse.SC_UNAUTHORIZED);
- return;
- }
+ if (MPJWTException.class.isInstance(e.getCause())) {
+ final MPJWTException jwtException = MPJWTException.class.cast(e.getCause());
+ HttpServletResponse.class.cast(response).sendError(jwtException.getStatus(), jwtException.getMessage());
+ }
- if (!authorizationHeader.toLowerCase(Locale.ENGLISH).startsWith("bearer ")) {
- HttpServletResponse.class.cast(response).sendError(HttpServletResponse.SC_UNAUTHORIZED);
- return;
}
- final String token = authorizationHeader.substring("bearer ".length());
- final JsonWebToken jsonWebToken;
- try {
- jsonWebToken = validate(token);
+ }
- } catch (final ParseException e) {
- // todo is this enough?
- HttpServletResponse.class.cast(response).sendError(HttpServletResponse.SC_UNAUTHORIZED);
- return;
- }
+ @Override
+ public void destroy() {
- // associate with the producer. Should not be needed.
- // todo We should be able to retrieve it based on the HTTP Servlet Request in the producer
- MPJWTProducer.setJWTPrincipal(jsonWebToken);
+ }
- // now wrap the httpServletRequest and override the principal so CXF can propagate into the SecurityContext
- chain.doFilter(new HttpServletRequestWrapper(httpServletRequest) {
+ private static Function<HttpServletRequest, JsonWebToken> token(final HttpServletRequest httpServletRequest, final JWTAuthContextInfo authContextInfo) {
+
+ return new Function<HttpServletRequest, JsonWebToken>() {
+
+ private JsonWebToken jsonWebToken;
@Override
- public Principal getUserPrincipal() {
+ public JsonWebToken apply(final HttpServletRequest request) {
+
+ // not sure it's worth having synchronization inside a single request
+ // worth case, we would parse and validate the token twice
+ if (jsonWebToken != null) {
+ return jsonWebToken;
+ }
+
+ final String authorizationHeader = httpServletRequest.getHeader("Authorization");
+ if (authorizationHeader == null || authorizationHeader.isEmpty()) {
+ throw new MissingAuthorizationHeaderException();
+ }
+
+ if (!authorizationHeader.toLowerCase(Locale.ENGLISH).startsWith("bearer ")) {
+ throw new BadAuthorizationPrefixException(authorizationHeader);
+ }
+
+ final String token = authorizationHeader.substring("bearer ".length());
+ try {
+ jsonWebToken = validate(token, authContextInfo);
+
+ } catch (final ParseException e) {
+ throw new InvalidTokenException(token, e);
+ }
+
return jsonWebToken;
- }
- @Override
- public boolean isUserInRole(String role) {
- return jsonWebToken.getGroups().contains(role);
}
+ };
- @Override
- public String getAuthType() {
- return "MP-JWT";
- }
+ }
+
+ private static JsonWebToken validate(final String bearerToken, final JWTAuthContextInfo authContextInfo) throws ParseException {
+ JWTCallerPrincipalFactory factory = JWTCallerPrincipalFactory.instance();
+ return factory.parse(bearerToken, authContextInfo);
+ }
+
+ public static class MPJWTServletRequestWrapper extends HttpServletRequestWrapper {
+
+ private final Function<HttpServletRequest, JsonWebToken> tokenFunction;
+ private final HttpServletRequest request;
+
+ /**
+ * Constructs a request object wrapping the given request.
+ *
+ * @param request The request to wrap
+ * @param authContextInfo the context configuration to validate the token
+ * @throws IllegalArgumentException if the request is null
+ */
+ public MPJWTServletRequestWrapper(final HttpServletRequest request, final JWTAuthContextInfo authContextInfo) {
+ super(request);
+ this.request = request;
+ tokenFunction = token(request, authContextInfo);
+
+ // this is so that the MPJWTProducer can find the function and apply it if necessary
+ request.setAttribute(JsonWebToken.class.getName(), tokenFunction);
+ request.setAttribute("javax.security.auth.subject.callable", new Callable<Subject>() {
+ @Override
+ public Subject call() throws Exception {
+ final Set<Principal> principals = new LinkedHashSet<Principal>();
+ final JsonWebToken namePrincipal = tokenFunction.apply(request);
+ principals.add(namePrincipal);
+ principals.addAll(namePrincipal.getGroups().stream().map(role -> (Principal) () -> role).collect(Collectors.toList()));
+ return new Subject(true, principals, Collections.emptySet(), Collections.emptySet());
+ }
+ });
+ }
+
+ @Override
+ public Principal getUserPrincipal() {
+ return tokenFunction.apply(request);
+ }
- }, response);
+ @Override
+ public boolean isUserInRole(String role) {
+ final JsonWebToken jsonWebToken = tokenFunction.apply(request);
+ return jsonWebToken.getGroups().contains(role);
+ }
+ @Override
+ public String getAuthType() {
+ return "MP-JWT";
+ }
}
- @Override
- public void destroy() {
+ private static abstract class MPJWTException extends RuntimeException {
+
+ public MPJWTException() {
+ super();
+ }
+
+ public MPJWTException(final Throwable cause) {
+ super(cause);
+ }
+ public abstract int getStatus();
+
+ public abstract String getMessage();
}
- protected JsonWebToken validate(String bearerToken) throws ParseException {
- JWTCallerPrincipalFactory factory = JWTCallerPrincipalFactory.instance();
- return factory.parse(bearerToken, authContextInfo);
+ private static class MissingAuthorizationHeaderException extends MPJWTException {
+
+ @Override
+ public int getStatus() {
+ return HttpServletResponse.SC_UNAUTHORIZED;
+ }
+
+ @Override
+ public String getMessage() {
+ return "No authorization header provided. Can't validate the JWT.";
+ }
}
+ private static class BadAuthorizationPrefixException extends MPJWTException {
+
+ private String authorizationHeader;
+
+ public BadAuthorizationPrefixException(final String authorizationHeader) {
+ this.authorizationHeader = authorizationHeader;
+ }
+
+ @Override
+ public int getStatus() {
+ return HttpServletResponse.SC_UNAUTHORIZED;
+ }
+
+ @Override
+ public String getMessage() {
+ return "Authorization header does not use the Bearer prefix. Can't validate header " + authorizationHeader;
+ }
+ }
+
+ private static class InvalidTokenException extends MPJWTException {
+
+ private final String token;
+
+ public InvalidTokenException(final String token, final Throwable cause) {
+ super(cause);
+ this.token = token;
+ }
+
+ @Override
+ public int getStatus() {
+ return HttpServletResponse.SC_UNAUTHORIZED;
+ }
+
+ @Override
+ public String getMessage() {
+ return "Invalid or not parsable JWT " + token; // we might want to break down the exceptions so we can have more messages.
+ }
+ }
+
+ @Provider // would be the ideal but not automatically registered
+ public static class MPJWTExceptionMapper implements ExceptionMapper<MPJWTException> {
+
+ @Override
+ public Response toResponse(final MPJWTException exception) {
+ return Response.status(exception.getStatus()).entity(exception.getMessage()).build();
+ }
+
+ }
}
http://git-wip-us.apache.org/repos/asf/tomee/blob/893525f8/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTInitializer.java
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTInitializer.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTInitializer.java
index 28a1735..bf64601 100644
--- a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTInitializer.java
+++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTInitializer.java
@@ -23,7 +23,6 @@ import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.HandlesTypes;
-import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
import java.util.Set;
@@ -51,23 +50,11 @@ public class MPJWTInitializer implements ServletContainerInitializer {
continue; // do we really want Application?
}
- final ApplicationPath applicationPath = clazz.getAnnotation(ApplicationPath.class);
-
final FilterRegistration.Dynamic mpJwtFilter = ctx.addFilter("mp-jwt-filter", MPJWTFilter.class);
mpJwtFilter.setAsyncSupported(true);
mpJwtFilter.addMappingForUrlPatterns(null, false, "/*");
- MPJWTContext.addMapping(
- new MPJWTContext.MPJWTConfigKey(
- ctx.getContextPath(),
- // todo instead of empty path, we need to look for default value
- applicationPath == null ? "" : applicationPath.value()),
-
- new MPJWTContext.MPJWTConfigValue(
- loginConfig.authMethod(),
- loginConfig.realmName())
- );
-
+ break; // no need to add it more than once
}
}
http://git-wip-us.apache.org/repos/asf/tomee/blob/893525f8/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/ClaimBean.java
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/ClaimBean.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/ClaimBean.java
index 0d3488a..513e271 100644
--- a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/ClaimBean.java
+++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/ClaimBean.java
@@ -22,8 +22,10 @@ import org.eclipse.microprofile.jwt.Claims;
import org.eclipse.microprofile.jwt.JsonWebToken;
import javax.enterprise.context.Dependent;
+import javax.enterprise.context.RequestScoped;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.Instance;
+import javax.enterprise.inject.Vetoed;
import javax.enterprise.inject.spi.Annotated;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
@@ -49,6 +51,7 @@ import java.util.Optional;
import java.util.Set;
import java.util.logging.Logger;
+@Vetoed
public class ClaimBean<T> implements Bean<T>, PassivationCapable {
private static Logger logger = Logger.getLogger(MPJWTCDIExtension.class.getName());
@@ -62,10 +65,14 @@ public class ClaimBean<T> implements Bean<T>, PassivationCapable {
@Inject
private Jsonb jsonb;
+ @Inject
+ private JsonWebToken jsonWebToken;
+
private final BeanManager bm;
private final Class rawType;
private final Set<Type> types;
private final String id;
+ private final Class<? extends Annotation> scope;
public ClaimBean(final BeanManager bm, final Type type) {
this.bm = bm;
@@ -73,6 +80,7 @@ public class ClaimBean<T> implements Bean<T>, PassivationCapable {
types.add(type);
rawType = getRawType(type);
this.id = "ClaimBean_" + types;
+ scope = Dependent.class;
}
private Class getRawType(final Type type) {
@@ -84,8 +92,6 @@ public class ClaimBean<T> implements Bean<T>, PassivationCapable {
return (Class) paramType.getRawType();
}
- // todo deal with Optional here? aka check type again
-
throw new UnsupportedOperationException("Unsupported type " + type);
}
@@ -122,7 +128,7 @@ public class ClaimBean<T> implements Bean<T>, PassivationCapable {
@Override
public Class<? extends Annotation> getScope() {
- return Dependent.class;
+ return scope;
}
@Override
@@ -156,7 +162,6 @@ public class ClaimBean<T> implements Bean<T>, PassivationCapable {
final Claim claim = annotated.getAnnotation(Claim.class);
final String key = getClaimKey(claim);
- System.out.println(String.format("Found Claim injection with name=%s and for InjectionPoint=%s", key, ip.toString()));
logger.finest(String.format("Found Claim injection with name=%s and for %s", key, ip.toString()));
if (annotated.getBaseType() instanceof ParameterizedType) {
@@ -190,20 +195,28 @@ public class ClaimBean<T> implements Bean<T>, PassivationCapable {
final ClaimValueWrapper claimValueWrapper = new ClaimValueWrapper(key);
if (claimValueType instanceof ParameterizedType && isOptional((ParameterizedType) claimValueType)) {
- final T claimValue = getClaimValue(key);
- claimValueWrapper.setValue(Optional.ofNullable(claimValue));
+ claimValueWrapper.setValue(() -> {
+ final T claimValue = getClaimValue(key);
+ return Optional.ofNullable(claimValue);
+ });
} else if (claimValueType instanceof ParameterizedType && isSet((ParameterizedType) claimValueType)) {
- final T claimValue = getClaimValue(key);
- claimValueWrapper.setValue(claimValue); // todo convert to set
+ claimValueWrapper.setValue(() -> {
+ final T claimValue = getClaimValue(key);
+ return claimValue;
+ });
} else if (claimValueType instanceof ParameterizedType && isList((ParameterizedType) claimValueType)) {
- final T claimValue = getClaimValue(key);
- claimValueWrapper.setValue(claimValue); // // todo convert to list
+ claimValueWrapper.setValue(() -> {
+ final T claimValue = getClaimValue(key);
+ return claimValue;
+ });
} else if (claimValueType instanceof Class) {
- final T claimValue = getClaimValue(key);
- claimValueWrapper.setValue(claimValue);
+ claimValueWrapper.setValue(() -> {
+ final T claimValue = getClaimValue(key);
+ return claimValue;
+ });
} else {
throw new IllegalArgumentException("Unsupported ClaimValue type " + claimValueType.toString());
@@ -245,13 +258,16 @@ public class ClaimBean<T> implements Bean<T>, PassivationCapable {
}
private T getClaimValue(final String name) {
- final JsonWebToken jwt = MPJWTProducer.getJWTPrincipal();
- if (jwt == null) {
+ final Bean<?> bean = bm.resolve(bm.getBeans(JsonWebToken.class));
+ if (RequestScoped.class.equals(bean.getScope())) {
+ jsonWebToken = JsonWebToken.class.cast(bm.getReference(bean, JsonWebToken.class, null));
+ }
+ if (jsonWebToken == null || !bean.getScope().equals(RequestScoped.class)) {
logger.warning(String.format("Can't retrieve claim %s. No active principal.", name));
return null;
}
- final Optional<T> claimValue = jwt.claim(name);
+ final Optional<T> claimValue = jsonWebToken.claim(name);
logger.finest(String.format("Found ClaimValue=%s for name=%s", claimValue, name));
return claimValue.orElse(null);
}
http://git-wip-us.apache.org/repos/asf/tomee/blob/893525f8/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
index e0aa68f..a5a4bb5 100644
--- 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
@@ -18,12 +18,14 @@ package org.apache.tomee.microprofile.jwt.cdi;
import org.eclipse.microprofile.jwt.ClaimValue;
+import java.util.function.Supplier;
+
public class ClaimValueWrapper<T> implements ClaimValue<T> {
private final String name;
- private T value;
+ private Supplier<T> value;
- public ClaimValueWrapper(String name) {
+ public ClaimValueWrapper(final String name) {
this.name = name;
}
@@ -34,10 +36,10 @@ public class ClaimValueWrapper<T> implements ClaimValue<T> {
@Override
public T getValue() {
- return value;
+ return value.get();
}
- public void setValue(T value) {
+ void setValue(final Supplier<T> value) {
this.value = value;
}
http://git-wip-us.apache.org/repos/asf/tomee/blob/893525f8/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/JsonbProducer.java
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/JsonbProducer.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/JsonbProducer.java
index 297dfb3..a0434ef 100644
--- a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/JsonbProducer.java
+++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/JsonbProducer.java
@@ -25,6 +25,7 @@ import java.util.logging.Level;
import java.util.logging.Logger;
@ApplicationScoped
+// todo add a qualifier here so we isolate our instance from what applications would do
public class JsonbProducer {
private static Logger log = Logger.getLogger(MPJWTCDIExtension.class.getName());
http://git-wip-us.apache.org/repos/asf/tomee/blob/893525f8/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
index 21196a8..453dcff 100644
--- 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
@@ -21,23 +21,29 @@ import org.eclipse.microprofile.jwt.JsonWebToken;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.inject.Produces;
+import javax.inject.Inject;
+import javax.servlet.http.HttpServletRequest;
+import java.util.Objects;
+import java.util.function.Function;
@ApplicationScoped
public class MPJWTProducer {
- private static ThreadLocal<JsonWebToken> currentPrincipal = new ThreadLocal<>();
-
- public static void setJWTPrincipal(final JsonWebToken principal) {
- currentPrincipal.set(principal);
- }
-
- public static JsonWebToken getJWTPrincipal() {
- return currentPrincipal.get();
- }
+ @Inject
+ private HttpServletRequest httpServletRequest;
@Produces
@RequestScoped
- JsonWebToken currentPrincipal() {
- return currentPrincipal.get();
+ public JsonWebToken currentPrincipal() {
+ Objects.requireNonNull(httpServletRequest, "HTTP Servlet Request is required to produce a JSonWebToken principal.");
+
+ // not very beatiful, but avoids having the MPJWTFilter setting the request or the principal in a thread local
+ // CDI integration already has one - dunno which approach is the best for now
+ final Object tokenAttribute = httpServletRequest.getAttribute(JsonWebToken.class.getName());
+ if (tokenAttribute != null && Function.class.isInstance(tokenAttribute)) {
+ return (JsonWebToken) Function.class.cast(tokenAttribute).apply(httpServletRequest);
+ }
+
+ return null;
}
}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tomee/blob/893525f8/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/jaxrs/MPJWPProviderRegistration.java
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/jaxrs/MPJWPProviderRegistration.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/jaxrs/MPJWPProviderRegistration.java
new file mode 100644
index 0000000..29c146e
--- /dev/null
+++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/jaxrs/MPJWPProviderRegistration.java
@@ -0,0 +1,46 @@
+/*
+ * 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.jaxrs;
+
+import org.apache.openejb.observer.Observes;
+import org.apache.openejb.server.cxf.rs.event.ExtensionProviderRegistration;
+import org.apache.tomee.microprofile.jwt.MPJWTFilter;
+import org.eclipse.microprofile.auth.LoginConfig;
+
+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.ApplicationPath;
+import javax.ws.rs.core.Application;
+import java.util.Set;
+
+/**
+ * OpenEJB/TomEE hack to register a new provider on the fly
+ * Could be package in tomee only or done in another way
+ *
+ * As soon as Roberto is done with the packaging, we can remove all this
+ */
+public class MPJWPProviderRegistration {
+
+ public void registerProvider(@Observes final ExtensionProviderRegistration event) { // openejb hack to register the provider
+ event.getProviders().add(new MPJWTFilter.MPJWTExceptionMapper());
+ event.getProviders().add(new MPJWTSecurityAnnotationsInterceptorsFeature());
+ }
+
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tomee/blob/893525f8/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/jaxrs/MPJWTSecurityAnnotationsInterceptor.java
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/jaxrs/MPJWTSecurityAnnotationsInterceptor.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/jaxrs/MPJWTSecurityAnnotationsInterceptor.java
new file mode 100644
index 0000000..60c2599
--- /dev/null
+++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/jaxrs/MPJWTSecurityAnnotationsInterceptor.java
@@ -0,0 +1,54 @@
+package org.apache.tomee.microprofile.jwt.jaxrs;
+
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.SecurityContext;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.net.HttpURLConnection;
+import java.util.Set;
+import java.util.concurrent.ConcurrentMap;
+
+public class MPJWTSecurityAnnotationsInterceptor implements ContainerRequestFilter {
+
+ private final javax.ws.rs.container.ResourceInfo resourceInfo;
+ private final ConcurrentMap<Method, Set<String>> rolesAllowed;
+ private final Set<Method> denyAll;
+ private final Set<Method> permitAll;
+
+ public MPJWTSecurityAnnotationsInterceptor(final javax.ws.rs.container.ResourceInfo resourceInfo, final ConcurrentMap<Method, Set<String>> rolesAllowed, final Set<Method> denyAll, final Set<Method> permitAll) {
+ this.resourceInfo = resourceInfo;
+ this.rolesAllowed = rolesAllowed;
+ this.denyAll = denyAll;
+ this.permitAll = permitAll;
+ }
+
+ @Override
+ public void filter(final ContainerRequestContext requestContext) throws IOException {
+ if (permitAll.contains(resourceInfo.getResourceMethod())) {
+ return;
+ }
+
+ if (denyAll.contains(resourceInfo.getResourceMethod())) {
+ forbidden(requestContext);
+ return;
+ }
+
+ final Set<String> roles = rolesAllowed.get(resourceInfo.getResourceMethod());
+ if (roles != null && !roles.isEmpty()) {
+ final SecurityContext securityContext = requestContext.getSecurityContext();
+ for (String role : roles) {
+ if (!securityContext.isUserInRole(role)) {
+ forbidden(requestContext);
+ break;
+ }
+ }
+ }
+
+ }
+
+ private void forbidden(final ContainerRequestContext requestContext) {
+ requestContext.abortWith(Response.status(HttpURLConnection.HTTP_FORBIDDEN).build());
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tomee/blob/893525f8/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/jaxrs/MPJWTSecurityAnnotationsInterceptorsFeature.java
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/jaxrs/MPJWTSecurityAnnotationsInterceptorsFeature.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/jaxrs/MPJWTSecurityAnnotationsInterceptorsFeature.java
new file mode 100644
index 0000000..5a0a00a
--- /dev/null
+++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/jaxrs/MPJWTSecurityAnnotationsInterceptorsFeature.java
@@ -0,0 +1,144 @@
+/*
+ * 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.jaxrs;
+
+import javax.annotation.security.DenyAll;
+import javax.annotation.security.PermitAll;
+import javax.annotation.security.RolesAllowed;
+import javax.ws.rs.container.DynamicFeature;
+import javax.ws.rs.container.ResourceInfo;
+import javax.ws.rs.core.FeatureContext;
+import javax.ws.rs.ext.Provider;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+@Provider
+public class MPJWTSecurityAnnotationsInterceptorsFeature implements DynamicFeature {
+
+ private final ConcurrentMap<Method, Set<String>> rolesAllowed = new ConcurrentHashMap<>();
+ private final Set<Method> denyAll = new HashSet<>();
+ private final Set<Method> permitAll = new HashSet<>();
+
+ @Override
+ public void configure(final ResourceInfo resourceInfo, final FeatureContext context) {
+
+ final boolean hasSecurity = processSecurityAnnotations(resourceInfo.getResourceClass(), resourceInfo.getResourceMethod());
+
+ if (hasSecurity) {
+ context.register(new MPJWTSecurityAnnotationsInterceptor(resourceInfo, rolesAllowed, denyAll, permitAll));
+ }
+
+ }
+
+ private boolean processSecurityAnnotations(final Class clazz, final Method method) {
+
+ final List<Class<? extends Annotation>[]> classSecurityAnnotations = hasClassLevelAnnotations(clazz,
+ RolesAllowed.class, PermitAll.class, DenyAll.class);
+
+ final List<Class<? extends Annotation>[]> methodSecurityAnnotations = hasMethodLevelAnnotations(method,
+ RolesAllowed.class, PermitAll.class, DenyAll.class);
+
+ if (classSecurityAnnotations.size() == 0 && methodSecurityAnnotations.size() == 0) {
+ return false; // nothing to do
+ }
+
+ /*
+ * Process annotations at the class level
+ */
+ if (classSecurityAnnotations.size() > 1) {
+ // todo error to properly handle
+ }
+
+ if (methodSecurityAnnotations.size() > 1) {
+ // todo proper error handling
+ }
+
+ if (methodSecurityAnnotations.size() == 0) { // no need to deal with class level annotations if the method has some
+ final RolesAllowed classRolesAllowed = (RolesAllowed) clazz.getAnnotation(RolesAllowed.class);
+ final PermitAll classPermitAll = (PermitAll) clazz.getAnnotation(PermitAll.class);
+ final DenyAll classDenyAll = (DenyAll) clazz.getAnnotation(DenyAll.class);
+
+ if (classRolesAllowed != null) {
+ Set<String> roles = new HashSet<String>();
+ final Set<String> previous = rolesAllowed.putIfAbsent(method, roles);
+ if (previous != null) {
+ roles = previous;
+ }
+ roles.addAll(Arrays.asList(classRolesAllowed.value()));
+ }
+
+ if (classPermitAll != null) {
+ permitAll.add(method);
+ }
+
+ if (classDenyAll != null) {
+ denyAll.add(method);
+ }
+ }
+
+ final RolesAllowed mthdRolesAllowed = (RolesAllowed) method.getAnnotation(RolesAllowed.class);
+ final PermitAll mthdPermitAll = (PermitAll) method.getAnnotation(PermitAll.class);
+ final DenyAll mthdDenyAll = (DenyAll) method.getAnnotation(DenyAll.class);
+
+ if (mthdRolesAllowed != null) {
+ Set<String> roles = new HashSet<String>();
+ final Set<String> previous = rolesAllowed.putIfAbsent(method, roles);
+ if (previous != null) {
+ roles = previous;
+ }
+ roles.addAll(Arrays.asList(mthdRolesAllowed.value()));
+ }
+
+ if (mthdPermitAll != null) {
+ permitAll.add(method);
+ }
+
+ if (mthdDenyAll != null) {
+ denyAll.add(method);
+ }
+
+ return true;
+ }
+
+ private List<Class<? extends Annotation>[]> hasClassLevelAnnotations(final Class clazz, final Class<? extends Annotation>... annotationsToCheck) {
+ final List<Class<? extends Annotation>[]> list = new ArrayList<>();
+ for (Class<? extends Annotation> annotationToCheck : annotationsToCheck) {
+ if (clazz.isAnnotationPresent(annotationToCheck)) {
+ list.add(annotationsToCheck);
+ }
+ }
+ return list;
+ }
+
+ private List<Class<? extends Annotation>[]> hasMethodLevelAnnotations(final Method method, final Class<? extends Annotation>... annotationsToCheck) {
+ final List<Class<? extends Annotation>[]> list = new ArrayList<>();
+ for (Class<? extends Annotation> annotationToCheck : annotationsToCheck) {
+ if (method.isAnnotationPresent(annotationToCheck)) {
+ list.add(annotationsToCheck);
+ }
+ }
+ return list;
+ }
+
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tomee/blob/893525f8/tck/mp-jwt-embedded/src/main/resources/META-INF/org.apache.openejb.extension
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/resources/META-INF/org.apache.openejb.extension b/tck/mp-jwt-embedded/src/main/resources/META-INF/org.apache.openejb.extension
new file mode 100644
index 0000000..9734019
--- /dev/null
+++ b/tck/mp-jwt-embedded/src/main/resources/META-INF/org.apache.openejb.extension
@@ -0,0 +1 @@
+org.apache.tomee.microprofile.jwt.jaxrs.MPJWPProviderRegistration
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tomee/blob/893525f8/tck/mp-jwt-embedded/src/test/resources/dev.xml
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/test/resources/dev.xml b/tck/mp-jwt-embedded/src/test/resources/dev.xml
index 3814456..3700a3f 100644
--- a/tck/mp-jwt-embedded/src/test/resources/dev.xml
+++ b/tck/mp-jwt-embedded/src/test/resources/dev.xml
@@ -21,6 +21,7 @@
<!-- The required base JAX-RS and CDI based tests that all MP-JWT implementations
must pass.
-->
+
<test name="base-tests" verbose="10">
<groups>
<define name="base-groups">
@@ -50,8 +51,9 @@
<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.RolesAllowedTest" />
+ <class name="org.eclipse.microprofile.jwt.tck.container.jaxrs.RolesAllowedTest" />
</classes>
</test>
@@ -73,10 +75,15 @@
</run>
</groups>
<classes>
- <class name="org.eclipse.microprofile.jwt.tck.container.ejb.EjbTest" />
+ <class name="org.eclipse.microprofile.jwt.tck.container.ejb.EjbTest" >
+ <methods>
+ <!-- Excluded cause we never really enforce ACC context for EJB Calls in TomEE -->
+ <exclude name="getSubjectClass"/>
+ </methods>
+ </class>
+
<class name="org.eclipse.microprofile.jwt.tck.container.servlet.ServletTest" />
<class name="org.eclipse.microprofile.jwt.tck.container.jacc.SubjectTest" />
</classes>
</test>
-
</suite>
http://git-wip-us.apache.org/repos/asf/tomee/blob/893525f8/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/TomcatSecurityService.java
----------------------------------------------------------------------
diff --git a/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/TomcatSecurityService.java b/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/TomcatSecurityService.java
index 81bffd4..4076ab5 100644
--- a/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/TomcatSecurityService.java
+++ b/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/TomcatSecurityService.java
@@ -39,6 +39,7 @@ import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Set;
import java.util.UUID;
+import java.util.concurrent.Callable;
public class TomcatSecurityService extends AbstractSecurityService {
private static final boolean ONLY_DEFAULT_REALM = "true".equals(SystemInstance.get().getProperty("tomee.realm.only-default", "false"));
@@ -330,4 +331,23 @@ public class TomcatSecurityService extends AbstractSecurityService {
}
}
+ @Override
+ protected SecurityContext getDefaultContext() {
+ final Request request = OpenEJBSecurityListener.requests.get();
+ if (request != null) {
+ final Object subjectCallable = request.getAttribute("javax.security.auth.subject.callable");
+ if (subjectCallable != null && Callable.class.isInstance(subjectCallable)) {
+ // maybe we should check, but it's so specific ...
+ try {
+ final Subject subject = (Subject) Callable.class.cast(subjectCallable).call();
+ return new SecurityContext(subject);
+
+ } catch (final Exception e) {
+ // ignore and let it go to the default implementation
+ }
+ }
+ }
+
+ return super.getDefaultContext();
+ }
}