0

I'm new to dropwizard and i'm trying to create an Authenticator which gets credentials from the user, then it uses the rest api getUser method which i implemented in my UserResouce class to get the user with the username that is in the credentials from the db users table. However in my autheticator class i having troubles in figuring out how to use the user resource functions to get the user.

I was trying to do something like that:

public List<com.amitbaz.tss.db.User> getUsersFromDB(String username){

    SessionFactory sessionFactory = TradingSystemServerApplication.hibernateBundle.getSessionFactory();
    UserDAO userDAO = new UserDAO(sessionFactory);
    List<com.amitbaz.tss.db.User> user = userDAO.getUser(username);
    logger.debug(user.toString());
    return user;
}

inside the autheticator and call it from the authenticte function but it says there is no session bound...

EDIT:

Ok so after much thinking i got to this: I'm dropwizard authenticator and authorizer implementions with BasicCredentials and.

Autheticator (don't mind the VALID_USER thing..):

package com.amitbaz.tss.auth;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import javax.persistence.NamedQuery;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.amitbaz.tss.TradingSystemServerApplication;
import com.amitbaz.tss.db.UserDAO;
import com.amitbaz.tss.db.UserResource;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;

import io.dropwizard.auth.AuthenticationException;
import io.dropwizard.auth.Authenticator;
import io.dropwizard.auth.basic.BasicCredentials;

public class TradingSystemServerAuthenticator implements Authenticator<BasicCredentials, User> {

    private Logger logger = LoggerFactory.getLogger(TradingSystemServerAuthenticator.class);

    private static final Map<String, Set<String>> VALID_USERS = ImmutableMap.of(
            "guest", ImmutableSet.of(),
            "amit", ImmutableSet.of("admin"),
            "stav", ImmutableSet.of("broker")
        );

    private UserDAO userDAO;

    public TradingSystemServerAuthenticator(UserDAO userDAO) {
        // TODO Auto-generated constructor stub
        this.userDAO = userDAO;
    }

    @Override
    public Optional<User> authenticate(BasicCredentials credentials) throws AuthenticationException {
        // TODO Auto-generated method stub
        List<com.amitbaz.tss.db.User> user = userDAO.getUser(credentials.getUsername());
        logger.debug(user.toString());
        if("amit".equals(credentials.getPassword())){
            return Optional.of(new User(credentials.getUsername(), VALID_USERS.get(credentials.getUsername())));
        }
        return Optional.empty();
    }

}

Authorizer:

package com.amitbaz.tss.auth;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.amitbaz.tss.db.UserDAO;

import io.dropwizard.auth.Authorizer;

public class TradingSystemServerAuthorizer implements Authorizer<User>{

    private Logger logger = LoggerFactory.getLogger(TradingSystemServerAuthorizer.class);

    private UserDAO userDAO;



    public TradingSystemServerAuthorizer(UserDAO userDAO) {
        super();
        this.userDAO = userDAO;
    }



    @Override
    public boolean authorize(User user, String role) {
        // TODO Auto-generated method stub
        logger.debug(userDAO.getUser(user.getName()).toString());
        return user.getName().equals("amit") && user.getRole().contains(new String("admin"));
    }

}

Now, In my Application class I do this:

package com.amitbaz.tss;


import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.amitbaz.tss.auth.TradingSystemServerAuthenticator;
import com.amitbaz.tss.auth.TradingSystemServerAuthorizer;
import com.amitbaz.tss.auth.User;
import com.amitbaz.tss.db.Broker;
import com.amitbaz.tss.db.BrokerDAO;
import com.amitbaz.tss.db.BrokerResource;
import com.amitbaz.tss.db.Contact;
import com.amitbaz.tss.db.ContactDAO;
import com.amitbaz.tss.db.ContactResource;
import com.amitbaz.tss.db.Product;
import com.amitbaz.tss.db.ProductDAO;
import com.amitbaz.tss.db.ProductResource;
import com.amitbaz.tss.db.Test;
import com.amitbaz.tss.db.TestDAO;
import com.amitbaz.tss.db.TestResource;
import com.amitbaz.tss.db.Transaction;
import com.amitbaz.tss.db.TransactionDAO;
import com.amitbaz.tss.db.TransactionResource;
import com.amitbaz.tss.db.UserDAO;
import com.amitbaz.tss.db.UserResource;
import com.amitbaz.tss.db.UserRole;
import com.amitbaz.tss.db.UserRoleDAO;
import com.amitbaz.tss.db.UserRoleResource;
import com.amitbaz.tss.db.Website;
import com.amitbaz.tss.db.WebsiteDAO;
import com.amitbaz.tss.db.WebsiteResource;

import io.dropwizard.Application;
import io.dropwizard.auth.AuthDynamicFeature;
import io.dropwizard.auth.AuthValueFactoryProvider;
import io.dropwizard.auth.basic.BasicCredentialAuthFilter;
import io.dropwizard.db.DataSourceFactory;
import io.dropwizard.hibernate.HibernateBundle;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;
import javassist.tools.web.Webserver;

public class TradingSystemServerApplication extends Application<TradingSystemServerConfiguration>{

    public static void main(String[] args) throws Exception{
        new TradingSystemServerApplication().run(args);
    }

    public final static HibernateBundle<TradingSystemServerConfiguration> hibernateBundle
    = new HibernateBundle<TradingSystemServerConfiguration>(
            Test.class,Broker.class, com.amitbaz.tss.db.User.class, UserRole.class
            ,Product.class, Transaction.class, Website.class, Contact.class
    ) {
        @Override
        public DataSourceFactory getDataSourceFactory(
                TradingSystemServerConfiguration configuration
        ) {
            return configuration.getDataSourceFactory();
        }
    };

    final Logger logger = LoggerFactory.getLogger(TradingSystemServerApplication.class);

    @Override
    public void initialize(
            final Bootstrap<TradingSystemServerConfiguration> bootstrap) {
        bootstrap.addBundle(hibernateBundle);
    }

    @Override
    public void run(TradingSystemServerConfiguration config, Environment env) throws Exception {
        final UserDAO userDAO = new UserDAO(hibernateBundle.getSessionFactory());
        final UserRoleDAO userRoleDAO = new 
        env.jersey().register(new UserResource(userDAO));

       /...
BasicCredentialAuthFilter.Builder<User>()
                .setAuthenticator(new TradingSystemServerAuthenticator(userDAO))
                .setAuthorizer(new TradingSystemServerAuthorizer(userDAO))
                .setRealm("Authetication Required")
                .buildAuthFilter()));
        env.jersey().register(RolesAllowedDynamicFeature.class);
        env.jersey().register(new AuthValueFactoryProvider.Binder<>(User.class));

    }

}

