Jersey 2.22 API with token authentication + role based authorization (the way I secured the API is based on the accepted answer from this post: Best practice for REST token-based authentication with JAX-RS and Jersey . It might be better to read it before trying to understand my question) :
here is my web.xml:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!-- LISTENERS -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>JerseySpringServlet</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>ca.toto.api.filters</param-value>
</init-param>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>ca.toto.api.restapi</param-value>
</init-param>
<init-param>
<param-name>jersey.config.server.provider.classnames</param-name>
<param-value>ca.toto.api.filters.AuthenticationFilter;ca.toto.api.filters.AuthorizationFilter;com.toto.api.restapi.TaskRestService</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>JerseySpringServlet</servlet-name>
<url-pattern>/filters/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>JerseySpringServlet</servlet-name>
<url-pattern>/restapi/*</url-pattern>
</servlet-mapping>
When I make a call to my tasks web service, the flow goes in the first filter (AuthenticationFilter) without any problem (@Priority(Priorities.AUTHENTICATION)), validates my token, gets the user from the decoded token then registers it as the Principal then passes in the second filter, AuthorizationFilter (@Priority(Priorities.AUTHORIZATION)) where I get the user from the security context, get his role then check if he has permission to make the call. If yes, exit the filter normally if no, use javax.ws.rs.container.ContainerRequestContext.abortWith method to send a response with status 403:
@Secured
@Provider
@Priority(Priorities.AUTHORIZATION)
public class AuthorizationFilter implements ContainerRequestFilter {
...
try {
boolean isAllowed = false;
// Check if the user is allowed to execute the method
// The method annotations override the class annotations
if (methodRoles.isEmpty()) {
logger.info("Checking permissions on CLASS level");
isAllowed = checkPermissions(classRoles);
} else {
logger.info("Checking permissions on METHOD level");
isAllowed = checkPermissions(methodRoles);
}
// Throw an Exception if the user has not permission to execute the method
if(isAllowed == false) {
logger.warn("USER IS NOT ALLOWED TO COMPLETE THE REQUEST. ABORT.");
requestContext.abortWith(Response.status(Response.Status.FORBIDDEN).build());
}
} catch (Exception e) {
requestContext.abortWith(Response.status(Response.Status.FORBIDDEN).build());
}
When the user has the right role, the service is called and I get the correct response with correct info. My problem is that when my variable isAllowed equals to false, I get a 404 instead of a 403 and I can't figure out why...
here is my TaskRestService service definition:
@Path("/tasks")
@Secured({RoleEnum.admin})
public class TaskRestService {
...
@GET
@Produces(MediaType.APPLICATION_JSON)
@Transactional(readOnly = true)
public List<Task> getTasks(@QueryParam("code") String code) {
... }