eclipse-ee4j / glassfish

Eclipse GlassFish
https://eclipse-ee4j.github.io/glassfish/
381 stars 143 forks source link

Custom jacc provider fails to find BeanManager via JNDI (java:comp/BeanManager) #24677

Open escay opened 11 months ago

escay commented 11 months ago

Environment Details


Problem Description

I have a custom jacc provider. The jar is placed it in the /lib folder, and registered in domain.xml. The implementation is based on Arjan Thijms example from: https://arjan-tijms.omnifaces.org/2015/03/java-ee-authorization-jacc-revisited.html

This provider works on Payara 5.2022 and 6.2023, but now I try to use the same provider on GlassFish 7. The jacc provider is registered during startup. No errors.

[2023-11-07T10:53:27.356527+01:00] [GF 7.0.10] [INFO] [NCLS-SECURITY-01115] [jakarta.enterprise.system.core.security] [tid: _ThreadID=1 _ThreadName=main] [levelValue: 800] [[
  Realm [MySecurityRealm] of classtype [my.userlib.security.MyGlassfishSecurityRealm] successfully created.]]
...

[2023-11-07T10:53:35.128538+01:00] [GF 7.0.10] [INFO] [NCLS-SECURITY-01010] [jakarta.enterprise.system.core.security] [tid: _ThreadID=141 _ThreadName=Thread-9] [levelValue: 800] [[
  Entering Security Startup Service.]]

[2023-11-07T10:53:35.130535+01:00] [GF 7.0.10] [INFO] [NCLS-SECURITY-01143] [jakarta.enterprise.system.core.security] [tid: _ThreadID=141 _ThreadName=Thread-9] [levelValue: 800] [[
  Loading policy provider my.jaccprovider.policy.CustomPolicy.]]