And I have the annotation @RolesAllowed("role_name") on one of the rest api methods which with im trying to test the auth.

Now when i try to test this and i make a request to that rest api method, I get the error No session currently bound to execution context where i do userDAO.getUser(...) in the authanticator and in the authorizer

EDIT 2:

UserDAO implementation:

package com.amitbaz.tss.db;

import java.util.List;

import org.hibernate.SessionFactory;

import io.dropwizard.hibernate.AbstractDAO;

public class UserDAO extends AbstractDAO<User>{

    public UserDAO(SessionFactory sessionFactory) {
        super(sessionFactory);
        // TODO Auto-generated constructor stub
    }

    public List<User> getUser(String username){
        return list(namedQuery("com.amitbaz.tss.db.user.getUser")
                .setParameter("username", username));
    }

}

EDIT 3:

Added @UnitOfWork to authenticate and authorize methods.

registered them as follow ( Notice the changes in hibernateBundle and run method): package com.amitbaz.tss;

import javax.servlet.ServletRegistration;

import org.atmosphere.cpr.ApplicationConfig;
import org.atmosphere.cpr.AtmosphereServlet;
import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.amitbaz.tss.auth.TradingSystemServerAuthenticator;
import com.amitbaz.tss.auth.TradingSystemServerAuthorizer;
import com.amitbaz.tss.auth.User;
import com.amitbaz.tss.db.Broker;
import com.amitbaz.tss.db.BrokerDAO;
import com.amitbaz.tss.db.BrokerResource;
import com.amitbaz.tss.db.Contact;
import com.amitbaz.tss.db.ContactDAO;
import com.amitbaz.tss.db.ContactResource;
import com.amitbaz.tss.db.Product;
import com.amitbaz.tss.db.ProductDAO;
import com.amitbaz.tss.db.ProductResource;
import com.amitbaz.tss.db.Test;
import com.amitbaz.tss.db.TestDAO;
import com.amitbaz.tss.db.TestResource;
import com.amitbaz.tss.db.Transaction;
import com.amitbaz.tss.db.TransactionDAO;
import com.amitbaz.tss.db.TransactionResource;
import com.amitbaz.tss.db.UserDAO;
import com.amitbaz.tss.db.UserResource;
import com.amitbaz.tss.db.UserRole;
import com.amitbaz.tss.db.UserRoleDAO;
import com.amitbaz.tss.db.UserRoleResource;
import com.amitbaz.tss.db.Website;
import com.amitbaz.tss.db.WebsiteDAO;
import com.amitbaz.tss.db.WebsiteResource;

