【问题标题】:Is it possible to invoke methods of secured EJBs through message-driven beans?是否可以通过消息驱动的 bean 调用安全 EJB 的方法?
【发布时间】:2013-09-09 15:00:53
【问题描述】:

当用户登录时,我需要在数据库中插入一条消息(主机名,仅作为示例)。由于它只是一条文本消息,因此完全没有必要将 EJB 注入客户端(Servlet、JSP、JSF 或其他)。

客户端,在这种情况下,它是一个身份验证过滤器,我通过它将主机名发送到消息驱动的 bean。在消息驱动 bean 的帮助下,消息被存储在一个队列(不是主题)中,然后通过将 EJB 注入到这个消息 bean 中来提交给一个 EJB。

这里提到的策略效果很好。当 EJB 被强制执行安全约束时,问题就出现了。在这种情况下,它会引发有关安全性的异常。

消息驱动的bean如下。

@MessageDriven(mappedName = "jms/destination", activationConfig = {
    @ActivationConfigProperty(propertyName = "acknowledgeMode", propertyValue = "Auto-acknowledge"),
    @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue")
})
public class UserStatusMessageBean implements MessageListener
{
    @Resource
    private MessageDrivenContext messageDrivenContext;
    @EJB
    private UserStatusBeanRemote userStatusBeanRemote;

    public UserStatusMessageBean() {}

    @Override
    public void onMessage(Message message)
    {
        TextMessage textMessage;

        try
        {
            if(message instanceof TextMessage)
            {
                textMessage = (TextMessage) message;
                userStatusBeanRemote.addHost(textMessage.getText());
                //This EJB method causes the exception as given below.
            }
            else
            {
                System.out.println("Message is of wrong type : " +message.getClass().getName());
            }
        }
        catch (JMSException e)
        {
            messageDrivenContext.setRollbackOnly();
            System.out.println(e);
        }
        catch (Throwable e)
        {
            System.out.println(e);
        }
    }
}

到目前为止,无状态 EJB 只有一种方法,它负责使用 JPA 标准 API 将消息插入数据库:

@Stateless
@DeclareRoles(value={"ROLE_ADMIN", "ROLE_USER"})
@RolesAllowed(value={"ROLE_ADMIN"})
public class UserStatusBean implements UserStatusBeanRemote
{
    @Override
    public void addHost(String hostName)
    {
         //Business logic to add the host name to the database.
    }
}

下面显示了对用户进行身份验证的过滤器(以防万一,需要审核)。

@WebFilter(filterName = "SecurityCheck", urlPatterns = {"/jass/*"})
public final class SecurityCheck implements Filter
{
    @Resource(mappedName="jms/destinationFactory")
    private ConnectionFactory connectionFactory;
    @Resource(mappedName="jms/destination")
    private Queue queue;
    @EJB
    private final UserBeanLocal userService=null;

    public SecurityCheck() {}

    private void sendJMSMessageToDestination(String message) throws JMSException
    {
        Connection connection = null;
        Session session = null;

        try
        {
            connection = connectionFactory.createConnection();
            session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
            MessageProducer messageProducer = session.createProducer(queue);
            TextMessage textMessage = session.createTextMessage();
            textMessage.setText(message);
            messageProducer.send(textMessage);
        }
        finally
        {
            if(session!=null){session.close();}
            if(connection!=null){connection.close();}
        }
    }

    private void doBeforeProcessing(ServletRequest request, ServletResponse response) throws IOException, ServletException
    {
        HttpServletRequest httpServletRequest=(HttpServletRequest)request;
        httpServletRequest.login(httpServletRequest.getParameter("userName"), httpServletRequest.getParameter("password"));
    }

