Fine-grained Authentication with RESTlet
Asked Answered
H

2

7

I want to expose a resource using RESTlet with a fine-grained authentication. My ServerResource should be accessable via GET only for authenticated members (using BASIC Authentication). However, requests using POST should be available also for callers without any authentication.

In order to clearify: http://path/myapp/user should allow anyone to register using POST, but only registered members should be able to GET a list of all users.

I'm unfortunately not much into RESTlet and I only find examples using coarser authentication for whole Restlets or Routers.

So how do I enable optional authentication for resources and check them on a per-method level?

Thanks in advance!

Hortensiahorter answered 7/2, 2010 at 16:50 Comment(0)
B
16

To do basic authentication in RESTlet 2.0 (I assume you're using 2.0 since you mention ServerResource), you need to use a ChallengeAuthenticator. If this is configured with optional = true then authentication will only be requested if you invoke ChallengeAuthenticator.challenge().

You can create your application with an authenticate() method, and call this whenever you need access to a resource to be secured:

Application:

package example;

import org.restlet.*;
import org.restlet.data.ChallengeScheme;
import org.restlet.routing.Router;
import org.restlet.security.*;

public class ExampleApp extends Application {

    private ChallengeAuthenticator authenticatior;

    private ChallengeAuthenticator createAuthenticator() {
        Context context = getContext();
        boolean optional = true;
        ChallengeScheme challengeScheme = ChallengeScheme.HTTP_BASIC;
        String realm = "Example site";

        // MapVerifier isn't very secure; see docs for alternatives
        MapVerifier verifier = new MapVerifier();
        verifier.getLocalSecrets().put("user", "password".toCharArray());

        ChallengeAuthenticator auth = new ChallengeAuthenticator(context, optional, challengeScheme, realm, verifier) {
            @Override
            protected boolean authenticate(Request request, Response response) {
                if (request.getChallengeResponse() == null) {
                    return false;
                } else {
                    return super.authenticate(request, response);
                }
            }
        };

        return auth;
    }

    @Override
    public Restlet createInboundRoot() {
        this.authenticatior = createAuthenticator();

        Router router = new Router();
        router.attach("/user", UserResource.class);

        authenticatior.setNext(router);
        return authenticatior;
    }

    public boolean authenticate(Request request, Response response) {
        if (!request.getClientInfo().isAuthenticated()) {
            authenticatior.challenge(response, false);
            return false;
        }
        return true;
    }

}

Resource:

package example;

import org.restlet.data.MediaType;
import org.restlet.representation.EmptyRepresentation;
import org.restlet.representation.Representation;
import org.restlet.representation.StringRepresentation;
import org.restlet.resource.ServerResource;

public class UserResource extends ServerResource {

    @Override
    public Representation get() {
        ExampleApp app = (ExampleApp) getApplication();
        if (!app.authenticate(getRequest(), getResponse())) {
            // Not authenticated
            return new EmptyRepresentation();
        }

        // Generate list of users
        // ...
    }     

    @Override
    public Representation post(Representation entity) {
        // Handle post
        // ...
    }

}
Benford answered 8/2, 2010 at 9:41 Comment(7)
First of all thanks for your answer, this looks promising. However, I have some problems getting your code to work. For instance, there is no getSubject() method for ClientInfo (I'm using 2.0m7). Also, I'm not sure if your authenticate() method is correct?Hortensiahorter
I was using an earlier snapshot; I've updated the examples to work with 2.0m7.Benford
Thanks, again, now the code compiles and POST is always available. Unfortunately, GET is never. No matter if I provide no, wrong or correct BASIC credentials, I always get a 401.Hortensiahorter
I've tried it again and the example works for me using RESTlet 2.0m7. Are you running the example, or trying to adapt it to your code?Benford
Ok, I'm sorry, your code works fine! Thank you very much for your sustainable help!Hortensiahorter
Thank you for the example, it was very useful! Just one question: why are you overriding the authenticator to check if the challengeResponse is null?Grace
Thanks for you code sample indeed! it very useful. I do have a question anyway , is your "ExampleApp's authenticate(...)" method really necessary? as you have a "authentcator" defined has predecessor of the "router" in createInRoot()... so i guess it already filters all requests before they can be even reached the router, hence the server's resource...Idealist
T
5

I'm presently using Restlet v2.0.10.

The problem with ChallengeAuthenticator.isOptional() is that it's all or nothing. An alternative to the answer provided by @sea36 above is to override ChallengeAuthenticator.beforeHandle() to either perform authentication or skip it based on request method. For example, the resource below will only require authentication when the GET method is used.

Application:

package example;

import org.restlet.*;
import org.restlet.data.ChallengeScheme;
import org.restlet.routing.Router;
import org.restlet.security.ChallengeAuthenticator;
import org.restlet.security.MapVerifier;

public class ExampleApp extends Application {

    private ChallengeAuthenticator createAuthenticator() {
        Context context = getContext();
        ChallengeScheme challengeScheme = ChallengeScheme.HTTP_BASIC;
        String realm = "Example site";

        // MapVerifier isn't very secure; see docs for alternatives
        MapVerifier verifier = new MapVerifier();
        verifier.getLocalSecrets().put("user", "password".toCharArray());

        ChallengeAuthenticator authOnGet = new ChallengeAuthenticator(context, challengeScheme, realm) {
            @Override
            protected int beforeHandle(Request request, Response response) {
                if (request.getMethod() == Method.GET)
                    return super.beforeHandle(request, response);

                response.setStatus(Status.SUCCESS_OK);
                return CONTINUE;
            }
        };

        return authOnGet;
    }

    @Override
    public Restlet createInboundRoot() {
        ChallengeAuthenticator userResourceWithAuth = createAuthenticator();
        userResourceWithAuth.setNext(UserResource.class);

        Router router = new Router();
        router.attach("/user", userResourceWithAuth);

        return router;
    }

}

Resource:

package example;

import org.restlet.resource.Get;
import org.restlet.resource.Post;
import org.restlet.representation.Representation;
import org.restlet.resource.ServerResource;

public class UserResource extends ServerResource {

    @Get
    public Representation listUsers() {
        // retrieve list of users and generate response
        // ...
    }     

    @Post
    public void register(Representation entity) {
        // handle post
        // ...
    }
}

Note that this example applies the policy of authenticating on GET only to the UserResource and not other resources handled by the router.

Tigerish answered 11/5, 2012 at 1:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.