import io.dropwizard.Application;
import io.dropwizard.auth.AuthDynamicFeature;
import io.dropwizard.auth.AuthValueFactoryProvider;
import io.dropwizard.auth.basic.BasicCredentialAuthFilter;
import io.dropwizard.db.DataSourceFactory;
import io.dropwizard.hibernate.HibernateBundle;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;
import javassist.tools.web.Webserver;

public class TradingSystemServerApplication extends Application<TradingSystemServerConfiguration>{

    public static void main(String[] args) throws Exception{
        new TradingSystemServerApplication().run(args);
    }

    public final static HibernateBundle<TradingSystemServerConfiguration> hibernateBundle
    = new HibernateBundle<TradingSystemServerConfiguration>(
            Test.class,Broker.class, com.amitbaz.tss.db.User.class, UserRole.class
            ,Product.class, Transaction.class, Website.class, Contact.class
            ,TradingSystemServerAuthenticator.class, TradingSystemServerAuthorizer.class
    ) {
        @Override
        public DataSourceFactory getDataSourceFactory(
                TradingSystemServerConfiguration configuration
        ) {
            return configuration.getDataSourceFactory();
        }
    };

    final Logger logger = LoggerFactory.getLogger(TradingSystemServerApplication.class);

    @Override
    public void initialize(
            final Bootstrap<TradingSystemServerConfiguration> bootstrap) {
        bootstrap.addBundle(hibernateBundle);
    }

