【问题标题】:dropwizard hibernate use a resource from within the serverdropwizard hibernate 使用服务器内的资源
【发布时间】:2016-10-17 07:43:08
【问题描述】:

我是 dropwizard 的新手,我正在尝试创建一个从用户那里获取凭据的 Authenticator,然后它使用我在 UserResouce 类中实现的其余 api getUser 方法来获取用户名在来自 db users 表的凭据。但是,在我的 autheticator 类中,我无法弄清楚如何使用用户资源函数来获取用户。

我试图做这样的事情:

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;
}

在 autheticator 中并从authenticte 函数调用它,但它说没有会话绑定...

编辑:

好的,经过深思熟虑,我明白了: 我是具有 BasicCredentials 和的 dropwizard 身份验证器和授权器实现。

身份验证器(不要介意 VALID_USER 的事情..):

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();
    }

}

授权人:

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"));
    }

}

现在,在我的应用程序类中,我这样做:

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));

    }

}

我在其他 api 方法之一上有注释 @RolesAllowed("role_name"),我试图测试身份验证。

现在,当我尝试对此进行测试并向那个 rest api 方法发出请求时,我 得到错误No session currently bound to execution context 我在身份验证器和授权器中执行 userDAO.getUser(...)

编辑 2:

UserDAO 实现:

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));
    }

}

编辑 3:

添加了@UnitOfWork 来验证和授权方法。

将它们注册如下(注意 hibernateBundle 和 run 方法的变化): 包 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));


}

【问题讨论】:

  • 您的用户资源是否部署在同一台服务器上?因为如果是这样,您不应该向自己的应用程序发出休息请求,而是将功能拆分为服务层和休息层,并直接从身份验证器调用您的服务,而不是向同一服务器发出休息请求
  • @pandaadb ,我有一个 User 类、一个 UserDAO 类和一个 UserResource 类。所以我应该在我的身份验证中使用 UserDAO 吗?问题是我需要向它传递一个 SessionFactory。你能给我举个例子吗?
  • 你能添加你的 UserDao 实现吗?

标签: java hibernate dropwizard


【解决方案1】:

您的方法看起来像是一个设计问题。我看到的问题是您正在尝试通过 rest 与您在应用程序中已经可以访问的服务集成。这增加了很多开销并使事情复杂化。

幸运的是,DW 已经有一个完全集成的授权和身份验证系统等待你的插入。你可以在这里阅读更多信息:http://www.dropwizard.io/1.0.2/docs/manual/auth.html

这里要注意的重要一点是,您应该将资源使用的服务从资源中分离出来。在您的情况下,例如 UserDao,或者您可以将其拆分为 UserService 和 UserResource,其中 UserService 提供对您的数据库层的访问。真的取决于你。

以下是使用 DW 集成身份验证实现此功能的方法以及如何注册它。

在我的示例中,我跳过了 Hibernate 方面,因为它不太相关。你可以在这里阅读:http://www.dropwizard.io/1.0.2/docs/manual/hibernate.html

这是我的代码:

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";
        }
    }

}

这是我们正在做的事情的细分。

首先,根据文档,您将在引导方法中注册您的HibernateBundle,如图所示(在文档中)。这使您可以访问身份验证所需的SessionFactory

您的资源方法将使用 java 安全注释进行注释。我正在使用PermitAll,因为我忽略了角色。

然后在 run 方法中创建 DAO,注册资源并使用 DW 构建器添加所需的过滤器和 Authenticator。这个专门针对BasicCredentials,但是没有什么能阻止你为此做任何类型的过滤器。 DW 已经支持 Ldap(在不同的依赖项中)、Basic Auth 等。

现在,由于您在 run 方法中创建了 bean,并在 bootstrap 方法中添加了 Hibernate 包,因此您可以访问 SessionFactory 并相应地实例化 DAO。无需传递它。

您也不必执行任何休息请求来访问您的用户(尽管无论如何都不会阻止您添加该资源,以防您需要外部访问它。)

