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:
There is an authentication endpoint which checks the login credentials.
It issues a token/cookie as a response, and is all good, (until I implemented the injection part)
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) {
}
}
- 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;
}
}
- This should produce a User instance, under variable name AuthenticatedUser.
- 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:
- Creation of bindings (not sure how this would be implemented)
- Something to do with a lot of config files.
- 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.