    private void doAfterProcessing(ServletRequest request, ServletResponse response) throws IOException, ServletException, JMSException
    {
        HttpServletRequest httpServletRequest=(HttpServletRequest)request;
        HttpServletResponse httpServletResponse=(HttpServletResponse)response;
        ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
        Map<String, Object> sessionMap = externalContext.getSessionMap();

        if(httpServletRequest.isUserInRole("ROLE_USER"))
        {
            sendJMSMessageToDestination(httpServletRequest.getLocalName());//Send a text message through a message-driven bean.
            String userName = httpServletRequest.getParameter("userName");
            UserTable userTable = userService.setLastLogin(userName);
            userTable.setPassword(null);
            sessionMap.put("userName", userTable!=null?userTable.getFirstName():"Unknown");
            sessionMap.put("user", userTable);

            httpServletResponse.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
            httpServletResponse.setHeader("Pragma", "no-cache");
            httpServletResponse.setDateHeader("Expires", 0);
            httpServletResponse.sendRedirect("../user_side/Home.jsf");
        }
        else if(httpServletRequest.isUserInRole("ROLE_ADMIN"))
        {
            sendJMSMessageToDestination(httpServletRequest.getLocalName());//Send a text message through a message-driven bean.

            String userName = httpServletRequest.getParameter("userName");
            UserTable userTable = userService.setLastLogin(userName);
            userTable.setPassword(null);
            sessionMap.put("adminName", userTable!=null?userTable.getFirstName():"Unknown");
            sessionMap.put("user", userTable);

            httpServletResponse.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
            httpServletResponse.setHeader("Pragma", "no-cache");
            httpServletResponse.setDateHeader("Expires", 0);
            httpServletResponse.sendRedirect("../admin_side/Home.jsf");
        }
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
    {
        try
        {
            doBeforeProcessing(request, response);
        }
        catch (Exception e)
        {
            HttpServletResponse httpServletResponse=(HttpServletResponse)response;
            //FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, "Error", "Incorrect user name and/or password. Access denied."));
            httpServletResponse.sendRedirect("../utility/Login.jsf");
            return;
        }

        chain.doFilter(request, response);

        try
        {
            doAfterProcessing(request, response);
        }
        catch (JMSException ex)
        {
            Logger.getLogger(SecurityCheck.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    //The rest of this filter.
}

此处应用的安全性适用于其他地方。 EJB UserStatusBean 之前的注释 @RolesAllowed(value={"ROLE_ADMIN"}) 导致引发以下异常。

WARNING: EJB5184:A system exception occurred during an invocation on EJB UserStatusBean, method: public void ejb.message.UserStatusBean.addHost(java.lang.String)
WARNING: javax.ejb.AccessLocalException: Client not authorized for this invocation
    at com.sun.ejb.containers.BaseContainer.preInvoke(BaseContainer.java:1895)
    at com.sun.ejb.containers.EJBObjectInvocationHandler.invoke(EJBObjectInvocationHandler.java:204)
    at com.sun.ejb.containers.EJBObjectInvocationHandlerDelegate.invoke(EJBObjectInvocationHandlerDelegate.java:79)
    at $Proxy366.addHost(Unknown Source)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at com.sun.corba.ee.impl.presentation.rmi.StubInvocationHandlerImpl.privateInvoke(StubInvocationHandlerImpl.java:239)
    at com.sun.corba.ee.impl.presentation.rmi.StubInvocationHandlerImpl.invoke(StubInvocationHandlerImpl.java:150)
    at com.sun.corba.ee.impl.presentation.rmi.codegen.CodegenStubBase.invoke(CodegenStubBase.java:226)
    at ejb.message.__UserStatusBeanRemote_Remote_DynamicStub.addHost(ejb/message/__UserStatusBeanRemote_Remote_DynamicStub.java)
    at ejb.message._UserStatusBeanRemote_Wrapper.addHost(ejb/message/_UserStatusBeanRemote_Wrapper.java)
    at bean.message.UserStatusMessageBean.onMessage(UserStatusMessageBean.java:37)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at org.glassfish.ejb.security.application.EJBSecurityManager.runMethod(EJBSecurityManager.java:1081)
    at org.glassfish.ejb.security.application.EJBSecurityManager.invoke(EJBSecurityManager.java:1153)
    at com.sun.ejb.containers.BaseContainer.invokeBeanMethod(BaseContainer.java:4695)
    at com.sun.ejb.EjbInvocation.invokeBeanMethod(EjbInvocation.java:630)
    at com.sun.ejb.containers.interceptors.AroundInvokeChainImpl.invokeNext(InterceptorManager.java:822)
    at com.sun.ejb.EjbInvocation.proceed(EjbInvocation.java:582)
    at org.jboss.weld.ejb.AbstractEJBRequestScopeActivationInterceptor.aroundInvoke(AbstractEJBRequestScopeActivationInterceptor.java:55)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at com.sun.ejb.containers.interceptors.AroundInvokeInterceptor.intercept(InterceptorManager.java:883)
    at com.sun.ejb.containers.interceptors.AroundInvokeChainImpl.invokeNext(InterceptorManager.java:822)
    at com.sun.ejb.containers.interceptors.InterceptorManager.intercept(InterceptorManager.java:369)
    at com.sun.ejb.containers.BaseContainer.__intercept(BaseContainer.java:4667)
    at com.sun.ejb.containers.BaseContainer.intercept(BaseContainer.java:4655)
    at org.glassfish.ejb.mdb.MessageBeanContainer.deliverMessage(MessageBeanContainer.java:1219)
    at org.glassfish.ejb.mdb.MessageBeanListenerImpl.deliverMessage(MessageBeanListenerImpl.java:81)
    at com.sun.enterprise.connectors.inbound.MessageEndpointInvocationHandler.invoke(MessageEndpointInvocationHandler.java:171)
    at $Proxy406.onMessage(Unknown Source)
    at com.sun.messaging.jms.ra.OnMessageRunner.run(OnMessageRunner.java:283)
    at com.sun.enterprise.connectors.work.OneWork.doWork(OneWork.java:107)
    at com.sun.corba.ee.impl.threadpool.ThreadPoolImpl$WorkerThread.performWork(ThreadPoolImpl.java:497)
    at com.sun.corba.ee.impl.threadpool.ThreadPoolImpl$WorkerThread.run(ThreadPoolImpl.java:540)

INFO:   javax.ejb.EJBAccessException
    at ejb.message._UserStatusBeanRemote_Wrapper.addHost(ejb/message/_UserStatusBeanRemote_Wrapper.java)
    at bean.message.UserStatusMessageBean.onMessage(UserStatusMessageBean.java:37)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at org.glassfish.ejb.security.application.EJBSecurityManager.runMethod(EJBSecurityManager.java:1081)
    at org.glassfish.ejb.security.application.EJBSecurityManager.invoke(EJBSecurityManager.java:1153)
    at com.sun.ejb.containers.BaseContainer.invokeBeanMethod(BaseContainer.java:4695)
    at com.sun.ejb.EjbInvocation.invokeBeanMethod(EjbInvocation.java:630)
    at com.sun.ejb.containers.interceptors.AroundInvokeChainImpl.invokeNext(InterceptorManager.java:822)
    at com.sun.ejb.EjbInvocation.proceed(EjbInvocation.java:582)
    at org.jboss.weld.ejb.AbstractEJBRequestScopeActivationInterceptor.aroundInvoke(AbstractEJBRequestScopeActivationInterceptor.java:55)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at com.sun.ejb.containers.interceptors.AroundInvokeInterceptor.intercept(InterceptorManager.java:883)
    at com.sun.ejb.containers.interceptors.AroundInvokeChainImpl.invokeNext(InterceptorManager.java:822)
    at com.sun.ejb.containers.interceptors.InterceptorManager.intercept(InterceptorManager.java:369)
    at com.sun.ejb.containers.BaseContainer.__intercept(BaseContainer.java:4667)
    at com.sun.ejb.containers.BaseContainer.intercept(BaseContainer.java:4655)
    at org.glassfish.ejb.mdb.MessageBeanContainer.deliverMessage(MessageBeanContainer.java:1219)
    at org.glassfish.ejb.mdb.MessageBeanListenerImpl.deliverMessage(MessageBeanListenerImpl.java:81)
    at com.sun.enterprise.connectors.inbound.MessageEndpointInvocationHandler.invoke(MessageEndpointInvocationHandler.java:171)
    at $Proxy406.onMessage(Unknown Source)
    at com.sun.messaging.jms.ra.OnMessageRunner.run(OnMessageRunner.java:283)
    at com.sun.enterprise.connectors.work.OneWork.doWork(OneWork.java:107)
    at com.sun.corba.ee.impl.threadpool.ThreadPoolImpl$WorkerThread.performWork(ThreadPoolImpl.java:497)
    at com.sun.corba.ee.impl.threadpool.ThreadPoolImpl$WorkerThread.run(ThreadPoolImpl.java:540)
Caused by: java.rmi.AccessException: CORBA NO_PERMISSION 9998 Maybe; nested exception is: 
    org.omg.CORBA.NO_PERMISSION:   vmcid: 0x2000  minor code: 1806 completed: Maybe
    at com.sun.corba.ee.impl.javax.rmi.CORBA.Util.mapSystemException(Util.java:264)
    at com.sun.corba.ee.impl.javax.rmi.CORBA.Util.wrapException(Util.java:695)
    at com.sun.corba.ee.impl.presentation.rmi.StubInvocationHandlerImpl.privateInvoke(StubInvocationHandlerImpl.java:249)
    at com.sun.corba.ee.impl.presentation.rmi.StubInvocationHandlerImpl.invoke(StubInvocationHandlerImpl.java:150)
    at com.sun.corba.ee.impl.presentation.rmi.codegen.CodegenStubBase.invoke(CodegenStubBase.java:226)
    at ejb.message.__UserStatusBeanRemote_Remote_DynamicStub.addHost(ejb/message/__UserStatusBeanRemote_Remote_DynamicStub.java)
    ... 30 more
Caused by: org.omg.CORBA.NO_PERMISSION:   vmcid: 0x2000  minor code: 1806 completed: Maybe
Caused by: javax.ejb.AccessLocalException: Client not authorized for this invocation
    at com.sun.ejb.containers.BaseContainer.preInvoke(BaseContainer.java:1895)
    at com.sun.ejb.containers.EJBObjectInvocationHandler.invoke(EJBObjectInvocationHandler.java:204)
    at com.sun.ejb.containers.EJBObjectInvocationHandlerDelegate.invoke(EJBObjectInvocationHandlerDelegate.java:79)
    at $Proxy366.addHost(Unknown Source)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at com.sun.corba.ee.impl.presentation.rmi.StubInvocationHandlerImpl.privateInvoke(StubInvocationHandlerImpl.java:239)
    at com.sun.corba.ee.impl.presentation.rmi.StubInvocationHandlerImpl.invoke(StubInvocationHandlerImpl.java:150)
    at com.sun.corba.ee.impl.presentation.rmi.codegen.CodegenStubBase.invoke(CodegenStubBase.java:226)
    at ejb.message.__UserStatusBeanRemote_Remote_DynamicStub.addHost(ejb/message/__UserStatusBeanRemote_Remote_DynamicStub.java)
    at ejb.message._UserStatusBeanRemote_Wrapper.addHost(ejb/message/_UserStatusBeanRemote_Wrapper.java)
    at bean.message.UserStatusMessageBean.onMessage(UserStatusMessageBean.java:37)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at org.glassfish.ejb.security.application.EJBSecurityManager.runMethod(EJBSecurityManager.java:1081)
    at org.glassfish.ejb.security.application.EJBSecurityManager.invoke(EJBSecurityManager.java:1153)
    at com.sun.ejb.containers.BaseContainer.invokeBeanMethod(BaseContainer.java:4695)
    at com.sun.ejb.EjbInvocation.invokeBeanMethod(EjbInvocation.java:630)
    at com.sun.ejb.containers.interceptors.AroundInvokeChainImpl.invokeNext(InterceptorManager.java:822)
    at com.sun.ejb.EjbInvocation.proceed(EjbInvocation.java:582)
    at org.jboss.weld.ejb.AbstractEJBRequestScopeActivationInterceptor.aroundInvoke(AbstractEJBRequestScopeActivationInterceptor.java:55)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at com.sun.ejb.containers.interceptors.AroundInvokeInterceptor.intercept(InterceptorManager.java:883)
    at com.sun.ejb.containers.interceptors.AroundInvokeChainImpl.invokeNext(InterceptorManager.java:822)
    at com.sun.ejb.containers.interceptors.InterceptorManager.intercept(InterceptorManager.java:369)
    at com.sun.ejb.containers.BaseContainer.__intercept(BaseContainer.java:4667)
    at com.sun.ejb.containers.BaseContainer.intercept(BaseContainer.java:4655)
    at org.glassfish.ejb.mdb.MessageBeanContainer.deliverMessage(MessageBeanContainer.java:1219)
    at org.glassfish.ejb.mdb.MessageBeanListenerImpl.deliverMessage(MessageBeanListenerImpl.java:81)
    at com.sun.enterprise.connectors.inbound.MessageEndpointInvocationHandler.invoke(MessageEndpointInvocationHandler.java:171)
    at $Proxy406.onMessage(Unknown Source)
    at com.sun.messaging.jms.ra.OnMessageRunner.run(OnMessageRunner.java:283)
    at com.sun.enterprise.connectors.work.OneWork.doWork(OneWork.java:107)
    at com.sun.corba.ee.impl.threadpool.ThreadPoolImpl$WorkerThread.performWork(ThreadPoolImpl.java:497)
    at com.sun.corba.ee.impl.threadpool.ThreadPoolImpl$WorkerThread.run(ThreadPoolImpl.java:540)

当这个注解@RolesAllowed(value={"ROLE_ADMIN"})在EJB被移除之前就起作用了。

是否可以通过消息驱动 bean 调用此类方法?

我使用的是 GlassFish 4.0。

【问题讨论】:

    标签: jakarta-ee ejb ejb-3.0 jaas message-driven-bean


    【解决方案1】:

    您可以为 MDB 使用 @RunAs 注释。

    EJB 规范说:

    Bean Provider 可以使用 RunAs 元数据注解或 Bean Provider 或 Application Assembler 可以使用 run-as 部署 为企业 bean 定义 run-as 身份的描述符元素 在部署描述符中。 run-as 身份适用于 企业bean作为一个整体,即对企业的所有方法 bean 的业务、家庭和组件接口,无接口视图, 和/或网络服务端点; 到一个消息监听器方法 消息驱动 bean;和超时回调方法 企业bean;以及它们可能的 bean 的所有内部方法 依次调用。

    调用者主体传播在这种情况下并不重要。与谁调用该方法以及是否传播身份无关,将使用 run-as 身份并将其进一步传播。

    不过,必须正确设置 run-as 身份。基本上,如果您使用默认主体到角色映射,则需要创建一个用户,例如RunAsAdmin 并为他分配 ROLE_ADMIN 角色。然后用@RunAs("RunAsAdmin")注释你的MDB。

    【讨论】:

    • @RunAs 注释仅在我声明此角色列表时才起作用(但未达到预期) - ROLE_USER,ROLE_ADMIN in a file ream - Configurations-&gt;server-config-&gt;Security-&gt;Realms-&gt;file in the Assign Groups field in this file realm . EJB 方法由使用文件领域中指定的任何角色 进行身份验证的任何用户调用。这不应该发生。 EJB 方法只能由权限为ROLE_ADMIN 的用户调用——因为@RolesAllowed(value={"ROLE_ADMIN"})。不应调用 EJB 方法,当 MDB 由 @RunAs(value="ROLE_USER") 注释但发生这种情况时
    • 我在ejb模块中的这个glassfish-ejb-jar.xml文件完全没有意义(即使它被删除也没有什么区别)。
    【解决方案2】:

    您可以尝试在 MDB 上使用 @RunAs,如下所示:

    @MessageDriven(...)
    @RunAs("ROLE_ADMIN")
    public class UserStatusMessageBean implements MessageListener {
    

    不过,一般来说,EJB 规范不保证安全上下文将通过 MDB(请参阅 JSR-318 v. 3.1FR 第 5.4.13 章):

    调用者主体可以传播到消息驱动 bean 的消息侦听器方法中。 是否发生这种情况取决于特定的消息侦听器接口和相关的消息传递提供程序,但不受本规范的约束。

    至于 Glassfish 是否处理这个问题,也许其他人可以帮助你。

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-02-27
    • 1970-01-01
    • 2023-03-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多