简体   繁体   中英

Jersey ContainerRequestFilter: Getting 404 after throwing 'abortWith' with status forbidden (403)

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) {
... }

You should set this Jersey init-param jersey.config.server.response.setStatusOverSendError to true . Here's what it states in the Javadoc

Whenever response status is 4xx or 5xx it is possible to choose between sendError or setStatus on container specific Response implementation. Eg on servlet container Jersey can call HttpServletResponse.setStatus(...) or HttpServletResponse.sendError(...) .

Calling sendError(...) method usually resets entity, response headers and provide error page for specified status code (eg servlet error-page configuration). However if you want to post-process response (eg by servlet filter) the only way to do it is calling setStatus(...) on container Response object.

If property value is true the method Response.setStatus(...) is used over default Response.sendError(...) .

Type of the property value is boolean . The default value is false .

So what happens is the error cause the container to try and send you to an error page, and when there is non configured, you get a 404. So when you set the property to true , it cause the use of setStatus rather then sendError


NOTE: If you are not using a web.xml, for a ResourceConfig , you can use the property(property, value) method. For an Application subclass, you can override Map<String, Object> getProperties()

public class MyApp extends ResourceConfig {
    public MyApp() {
        property(ServletProperties.RESPONSE_SET_STATUS_OVER_SEND_ERROR, true);
    }
}

public class MyApp extends Application {
    @Override
    public Map<String, Object> getProperties() {
        Map<String, Object> props = new HashMap<>();
        props.put("jersey.config.server.response.setStatusOverSendError", true);
        return props;
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM