0

I am following the guide: Best practice for REST token-based authentication with JAX-RS and Jersey, for implementing an Authentication and Authorization filter for my RestAPI. I am getting this one error:

    org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at SystemInjecteeImpl(requiredType=User,parent=AuthorizationFilter,qualifiers={@nl.utwente.di.team26.Security.Authentication.User.AuthenticatedUser()},position=-1,optional=false,self=false,unqualified=null,1372104990)
        at org.jvnet.hk2.internal.ThreeThirtyResolver.resolve(ThreeThirtyResolver.java:51)
        at org.jvnet.hk2.internal.ClazzCreator.resolve(ClazzCreator.java:188)
        at org.jvnet.hk2.internal.ClazzCreator.resolveAllDependencies(ClazzCreator.java:211)
        at org.jvnet.hk2.internal.ClazzCreator.create(ClazzCreator.java:334)
        at org.jvnet.hk2.internal.SystemDescriptor.create(SystemDescriptor.java:463)
        at org.jvnet.hk2.internal.SingletonContext$1.compute(SingletonContext.java:59)
        at org.jvnet.hk2.internal.SingletonContext$1.compute(SingletonContext.java:47)
        at org.glassfish.hk2.utilities.cache.Cache$OriginThreadAwareFuture$1.call(Cache.java:74)
        at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
        at org.glassfish.hk2.utilities.cache.Cache$OriginThreadAwareFuture.run(Cache.java:131)
        at org.glassfish.hk2.utilities.cache.Cache.compute(Cache.java:176)
        at org.jvnet.hk2.internal.SingletonContext.findOrCreate(SingletonContext.java:98)
        at org.jvnet.hk2.internal.Utilities.createService(Utilities.java:2102)
        at org.jvnet.hk2.internal.ServiceHandleImpl.getService(ServiceHandleImpl.java:93)
        at org.jvnet.hk2.internal.ServiceHandleImpl.getService(ServiceHandleImpl.java:67)
        at org.glassfish.jersey.inject.hk2.AbstractHk2InjectionManager.lambda$getAllServiceHolders$0(AbstractHk2InjectionManager.java:136)
        at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)
        at java.base/java.util.LinkedList$LLSpliterator.forEachRemaining(LinkedList.java:1239)
        at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
        at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
        at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913)
        at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
        at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578)
        at org.glassfish.jersey.inject.hk2.AbstractHk2InjectionManager.getAllServiceHolders(AbstractHk2InjectionManager.java:140)
        at org.glassfish.jersey.inject.hk2.ImmediateHk2InjectionManager.getAllServiceHolders(ImmediateHk2InjectionManager.java:30)
        at org.glassfish.jersey.internal.inject.Providers.getServiceHolders(Providers.java:299)
        at org.glassfish.jersey.internal.inject.Providers.getAllRankedProviders(Providers.java:182)
        at org.glassfish.jersey.server.ProcessingProvidersConfigurator.postInit(ProcessingProvidersConfigurator.java:95)
        at org.glassfish.jersey.server.ApplicationHandler.lambda$initialize$2(ApplicationHandler.java:349)
        at java.base/java.util.Arrays$ArrayList.forEach(Arrays.java:4411)
        at org.glassfish.jersey.server.ApplicationHandler.initialize(ApplicationHandler.java:349)
        at org.glassfish.jersey.server.ApplicationHandler.lambda$initialize$1(ApplicationHandler.java:293)

In essence:

  1. There is an authentication endpoint which checks the login credentials.

  2. It issues a token/cookie as a response, and is all good, (until I implemented the injection part)

  3. So when the makes a request the request is first inspected by this Authentication Filter:

@Secured
@Provider
@Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {

    @Inject
    @AuthenticatedUser
    Event<String> userAuthenticatedEvent;

    @Context
    UriInfo uriInfo;

    String userId;

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {

        Map<String, Cookie> cookieJar = requestContext.getCookies();

        // Validate the Cookie Token
            try {
                if (!hasCookie(cookieJar)) {
                sendToLogin(requestContext);
                } else {
                    // Extract the token from the Cookie
                    String token = cookieJar.get(CONSTANTS.COOKIENAME).getValue();

                    try {
                        // Validate the token
                        validateToken(token);
                        userAuthenticatedEvent.fire(userId);
                    } catch (TokenObsoleteException e) {
                        e.printStackTrace();
                        sendToLogin(requestContext);
                    } catch (AuthenticationDeniedException e) {
                        e.printStackTrace();
                        abortWithUnauthorized(requestContext);
                    }
                }
            } catch (URISyntaxException e) {
                e.printStackTrace();
            }

    }

    private boolean hasCookie(Map<String, Cookie> cookieJar) {

        // Check if the Authorization header is valid
        // It must not be null and must be prefixed with "Bearer" plus a whitespace
        // The authentication scheme comparison must be case-insensitive
        return cookieJar.containsKey(CONSTANTS.COOKIENAME);
    }

    private void sendToLogin(ContainerRequestContext requestContext) throws URISyntaxException {
        requestContext.abortWith(Response.seeOther(new URI("http://localhost:8080/apiName/")).build());
    }


    private void abortWithUnauthorized(ContainerRequestContext requestContext) {

        // Abort the filter chain with a 401 status code response
        requestContext.abortWith(
                Response.status(Response.Status.UNAUTHORIZED).build());
    }

    private void validateToken(String token) throws AuthenticationDeniedException, TokenObsoleteException {
        // Check if the token was issued by the server and if it's not expired
        // Throw an Exception if the token is invalid

    }

    public static Claims decodeJWT(String jwt) {

    }
}
  1. This filter fires an event such that an Authenticated user can be created.
@RequestScoped
public class AuthenticatedUserProducer {

    @Produces
    @RequestScoped
    @AuthenticatedUser
    private User authenticatedUser;

    UserDao userDao = new UserDao();

    @RequestScoped
    public void handleAuthenticationEvent(@Observes @AuthenticatedUser String userId) {
        int user = Integer.parseInt(userId);
        this.authenticatedUser = findUser(user);
    }

    private User findUser(int userId) {
        User user = null;
        // Hit the the database or a service to find a user by its username and return it
        // Return the User instance
        return user;
    }
}
  1. This should produce a User instance, under variable name AuthenticatedUser.
  2. But, when the same code is called for checking User Roles, in the Authorization filter, the error above is shown: org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at SystemInjecteeImpl(requiredType=User,parent=AuthorizationFilter,qualifiers={@nl.utwente.di.team26.Security.Authentication.User.AuthenticatedUser()},position=-1,optional=false,self=false,unqualified=null,1372104990)

The Authorization Filter is:

@Secured
@Provider
@Priority(Priorities.AUTHORIZATION)
public class AuthorizationFilter implements ContainerRequestFilter {

    @Context
    private ResourceInfo resourceInfo;

    @Inject
    @AuthenticatedUser
    User authenticatedUser; //<--This is where the error comes from.

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {

        // Get the resource class which matches with the requested URL
        // Extract the roles declared by it
        Class<?> resourceClass = resourceInfo.getResourceClass();
        List<Role> classRoles = extractRoles(resourceClass);

        // Get the resource method which matches with the requested URL
        // Extract the roles declared by it
        Method resourceMethod = resourceInfo.getResourceMethod();
        List<Role> methodRoles = extractRoles(resourceMethod);

        try {

            // Check if the user is allowed to execute the method
            // The method annotations override the class annotations
            if (methodRoles.isEmpty()) {
                checkPermissions(classRoles);
            } else {
                checkPermissions(methodRoles);
            }

        } catch (NotAuthorizedException e) {
            requestContext.abortWith(
                    Response.status(Response.Status.FORBIDDEN).build());
        }
    }

    // Extract the roles from the annotated element
    private List<Role> extractRoles(AnnotatedElement annotatedElement) {
        if (annotatedElement == null) {
            return new ArrayList<>();
        } else {
            Secured secured = annotatedElement.getAnnotation(Secured.class);
            if (secured == null) {
                return new ArrayList<>();
            } else {
                Role[] allowedRoles = secured.value();
                return Arrays.asList(allowedRoles);
            }
        }
    }

    private void checkPermissions(List<Role> allowedRoles) throws NotAuthorizedException {
        // Check if the user contains one of the allowed roles
        // Throw an Exception if the user has not permission to execute the method
        if (authenticatedUser.getClarificationLevel() < allowedRoles.get(0).ordinal()) {
            throw new NotAuthorizedException("You shall not pass!");
        }
    }
}

Some other code: @Secured Annotation

@NameBinding
@Retention(RUNTIME)
@Target({TYPE, METHOD})
public @interface Secured {
    Role[] value() default {};
}

Authenticated User Annotation

