Search code examples
javasslherokujettyrestlet

How to force SSL on some URLs using restlet on Heroku


I have a restlet-based app running on heroku that I want to use SSL. I have enabled the basic "piggyback" ssl option, so it accepts ssl connections -- almost there! However, I want some (actually, probably all) URLs to only work if the user connects via ssl. As it is now (I am told) Heroku converts SSL to non-ssl calls and sets the HTTP_X_FORWARDED_PROTO to https, so I think I need to deal with this in restlet, but I'm not sure how. I see the Redirector class, but it's not clear to me if I can use this class to solve this problem.

Note: I was previously running my app using GAE, which used a servlet container, so I could specify "CONFIDENTIAL" in web.xml.

Thanks!


Solution

  • You can get the information out of the HTTP request headers and then decide if you want to throw an error or redirect:

        Form requestHeaders = (Form) this.getRequest().getAttributes().get("org.restlet.http.headers");
    
        boolean secure = false;
        if (requestHeaders.getValues("x-forwarded-proto") != null) {
            secure = requestHeaders.getValues("x-forwarded-proto").contains("https");
        }
    

    Expanding on this you can create a Filter that can easily be applied to a route. A complete code example is on GitHub. But here is the basic SecureFilter:

    public class SecureFilter extends Filter {
    
        private boolean doRedirect;
    
        public SecureFilter(Context context, Restlet next) {
            super(context);
            doRedirect = false;
            setNext(next);
        }
    
        public SecureFilter(Context context, Restlet next, boolean doRedirect) {
            super(context);
            this.doRedirect = doRedirect;
            setNext(next);
        }
    
        public SecureFilter(Context context, Class<?> next) {
            super(context);
            doRedirect = false;
            setNext(next);
        }
    
        public SecureFilter(Context context, Class<?> next, boolean doRedirect) {
            super(context);
            this.doRedirect = doRedirect;
            setNext(next);
        }
    
        public boolean isDoRedirect() {
            return doRedirect;
        }
    
        public void setDoRedirect(boolean doRedirect) {
            this.doRedirect = doRedirect;
        }
    
        @Override
        protected int beforeHandle(Request request, Response response) {
            Form requestHeaders = (Form) request.getAttributes().get("org.restlet.http.headers");
    
            if ((requestHeaders.getValues("x-forwarded-proto") != null) && (requestHeaders.getValues("x-forwarded-proto").indexOf("https") != 0)) {
                if (doRedirect) {
                    String target = "https://" + request.getHostRef().getHostDomain() + request.getResourceRef().getPath();
                    Redirector redirector = new Redirector(getContext(), target, Redirector.MODE_CLIENT_SEE_OTHER);
                    redirector.handle(request, response);
                    return STOP;
                }
                else {
                    response.setStatus(Status.CLIENT_ERROR_FORBIDDEN);
                    return STOP;
                }
            }
    
            return CONTINUE;
        }
    
    }
    

    To use the SecureFilter, simply wrap the route / resource mappings:

        router.attach("/secure", new SecureFilter(getContext(), HelloSecureResource.class));
        router.attach("/secureWithRedirect", new SecureFilter(getContext(), HelloSecureResource.class, true));