0

I need to apply an authentication filter to just some methods of servlets. The filter checks the validity of a JWT passed as a header key in the request.

For example, the session endpoint has a POST (log in, public) and a DELETE (log out, needs auth) method. Maybe I can split this two into a logout and login servlet but in the case I have resource endpoints that need auth to POST (create) and don't need auth to GET, a filter is impossible to apply and creating two separate servlets for each method is a pain to manage in a large scale app.

Is there a solution to this by just using servlets without any framework?

Apart from this, in the case I would apply the filter to "/secure" path, isn't this really user unfriendly? (To access a url that says "secure" or "public").

3 Answers3

0

In servlets and filter specification, I am not aware of any way to do a mapping actually depending on the request type (GET/POST/PUT/DELETE...). One direct but intrusive way is to pass a string describing the request types that should be processed. In that case, if the request is not in those types, the filter should immediately call next element in filter chain.

If you do not want to change the authentification filter, you could imagine a wrapping filter that take the class of the actual filter as a parameter along with parameters to pass to the real filter. In that case, the wrapping filter will:

  • at initialization time, create an instance of the real filter in initialize it
  • at filter time test is the request is of an acceptable type and
    • if not directly call the FilterChain
    • if yes call the real filter with the current request, response and chain

A possible code for the wrapping filter could be:

public class WrappingFilter implements Filter {
    Filter wrapped;
    List<String> methods;

    @Override
    public void init(final FilterConfig filterConfig) throws ServletException {
        // process FilterConfig parameters "actual_filter" and "methods"
        String classname = filterConfig.getInitParameter("actual_filter");
        String methodsString = filterConfig.getInitParameter("methods");
        methods = Arrays.asList(methodsString.split("|"));
        try {
            // create and initialize actual filter
            Class<?> clazz = Class.forName(classname);
            wrapped = (Filter) clazz.getConstructor().newInstance();
            wrapped.init(filterConfig);            
        } catch (ClassNotFoundException ex) {
            throw new ServletException(ex);
        } catch (NoSuchMethodException ex) {
            throw new ServletException(ex);
        } catch (SecurityException ex) {
            throw new ServletException(ex);
        } catch (InstantiationException ex) {
            throw new ServletException(ex);
        } catch (IllegalAccessException ex) {
            throw new ServletException(ex);
        } catch (IllegalArgumentException ex) {
            throw new ServletException(ex);
        } catch (InvocationTargetException ex) {
            throw new ServletException(ex);
        }
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        String method = ((HttpServletRequest) request).getMethod();
        if (methods.contains(method)) {
            // appropriate method: call wrapped filter
            wrapped.doFilter(request, response, chain);
        }
        else {
            // directly pass request to next element in chain by-passing wrapped filter
            chain.doFilter(request, response);
        }
    }

    @Override
    public void destroy() {
        // destroys wrapped filter
        wrapped.destroy();
    }    
}

The WrappingFilter filter should be normally declared in the web application. It should be passed directly through a FilterConfig in Java initialization, or indirectly through filter_params in Web.xml file :

  • actual_filter: the class name of the actual filter to be wrapped by the WrappingFilter
  • methods: a String giving the methods to process separated with |, for example POST|DELETE
  • other parameters will be passed for the initialization of the wrapped filter

BEWARE: untested...

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • I don't see how it's possible to add a filter (wrapping filter) to a specific servlet passing it a string that describes what methods in that specific servlet should be authenticated. That is your idea right? FilterConfig maybe? –  Apr 04 '17 at 16:34
  • I'm staying away from XML by the way, adding the filters to a context programatically. –  Apr 04 '17 at 16:47
0

If you just need it for authentication perhaps you dont have to write a servlet filter. Specifying a SecurityConstraint either in web.xml or via @ServletSecurity you can restrict the constraint to certain http methods. See e.g. here and there.

But if you really have to write a filter the only way I know is to check the http method using httpRequest.getMethod() in your filter implementation, see eg.this answer.

Community
  • 1
  • 1
frifle
  • 810
  • 5
  • 24
  • So let's say I @ServletSecurity the Session servlet saying that the DELETE method can only be accessed by authenticated requests. The only way is via Roles? I can't understand how I would make the servlet detect the role if the request only has a json web token. –  Apr 04 '17 at 18:01
0

I ended up creating a method addPublicFilter() that applies the filterPublicFilter to the path and methods you pass as arguments.

public class PublicFilter implements Filter {

    private FilterConfig filterConfig;
    public static final String PUBLIC_METHODS = "com.example.access.PUBLIC_METHODS";
    static final String IS_PUBLIC = "com.example.access.IS_PUBLIC";

    public void init(FilterConfig filterConfig) throws ServletException {
        this.filterConfig = filterConfig;
    }

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        final List<String> allowedMethods = Arrays.asList(filterConfig.getInitParameter(PUBLIC_METHODS).split(","));
        final String method = ((HttpServletRequest) request).getMethod();

        if (allowedMethods.contains(method)) request.setAttribute(IS_PUBLIC, true);
        chain.doFilter(request, response);
    }

    public void destroy() {}
}

Then in Jetty I have the method:

private static void addPublicFilter(ServletContextHandler context, String path, String publicMethods) {
    final FilterHolder holder = new FilterHolder(PublicFilter.class);
    holder.setInitParameter(PublicFilter.PUBLIC_METHODS, publicMethods);
    context.addFilter(holder, path, EnumSet.of(DispatcherType.REQUEST));
}

And I call it:

addPublicFilter(context, "/*", "OPTIONS");
addPublicFilter(context, "/user/*", "POST");
addPublicFilter(context, "/session", "POST,GET");