[2023-11-07T10:53:35.137544+01:00] [GF 7.0.10] [INFO] [] [my.jaccprovider.policy.CustomPolicy] [tid: _ThreadID=141 _ThreadName=Thread-9] [levelValue: 800] [[
  ### CustomPolicy - constructor - start]]

[2023-11-07T10:53:35.137544+01:00] [GF 7.0.10] [INFO] [] [my.jaccprovider.policy.CustomPolicy] [tid: _ThreadID=141 _ThreadName=Thread-9] [levelValue: 800] [[
  ### CustomPolicy - constructor - end]]

[2023-11-07T10:53:35.146490+01:00] [GF 7.0.10] [INFO] [NCLS-SECURITY-01011] [jakarta.enterprise.system.core.security] [tid: _ThreadID=141 _ThreadName=Thread-9] [levelValue: 800] [[
  Security Service(s) started successfully.]]

What is special about the implementation is that it wants to use BeanManager via JNDI. The jacc provider jar contains interface class:

public interface AuthorizationMechanism {

    default Boolean preAuthenticatePreAuthorize(Permission requestedPermission, SecurityConstraints securityConstraints) {
        return null;
    }
    default Boolean preAuthenticatePreAuthorizeByRole(Permission requestedPermission, SecurityConstraints securityConstraints) {
        return null;
    }
    default Boolean postAuthenticatePreAuthorize(Permission requestedPermission, Caller caller, SecurityConstraints securityConstraints) {
        return null;
    }
    default Boolean postAuthenticatePreAuthorizeByRole(Permission requestedPermission, Caller caller, SecurityConstraints securityConstraints) {
        return null;
    }
}

The implementation bean:

@ApplicationScoped
public class CustomAuthorizationMechanism implements AuthorizationMechanism {

is part of the ear file (as an ejb jar with beans.xml) of the application that needs to use the jacc provider and will make some database calls and reuses the datasource that is already configured for the ear file. The goal is to find the application scoped CustomAuthorizationMechanism instance by trying to load the AuthorizationMechanism via BeanManager.

When I deploy an ear file at one point deployment of the ear fails with NameNotFoundException: No object bound for java:comp/BeanManager:

[2023-11-07T11:28:28.074091+01:00] [GF 7.0.10] [INFO] [] [my.jaccprovider.cdi] [tid: _ThreadID=143 _ThreadName=AutoDeployer] [levelValue: 800] [[
  ### CdiUtils - is, exception: javax.naming.NamingException: Lookup failed for java:comp/BeanManager in SerialContext[myEnv={java.naming.factory.state=com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl, java.naming.factory.url.pkgs=com.sun.enterprise.naming}] [Root exception is javax.naming.NameNotFoundException: No object bound for java:comp/BeanManager], type: class javax.naming.NameNotFoundException]]

[2023-11-07T11:28:28.475304+01:00] [GF 7.0.10] [SEVERE] [] [jakarta.enterprise.system.core] [tid: _ThreadID=143 _ThreadName=AutoDeployer] [levelValue: 1000] [[
  Exception while loading the app : jakarta.ejb.CreateException: Initialization failed for Singleton scheduler.ServiceStarter
jakarta.ejb.CreateException: Initialization failed for Singleton scheduler.ServiceStarter
    at com.sun.ejb.containers.AbstractSingletonContainer.createSingletonEJB(AbstractSingletonContainer.java:450)
    at com.sun.ejb.containers.AbstractSingletonContainer$SingletonContextFactory.create(AbstractSingletonContainer.java:620)
    at com.sun.ejb.containers.AbstractSingletonContainer.instantiateSingletonInstance(AbstractSingletonContainer.java:362)
    at org.glassfish.ejb.startup.SingletonLifeCycleManager.initializeSingleton(SingletonLifeCycleManager.java:195)
    at org.glassfish.ejb.startup.SingletonLifeCycleManager.initializeSingleton(SingletonLifeCycleManager.java:156)
    at org.glassfish.ejb.startup.SingletonLifeCycleManager.doStartup(SingletonLifeCycleManager.java:134)
    at org.glassfish.ejb.startup.EjbApplication.start(EjbApplication.java:137)
...
    at org.glassfish.deployment.autodeploy.AutoDeployService$1.run(AutoDeployService.java:209)
    at java.base/java.util.TimerThread.mainLoop(Timer.java:566)
    at java.base/java.util.TimerThread.run(Timer.java:516)
Caused by: jakarta.ejb.EJBAccessException
    at com.sun.ejb.containers.BaseContainer.mapLocal3xException(BaseContainer.java:2057)
    at com.sun.ejb.containers.BaseContainer.postInvoke(BaseContainer.java:1858)
    ... 32 more
Caused by: jakarta.ejb.AccessLocalException: Client not authorized for this invocation
    at com.sun.ejb.containers.BaseContainer.preInvoke(BaseContainer.java:1699)
    at com.sun.ejb.containers.EJBLocalObjectInvocationHandler.invoke(EJBLocalObjectInvocationHandler.java:168)
    ... 63 more
]]

the relevant code shortened is:

public class my.jaccprovider.policy.CustomPolicy extends java.security.Policy {
    @Override
    public boolean implies(ProtectionDomain domain, Permission requestedPermission) {
       ....
       context = new InitialContext();
       BeanManager bm = (BeanManager) context.lookup("java:comp/BeanManager");
    }
}

Impact of Issue

The questions I have are:

dmatej commented 11 months ago

Can you quickly try it yet with 7.0.9? We did some changes related to authentication, it might be a bug.

arjantijms commented 11 months ago

Thanks for the report! It's a long time ago I created that example, and I would have to take a look at it again.

One important question though; on Payara did you also install the JACC Provider in the \glassfish\domains\my-domain\lib folder, or did you install it using the JACC-per-app feature I added to Payara?

Note btw that for Jakarta EE 11 / GlassFish 8, the JACC Provider will have to change as the JDK Policy is deprecated for removal. The plan is also to make that example a default feature in Jakarta Security.

See:

escay commented 11 months ago

I tested on Glassfish 7.0.9 with the same "NameNotFoundException: No object bound for java:comp/BeanManager" result. (manually copied domain from 7.0.10 and erased osgi-cache/felix folder)

I have the same domain configuration in use for both Payara and Glassfish the same domain.xml configuration like:

<security-service jacc="amm">
    <auth-realm classname="com.my.userlib.security.MyGlassfishSecurityRealm" name="MySecurityRealm">
         <property name="jaas-context" value="myRealm"></property>
    </auth-realm>
    <jacc-provider policy-provider="com.my.jaccprovider.policy.CustomPolicy" name="amm" policy-configuration-factory-provider="my.jaccprovider.configuration.CustomPolicyConfigurationFactory"></jacc-provider>

and the jars are in the domain specific lib on the same location:

C:\payara-5.2022.5\glassfish\domains\my-domain\lib\jacc-provider-2.44.5-SNAPSHOT.jar - javaee8
C:\payara-6.2023.10\glassfish\domains\my-domain\lib\jacc-provider-3.0.2-SNAPSHOT.jar - jakartaee10
C:\glassfish-7.0.9\glassfish\domains\my-domain\lib\jacc-provider-3.0.2-SNAPSHOT.jar - jakartaee10
C:\glassfish-7.0.10\glassfish\domains\my-domain\lib\jacc-provider-3.0.2-SNAPSHOT.jar - jakartaee10

I tested again using Payara with my logging enabled. I see the following behavior difference:

Payara: CustomPolicy constructor called after ear deployment

[2023-11-07T20:28:33.302+0100] [Payara 6.2023.10] [INFO] [NCLS-DEPLOYMENT-02027] [javax.enterprise.system.tools.deployment.autodeploy] [tid: _ThreadID=111 _ThreadName=payara-executor-service-scheduled-task] [timeMillis: 1699385313302] [levelValue: 800] [[
  Selecting file C:\payara-6.2023.10\glassfish\domains\my-amm\autodeploy\my-test.payara.postgresql.ear for autodeployment]]
...
[2023-11-07T20:28:52.903+0100] [Payara 6.2023.10] [INFO] [NCLS-SECURITY-01002] [javax.enterprise.system.core.security] [tid: _ThreadID=111 _ThreadName=payara-executor-service-scheduled-task] [timeMillis: 1699385332903] [levelValue: 800] [[
  Java security manager is disabled.]]
[2023-11-07T20:28:52.903+0100] [Payara 6.2023.10] [INFO] [NCLS-SECURITY-01010] [javax.enterprise.system.core.security] [tid: _ThreadID=111 _ThreadName=payara-executor-service-scheduled-task] [timeMillis: 1699385332903] [levelValue: 800] [[
  Entering Security Startup Service.]]
[2023-11-07T20:28:52.904+0100] [Payara 6.2023.10] [INFO] [NCLS-SECURITY-01143] [javax.enterprise.system.core.security] [tid: _ThreadID=111 _ThreadName=payara-executor-service-scheduled-task] [timeMillis: 1699385332904] [levelValue: 800] [[
  Loading policy provider my.jaccprovider.policy.CustomPolicy.]]
[2023-11-07T20:28:52.913+0100] [Payara 6.2023.10] [INFO] [] [my.jaccprovider.policy.CustomPolicy] [tid: _ThreadID=111 _ThreadName=payara-executor-service-scheduled-task] [timeMillis: 1699385332913] [levelValue: 800] [[
  ### CustomPolicy - constructor - start]]
[2023-11-07T20:28:52.913+0100] [Payara 6.2023.10] [INFO] [] [my.jaccprovider.policy.CustomPolicy] [tid: _ThreadID=111 _ThreadName=payara-executor-service-scheduled-task] [timeMillis: 1699385332913] [levelValue: 800] [[
  ### CustomPolicy - constructor - end]]

Glassfish: CustomPolicy constructor called before ear deployment

[2023-11-07T15:16:54.299543+01:00] [GF 7.0.10] [INFO] [NCLS-SECURITY-01002] [jakarta.enterprise.system.core.security] [tid: _ThreadID=140 _ThreadName=Thread-9] [levelValue: 800] [[
  Java security manager is disabled.]]
[2023-11-07T15:16:54.299543+01:00] [GF 7.0.10] [INFO] [NCLS-SECURITY-01010] [jakarta.enterprise.system.core.security] [tid: _ThreadID=140 _ThreadName=Thread-9] [levelValue: 800] [[
  Entering Security Startup Service.]]
[2023-11-07T15:16:54.301538+01:00] [GF 7.0.10] [INFO] [NCLS-SECURITY-01143] [jakarta.enterprise.system.core.security] [tid: _ThreadID=140 _ThreadName=Thread-9] [levelValue: 800] [[
  Loading policy provider my.jaccprovider.policy.CustomPolicy.]]
[2023-11-07T15:16:54.307551+01:00] [GF 7.0.10] [INFO] [] [my.jaccprovider.policy.CustomPolicy] [tid: _ThreadID=140 _ThreadName=Thread-9] [levelValue: 800] [[
  ### CustomPolicy - constructor - start]]
[2023-11-07T15:16:54.307551+01:00] [GF 7.0.10] [INFO] [] [my.jaccprovider.policy.CustomPolicy] [tid: _ThreadID=140 _ThreadName=Thread-9] [levelValue: 800] [[
  ### CustomPolicy - constructor - end]]
[2023-11-07T15:16:54.313534+01:00] [GF 7.0.10] [INFO] [NCLS-SECURITY-01011] [jakarta.enterprise.system.core.security] [tid: _ThreadID=140 _ThreadName=Thread-9] [levelValue: 800] [[
  Security Service(s) started successfully.]]
...
[2023-11-07T15:16:55.865800+01:00] [GF 7.0.10] [INFO] [] [org.glassfish.soteria.servlet.SamRegistrationInstaller] [tid: _ThreadID=140 _ThreadName=Thread-9] [levelValue: 800] [[
  Initializing Soteria 3.0.3 for context '']]
[2023-11-07T15:16:55.920622+01:00] [GF 7.0.10] [INFO] [faces.config.listener.version] [jakarta.enterprise.resource.webcontainer.faces.config] [tid: _ThreadID=140 _ThreadName=Thread-9] [levelValue: 800] [[
  Initializing Mojarra 4.0.4 for context '']]
...
[2023-11-07T15:16:56.460449+01:00] [GF 7.0.10] [INFO] [AS-WEB-GLUE-00172] [jakarta.enterprise.web] [tid: _ThreadID=140 _ThreadName=Thread-9] [levelValue: 800] [[
  Loading application [__admingui] at [/]]]
[2023-11-07T15:16:56.461418+01:00] [GF 7.0.10] [INFO] [NCLS-CORE-00022] [jakarta.enterprise.system.core] [tid: _ThreadID=140 _ThreadName=Thread-9] [levelValue: 800] [[
  Loading application __admingui done in 3,351 ms]]
[2023-11-07T15:19:05.140118+01:00] [GF 7.0.10] [INFO] [NCLS-DEPLOYMENT-02027] [jakarta.enterprise.system.tools.deployment.autodeploy] [tid: _ThreadID=142 _ThreadName=AutoDeployer] [levelValue: 800] [[
  Selecting file C:\glassfish-7.0.10\glassfish\domains\my-amm\autodeploy\my-test.payara.postgresql.ear for autodeployment]]
[2023-11-07T15:19:05.209142+01:00] [GF 7.0.10] [INFO] [] [my.jaccprovider.policy.CustomPolicy] [tid: _ThreadID=142 _ThreadName=AutoDeployer] [levelValue: 800] [[
  ### CustomPolicy - implies - start]]
[2023-11-07T15:19:05.209142+01:00] [GF 7.0.10] [INFO] [] [my.jaccprovider.policy.CustomPolicy] [tid: _ThreadID=142 _ThreadName=AutoDeployer] [levelValue: 800] [[
  ### CustomPolicy - doImplies - start]]

Trace from my code contains eventually: "NameNotFoundException: No object bound for java:comp/BeanManager" on both 7.0.9 and 7.0.10

  ### CdiUtils - is, exception: javax.naming.NamingException: Lookup failed for java:comp/BeanManager in SerialContext[myEnv={java.naming.factory.state=com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl, java.naming.factory.url.pkgs=com.sun.enterprise.naming}] [Root exception is javax.naming.NameNotFoundException: No object bound for java:comp/BeanManager], type: class javax.naming.NameNotFoundException]]
escay commented 11 months ago

I was not aware a JACC per application option exists in Payara, I assume you mean: https://www.javadoc.io/doc/fish.payara.api/payara-api/6.2023.9/fish/payara/jacc/JaccConfigurationFactory.html No payara specific code is used.

I think (not 100% sure) the dependencies used to build the custom provider are:

        <dependency>
            <groupId>jakarta.authorization</groupId>
            <artifactId>jakarta.authorization-api</artifactId>
            <version>2.1.0</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>jakarta.enterprise</groupId>
            <artifactId>jakarta.enterprise.cdi-api</artifactId>
            <version>3.0.0</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>jakarta.platform</groupId>
            <artifactId>jakarta.jakartaee-api</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.glassfish.security</groupId>
            <artifactId>security</artifactId>
            <version>3.1.1</version>
            <scope>provided</scope>
        </dependency>

I am sure there are no Payara specific dependencies.

escay commented 10 months ago

I can also add that instead of manually looking for: BeanManager foundJndi = jndiLookup("java:comp/BeanManager"); I tried using CDI directly via: beanReference = CDI.current().select(AuthorizationMechanism.class).get(); also does not work on Glassfish, it leads to: java.lang.IllegalStateException: Unable to access CDI.

I can try to build a reproducer if needed.

OndroMih commented 8 months ago

Hi @escay, it looks like the EJB container is initialized before the CDI container, and then CDI is not available in your custom JACC provider. Can you try adding beans.xml file into each of your EJB and WAR modules that are in the EAR?

escay commented 8 months ago

All ejb modules and war modules (and there are many) have beans.xml, most of them use bean-discovery-mode="none" to make deployment quicker / avoid scanning (since they only contain EJB annotations), only web uses some "annotated" for CDI annotations.

The custom JACC provider is already loaded and active before any ear file is deployed when I configure it in Glassfish. Regardless of ear deployment or not it seems to me that there is no BeanManager and no CDI container available for the jacc provider code, in my-domain\lib. Comes back to my question: Would BeanManager be allowed to be found via JNDI in GlassFish 7 from the glassfish-7.0.10\glassfish\domains\my-domain\lib folder? or from a custom jacc provider? I know Payara allows this, I do not know what you would expect / support in Glassfish.

Another note: Within the ear file the BeanManager bm = (BeanManager) context.lookup("java:comp/BeanManager"); is working fine to find any bean and load them in Glassfish 7.

The workaround for me is:

This seems to work reliably in the last few days.

There is no need to improve things in Glassfish at this moment, I can wait to see what the new JakartaEE11 / GF8 solution brings.