    @Override
    public void run(TradingSystemServerConfiguration config, Environment env) throws Exception {
        final UserDAO userDAO = new UserDAO(hibernateBundle.getSessionFactory());
        final UserRoleDAO userRoleDAO = new UserRoleDAO(hibernateBundle.getSessionFactory());

        final TradingSystemServerAuthorizer authorizer = new TradingSystemServerAuthorizer(userDAO);
        final TradingSystemServerAuthenticator authenticator = new TradingSystemServerAuthenticator(userDAO);

        env.jersey().register(new UserResource(userDAO));
        env.jersey().register(new UserRoleResource(userRoleDAO));

        env.jersey().register(authorizer);
        env.jersey().register(authenticator);

        env.jersey().register(new AuthDynamicFeature(new BasicCredentialAuthFilter.Builder<User>()
                .setAuthenticator(authenticator)
                .setAuthorizer(authorizer)
                .setRealm("Authetication Required")
                .buildAuthFilter()));
        env.jersey().register(RolesAllowedDynamicFeature.class);
        env.jersey().register(new AuthValueFactoryProvider.Binder<>(User.class));


}
kitsuneFox
  • 1,243
  • 3
  • 18
  • 31
  • Is your user resource deployed within the same server? Because if so, you shouldn't make a rest request to your own application but rather split the functionality into a service layer and rest layer and call your service directly from the authenticator instead of making a rest request to the same server – pandaadb Oct 17 '16 at 09:50
  • @pandaadb , i have an User class, an UserDAO class and an UserResource class. So i should use the UserDAO in my authentication? the problem is that i need to pass it a SessionFactory. can you give me an example for that? – kitsuneFox Oct 17 '16 at 09:58
  • Can you add your UserDao implementation please? – pandaadb Oct 17 '16 at 11:44

2 Answers2

2

Your approach looks like a design issue. The issue I see is that you are trying to integrate via rest with a service that is already accessible for you within your application. That adds a lot of overhead and complicates things.

Fortunately, DW already has a fully integrated Authorization and Authentication system just waiting for you to plug in. You can read more about it here: http://www.dropwizard.io/1.0.2/docs/manual/auth.html

The essential thing to note here is that you should split the service used by your resource from your resource. In your case for example the UserDao, or you could split it into a UserService and UserResource, where the UserService provides access to your database layer. Up to you really.

Here is how you would implement this with DW integrated auth and how you would register this as well.

In my example I am skipping the Hibernate aspect of this as it isn't too relevant. you can read about it here: http://www.dropwizard.io/1.0.2/docs/manual/hibernate.html

Here's my code:

public class AuthenticatorTest extends io.dropwizard.Application<Configuration> {

    @Override
    public void run(Configuration configuration, Environment environment) throws Exception {
        // register resource
        environment.jersey().register(MyHelloResource.class);

        // create the dao + dependencies
        UserDao dao = new UserDao(null); 

        // register new authenticator 
        environment.jersey().register(new AuthDynamicFeature(new BasicCredentialAuthFilter.Builder<Principal>()
                .setAuthenticator(new UserAuth(dao)).setRealm("SUPER SECRET STUFF").buildAuthFilter()));

        // enables authentication via filter
        environment.jersey().register(RolesAllowedDynamicFeature.class);
    }

    public static void main(String[] args) throws Exception {
        new AuthenticatorTest().run("server", "/home/artur/dev/repo/sandbox/src/main/resources/config/test.yaml");
    }

    @Path("test")
    @Produces(MediaType.APPLICATION_JSON)
    public static class MyHelloResource {

        @GET
        @Path("asd")
        @PermitAll
        public String test(String x) {
            return "Hello";
        }

    }


    public static class UserAuth implements Authenticator<BasicCredentials, Principal> {
        private UserDao dao;

        public UserAuth(UserDao dao) {
            this.dao = dao;
        }

        @Override
        public Optional<Principal> authenticate(BasicCredentials credentials) throws AuthenticationException {
            String user = dao.getUser();
            return Optional.of(new Principal() {
                @Override
                public String getName() {
                    return user;
                }

            });
        }
    }

    public static class UserDao {

        private SessionFactory s;

        public UserDao(final SessionFactory s) {
            this.s = s;
        }

        public String getUser() {
            return "pandaadb";
        }
    }

}

And this is the breakdown of what we are doing.

First, as per docs, you would register your HibernateBundle within the bootstrap method as shown (in docs). This gives you access to the SessionFactory you require for your authentication.

Your resource method will be annotated with a java security annotation. i am using PermitAll because I am disregarding roles.

In the run method, you then create your DAO, register your resource and use the DW builder to add the required Filter and the Authenticator. This one specifically is for BasicCredentials, however there is nothing stopping you from doing any kind of filter for this. DW already supports things like Ldap (in a different dependency), Basic Auth and so on.

Now, since you create your beans in the run method, and you added your Hibernate bundle in the bootstrap method, you have access to the SessionFactory and can instantiate the DAO accordingly. No need to have to pass it around.

You also don't have to do any rest-request to access your user (though there is nothing stopping you adding that resource anyway, in case you need external access to it.)

So, to sum up, the important parts are:

