DropWizard Auth by Example

风流意气都作罢 提交于 2019-11-27 19:24:39
Paul Samsotha

Question 1:

Basic Authentication protocol states the client request should have a header in the form of

Authorization: Basic Base64Encoded(username:password)

where Base64Encoded(username:password) is an actual Base64 encoded string of the username:password. For example, if my username and password are peeskillet:pass, the header should be sent out as

Authorization: Basic cGVlc2tpbGxldDpwYXNz

That being said, the Jersey Client (assuming 1.x) has an HTTPBasicAuthFilter, which is a client side filter, that will handle the encoding part for us. So the client side request might look something like

Client client = Client.create();
WebResource resource = client.resource(BASE_URI);
client.addFilter(new HTTPBasicAuthFilter("peeskillet", "pass"));
String response = resource.get(String.class);

That's all we would need to make a simple GET request with the authorization header.

Question 2:

SimpleCredential: For Basic auth, we would actually be required to use BasicCredentials, instead of our own credentials. Basically, the request will go through the BasicAuthProvider. The provider will parse the Authorization header and create a BasicCredentials object from the parsed username and password. Once that processing has finished, the BasicCredentials will get passed to our SimpleAuthenticator's. We use those credentials to authenticate the user.

SimplePrincipal: is basically what we will use to authorize the client. From the authentication process, we can build a principal, that will be used to authorize later (see Question 3). So an example might look something like

import com.google.common.base.Optional;
import io.dropwizard.auth.AuthenticationException;
import io.dropwizard.auth.Authenticator;
import io.dropwizard.auth.basic.BasicCredentials;

public class SimpleAuthenticator implements Authenticator<BasicCredentials,
                                                          SimplePrincipal> {
    @Override
    public Optional<SimplePrincipal> authenticate(BasicCredentials credentials)
            throws AuthenticationException {

        // Note: this is horrible authentication. Normally we'd use some
        // service to identify the password from the user name.
        if (!"pass".equals(credentials.getPassword())) {
            throw new AuthenticationException("Boo Hooo!");
        }

        // from some user service get the roles for this user
        // I am explicitly setting it just for simplicity
        SimplePrincipal prince = new SimplePrincipal(credentials.getUsername());
        prince.getRoles().add(Roles.ADMIN);

        return Optional.fromNullable(prince);
    }
}

I altered the SimplePrincipal class a bit, and created a simple Roles class.

public class SimplePrincipal {

    private String username;
    private List<String> roles = new ArrayList<>();

    public SimplePrincipal(String username) {
        this.username = username;
    }

    public List<String> getRoles() {
        return roles;
    }

    public boolean isUserInRole(String roleToCheck) {
        return roles.contains(roleToCheck);
    }

    public String getUsername() {
        return username;
    }
}

public class Roles {
    public static final String USER = "USER";
    public static final String ADMIN = "ADMIN";
    public static final String EMPLOYEE = "EMPLOYEE";
}

Question 3:

Some might prefer to have an extra filter layer for authorization, but Dropwizard appears to have the opinionated view that the authorization should occur in the resource class (I forgot exactly where I read it, but I believe their argument is testability). What happens with the SimplePrincial that we created in the SimpleAuthenticator is that it can be injected into our resource method, with the use of the @Auth annotations. We can use the SimplePrincipal to authorize. Something like

import dropwizard.sample.helloworld.security.Roles;
import dropwizard.sample.helloworld.security.SimplePrincipal;
import io.dropwizard.auth.Auth;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

@Path("/simple")
public class SimpleResource {

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Response getResponse(@Auth SimplePrincipal principal) {
        if (!principal.isUserInRole(Roles.ADMIN)) {
            throw new WebApplicationException(Response.Status.FORBIDDEN);
        }
        return Response.ok(
                "{\"Hello\": \"" + principal.getUsername() + "\"}").build();
    }
}

So putting it all together, with this configuration

environment.jersey().register(new BasicAuthProvider<SimplePrincipal>(
                                            new SimpleAuthenticator(), 
                                            "Basic Example Realm")
);

and the client credentials I posted previously, when we make the request, we should get a returned

{"Hello": "peeskillet"}

Also it should be mentioned that Basic auth alone is not secure, and it is recommended to be done over SSL


See Related:


UPDATE

A couple things:

  • For Dropwizard 0.8.x, the configuration of Basic Auth has changed a bit. You can see more here. A simple example would be

    SimpleAuthenticator auth = new SimpleAuthenticator();
    env.jersey().register(AuthFactory.binder(
            new BasicAuthFactory<>(auth,"Example Realm",SimplePrincipal.class)));
    
  • See above link for recommended usage of AuthenticationException

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!