@Qualifier
@Retention(RUNTIME)
@Target({ METHOD, FIELD, PARAMETER })
public @interface AuthenticatedUser { }

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
         http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">

    <welcome-file-list>
        <welcome-file>login.html</welcome-file>
    </welcome-file-list>

    <servlet>
        <servlet-name>javax.ws.rs.core.Application</servlet-name>
    </servlet>
    <servlet-mapping>
        <servlet-name>javax.ws.rs.core.Application</servlet-name>
        <url-pattern>/rest/*</url-pattern>
    </servlet-mapping>

</web-app>

POM

<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">
    <modelVersion>4.0.0</modelVersion>
    <groupId>nl.utwente.di.app</groupId>
    <artifactId>app</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <release>11</release>
                </configuration>
            </plugin>

            <plugin>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <filteringDeploymentDescriptors>true</filteringDeploymentDescriptors>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.6.1</version>
        </dependency>

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>5.6.1</version>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.glassfish.jersey.containers/jersey-container-servlet -->
        <dependency>
            <groupId>org.glassfish.jersey.containers</groupId>
            <artifactId>jersey-container-servlet</artifactId>
            <version>2.30.1</version>
        </dependency>

        <dependency>
            <groupId>org.glassfish.jersey.inject</groupId>
            <artifactId>jersey-hk2</artifactId>
            <version>2.30.1</version>
        </dependency>

        <!-- Required only when you are using JAX-RS Client -->
        <dependency>
            <groupId>org.glassfish.jersey.core</groupId>
            <artifactId>jersey-client</artifactId>
            <version>2.30.1</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.glassfish.jersey.media/jersey-media-json-jackson -->
        <dependency>
            <groupId>org.glassfish.jersey.media</groupId>
            <artifactId>jersey-media-json-jackson</artifactId>
            <version>2.29.1</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/jakarta.xml.bind/jakarta.xml.bind-api -->
        <dependency>
            <groupId>jakarta.xml.bind</groupId>
            <artifactId>jakarta.xml.bind-api</artifactId>
            <version>2.3.3</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.glassfish.jaxb/jaxb-runtime -->
        <dependency>
            <groupId>org.glassfish.jaxb</groupId>
            <artifactId>jaxb-runtime</artifactId>
            <version>2.3.3</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.googlecode.json-simple/json-simple  -->
        <dependency>
            <groupId>com.googlecode.json-simple</groupId>
            <artifactId>json-simple</artifactId>
            <version>1.1.1</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.postgresql/postgresql -->
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <version>42.2.12</version>
        </dependency>

        <!-- For the cookie things-->
        <dependency>
            <groupId>javax.enterprise</groupId>
            <artifactId>cdi-api</artifactId>
            <version>2.0.SP1</version>
        </dependency>

        <!-- For the token things -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.11.1</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.11.1</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
            <version>0.11.1</version>
            <scope>runtime</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.glassfish.jersey.containers.glassfish/jersey-gf-cdi -->
        <dependency>
            <groupId>org.glassfish.jersey.containers.glassfish</groupId>
            <artifactId>jersey-gf-cdi</artifactId>
            <version>2.14</version>
            <scope>provided</scope>
        </dependency>

    </dependencies>
</project>

I have a beans.xml after searching a bit this was recommended, but I am not sure how it works.

<?xml version="1.0" encoding="UTF-8"?>
<beans
        xmlns="http://xmlns.jcp.org/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
                      http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
        bean-discovery-mode="all">
</beans>

Other things I have read:

  1. Creation of bindings (not sure how this would be implemented)
  2. Something to do with a lot of config files.
  3. I am not using SpringBoot or other fancy things. Only simple Jax-rs, Jersey 2.30.1 and some things regarding tokens like jwt.

By reading the error, it seems only that the injected User is not present, so the program has nothing there to inject, but if the class is present why id the DependencyUnsatisfied?

Have I defined the injection properly? Have I defined the name bindings properly? Have I used the right maven imports?

Can anyone help? Feel free to ask any further questions.

Thanks.

  • As mentioned in other post, are you using correct Annotation javax.enterprise.inject.Produces ? – Nagaraddi Jun 09 '20 at 16:00
  • Yes, I indeed am. – Puru Vaish Jun 09 '20 at 19:05
  • You know there are frameworks that do all this for you? Based on standards. For me **that** is best practice. Although I understand this is a dutch university project (contest?) – Kukeltje Jun 09 '20 at 21:26
  • Does it fail deploytime? Then it might be scope related, if the filter needs something to be injected at creation and the user us only 'produced' at request scope there is nothing – Kukeltje Jun 09 '20 at 21:44
  • Yes the error is shown right after I deploy the project. So am I using a wrong annotation? How should I change it? – Puru Vaish Jun 10 '20 at 05:53

0 Answers0