  • Add A security annotation to your resource (e.g. PermitAll to allow all roles)
  • Add an authenticator implementation (in my case UserAuth) that uses your DAO
  • Instantiate it in the run method provided by dropwizard and register it with the jersey environment.

Note, this requires your user to implement the javax.security.Principal interface. this is not a bad idea in general as a lot of security Frameworks make use of this.

This, also, gives you more options with regards to DW.

You can add an Authorization implementation and a filter, and you'll be able to inject the User object into any resource method by adding an @Auth annotated object (see docs).

Finally, the test of the standalone app from above:

artur@pandaadb:~$ curl "localhost:9085/api/test/asd" -v 
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 9085 (#0)
> GET /api/test/asd HTTP/1.1
> Host: localhost:9085
> User-Agent: curl/7.47.0
> Accept: */*
> 
< HTTP/1.1 401 Unauthorized
< Date: Mon, 17 Oct 2016 10:45:51 GMT
< WWW-Authenticate: Basic realm="SUPER SECRET STUFF"
< Content-Type: text/plain
< Content-Length: 49
< 
* Connection #0 to host localhost left intact
Credentials are required to access this resource.

artur@pandaadb:~$ curl "localhost:9085/api/test/asd" -H "Authorization: Basic dXNlcjpwYXNz" -v
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 9085 (#0)
> GET /api/test/asd HTTP/1.1
> Host: localhost:9085
> User-Agent: curl/7.47.0
> Accept: */*
> Authorization: Basic dXNlcjpwYXNz
> 
< HTTP/1.1 200 OK
< Date: Mon, 17 Oct 2016 10:46:11 GMT
< Content-Type: application/json
< Vary: Accept-Encoding
< Content-Length: 5
< 
* Connection #0 to host localhost left intact
Helloartur

I hope this helps you with your issue.

Of course you don't need to use the DW method for Authentication. However I would recommend going down that road as you will have more support and a lot of things out of the box.

However, the one thing you should rethink (if you don't use DW) is to not do a curl request to your Filter. Instead, instantiate it in the run method, and pass that instance to your Filter.

Note also, if you register your DAO with DW (as seen in hibernate docs), you will be able to use @Inject to inject your DAO into the Filter class that needs to use it.

Right, I think that's all the info you need :)

Let me know if you have any problems,

Artur

Edit:

I am doing an edit because I wrote a lot above and don't want to go over it.

I set up Hibernate to test this. The reason you are seeing issues is because the UnitOfWork is bound to the request scope. However, the resource annotations is matched AFTER the filter is invoked (since you need to do auth before executing the method). This is why you don't have a session.

This is the solution.

In your run method, register a proxy for your auth implementation:

UnitOfWorkAwareProxyFactory fac = new UnitOfWorkAwareProxyFactory(hibernate);

        UserAuth proxy = fac.create(UserAuth.class, UserDao.class, dao);

        environment.jersey().register(new AuthDynamicFeature(new BasicCredentialAuthFilter.Builder<Principal>()
                .setAuthenticator(proxy).setRealm("SUPER SECRET STUFF").buildAuthFilter()));

This creates a proxy around the UserAuth class so that it is aware of the UnitOfWork annotation.

In your UserAuth class (or mine rather) you do:

public static class UserAuth implements Authenticator<BasicCredentials, Principal> {
        private UserDao dao;

        public UserAuth(UserDao dao) {
            this.dao = dao;
        }

        @Override
        @UnitOfWork
        public Optional<Principal> authenticate(BasicCredentials credentials) throws AuthenticationException {
            String user = dao.getUser();
            return Optional.of(new Principal() {
                @Override
                public String getName() {
                    return user;
                }

            });
        }
    }

Note the UnitOfWork annotation on the authenticate. This now opens a new session for you. Please make sure to read up on UnitOfWork as it may have tricky side effects (or not) depending on how you use it.

Finally, this allowed my dao to talk to the database on an existing session.

Regards,

Artur

pandaadb
  • 6,306
  • 2
  • 22
  • 41
  • Thank you for the detailed answer, it helped me to understand that i did everything ok (I found the way to do it while you were writing this answer). However, now i get the error "No session currently bound to execution context" from within the Authorizer (which also uses UserDAO). let me edit my post and I'd be happy if you could take a look and help me find the missing part – kitsuneFox Oct 17 '16 at 11:28
  • Please take a look :) – kitsuneFox Oct 17 '16 at 11:39
  • could you add your userdao implementation @kitsuneFox – pandaadb Oct 17 '16 at 11:57
  • Ok i added UserDAO – kitsuneFox Oct 17 '16 at 12:00
  • @kitsuneFox can you add UnitOfWork to your resource method and (additionally I think) register your authenticators with the proxy as shown in http://www.dropwizard.io/1.0.2/docs/manual/hibernate.html at the bottom of the page. The issue seems to be that your auth is now outside the jersey context and therefore no session is created when your resource method is called. The second step may be unnecessary. See also: http://stackoverflow.com/questions/30212125/no-session-currently-bound-to-execution-context – pandaadb Oct 17 '16 at 12:03
  • I think you misunderstood. The unitOfWork annotations belong on the resource method that accesses the database. E.g. if you have a method "doSomething" that needs authentication, the unitOfWork will handle opening a news session for you. Alternatively you can call openSession yoruself in the dao before executing the list method. The problem you are seeing is that your auth daos never have opened a session and therefore have nothing to use – pandaadb Oct 17 '16 at 12:19
  • Ok but I have @ UnitOfWork above the getUser function in the user resource, but we said that we don't use the resource but the service (or dao) instead, didn't we? Anyway. I assigned the sessionFactory in the UserDAO into a local sessionFactory var. then in authenticate (for example) i did userDAO.getSessionFactory().openSession(); before i call userDAO.getUser and userDAO.getSessionFactory().close(); after. But still the same .. – kitsuneFox Oct 17 '16 at 12:30
  • @kitsuneFox i know what you mean. I have worked through the example and made it work with the above code in the edit. The issue is that auth is matched before the resource i believe, so the session isn't open yet. however, wihtout adding a proxy for your authenticator and authorization code, you can't use the UnitOfWork approach. You can open the session manually, but you need to bind it to the threadLocal as well. Adding a proxy, you will be able to simply use the annotation as shown in my edit – pandaadb Oct 17 '16 at 12:45
  • Finally found the solution, take a look at my answer. – kitsuneFox Oct 17 '16 at 13:56
0

I finally, after much debugginf, find the proper way to open a session and execute a query. I added this lines in Authenticator's authenticate mehod:

 Session session = userDAO.getSessionFactory().openSession();
        Transaction transaction = session.getTransaction();
        Query userquery = session.createQuery("select u from User u where u.username = :username").setParameter("username", credentials.getUsername());
        List<com.amitbaz.tss.db.User> u = userquery.list();
        session.close();

And the same in Authorizater and it now works :)

kitsuneFox
  • 1,243
  • 3
  • 18
  • 31
  • This is not the "proper" way of doing it ;) You should use the ProxyFactory to bind your auth code to the hibernate session provider that is managed by the UnitOfWork annotation (see my update). This way you are separating 2 parts of the application that belong together. you are running the risk of multiple transactions within one request (1 for your request, 1 for your auth AND 1 for your authorization). This can have nasty side-effects and i can't tell you how hibernate handles these situations. – pandaadb Oct 17 '16 at 14:05
  • Frankly there is nothing wrong with your approach either. You are doing manual transactions (I think). I just wonder what will happen once you mix DW and your manual approach and have transactions flying around. – pandaadb Oct 17 '16 at 15:29
  • Actually is you can see, I'm not using the transaction variable at all. I am not changing anything in the database within this manual session so I guess that there is no need to commit a transaction at all. – kitsuneFox Oct 17 '16 at 18:53