所以,总结起来,重要的部分是:

  • 为您的资源添加安全注释(例如,PermitAll 以允许所有角色)
  • 添加使用您的 DAO 的身份验证器实现(在我的情况下为 UserAuth
  • 在dropwizard提供的run方法中实例化,并注册到jersey环境中。

注意,这需要您的用户实现javax.security.Principal 接口。一般来说,这不是一个坏主意,因为许多安全框架都利用了这一点。

这也为您提供了有关 DW 的更多选择。

您可以添加Authorization 实现和过滤器,并且您可以通过添加@Auth 注释对象将用户对象注入任何资源方法(请参阅文档)。

最后,从上面对独立应用的测试:

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

我希望这可以帮助您解决问题。

当然你不需要使用 DW 方法进行身份验证。但是,我建议您走这条路,因为您将获得更多支持和很多开箱即用的东西。

但是,您应该重新考虑的一件事(如果您不使用 DW)是不要向您的过滤器发出 curl 请求。相反,在 run 方法中实例化它,并将该实例传递给您的过滤器。

另请注意,如果您使用 DW 注册您的 DAO(如 hibernate 文档中所示),您将能够使用 @Inject 将您的 DAO 注入需要使用它的 Filter 类中。

好吧,我想这就是你需要的所有信息:)

如果您有任何问题,请告诉我,

阿图尔

编辑:

我正在编辑,因为我在上面写了很多内容,不想重复。

我设置了 Hibernate 来测试这个。您看到问题的原因是因为 UnitOfWork 绑定到请求范围。但是,资源注释在调用过滤器之后匹配(因为您需要在执行方法之前进行身份验证)。这就是您没有会话的原因。

这就是解决方案。

在你的 run 方法中,为你的 auth 实现注册一个代理:

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()));

这会围绕 UserAuth 类创建一个代理,以便它知道 UnitOfWork 注释。

在您的 UserAuth 类(或者我的类)中,您可以这样做:

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;
                }

            });
        }
    }

注意身份验证上的 UnitOfWork 注释。现在,这将为您打开一个新会话。请务必阅读 UnitOfWork,因为它可能有棘手的副作用(或没有),具体取决于您如何使用它。

最后,这允许我的 dao 在现有会话上与数据库对话。

问候,

阿图尔

【讨论】:

  • 感谢您的详细回答,它帮助我了解我做的一切都很好(我在您写这个答案时找到了方法)。但是,现在我从授权方(也使用 UserDAO)中收到错误“当前没有会话绑定到执行上下文”。让我编辑我的帖子,如果你能看一下并帮我找到缺失的部分,我会很高兴
  • 请看一下:)
  • 你能添加你的userdao实现@kitsuneFox
  • 好的,我添加了 UserDAO
  • @kitsuneFox 您可以将 UnitOfWork 添加到您的资源方法中,并且(另外我认为)使用代理注册您的身份验证器,如页面底部的dropwizard.io/1.0.2/docs/manual/hibernate.html 所示。问题似乎是您的身份验证现在不在球衣上下文中,因此在调用您的资源方法时不会创建会话。第二步可能是不必要的。另见:stackoverflow.com/questions/30212125/…
【解决方案2】:

经过多次调试,我终于找到了打开会话并执行查询的正确方法。 我在 Authenticator 的身份验证方法中添加了这一行:

 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();

在 Authorizater 中也是如此,它现在可以工作了 :)

【讨论】:

  • 这不是“正确”的做法;)您应该使用 ProxyFactory 将您的身份验证代码绑定到由 UnitOfWork 注释管理的休眠会话提供程序(请参阅我的更新)。这样,您就可以将应用程序的两个部分分开。您冒着在一个请求中进行多笔交易的风险(1 个用于您的请求,1 个用于您的身份验证,1 个用于您的授权)。这可能会产生令人讨厌的副作用,我无法告诉您 hibernate 如何处理这些情况。
  • 坦率地说,您的方法也没有任何问题。您正在进行手动交易(我认为)。我只是想知道一旦你将 DW 和你的手动方法混合在一起并让交易飞来飞去,会发生什么。
  • 其实你可以看到,我根本没有使用事务变量。在此手动会话中,我没有更改数据库中的任何内容,所以我想根本不需要提交事务。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-01-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-04-13
  • 2015-07-31
  • 1970-01-01
相关资源
最近更新 更多