SecurityException in AppLifecyle.applicationStarted()

Hi

I just upgraded IDEA to 2017 and jdk to 1.8_131. Then I made a sample project to test services composition and injection in different components, and I got a Security Exception (below), not sure why.

Project files attached.


13:33:30.577 INFO  c.h.c.security.app.LoginWorkerBean - Logged in: 68756a19-a044-84d1-0353-783769de8605 [anonymous]
13:33:30.578 INFO  com.company.test.core.AppLifecycle - *****************************************com.company.test.service.SampleServiceBean@11ac07ce
13:33:30.585 ERROR c.h.cuba.core.sys.AppContextLoader - Error initializing application
java.lang.SecurityException: No security context bound to the current thread
        at com.haulmont.cuba.core.sys.AppContext.getSecurityContextNN(AppContext.java:148) ~[cuba-global-6.4.2.jar:6.4.2]
        at com.haulmont.cuba.core.sys.ServiceInterceptor.aroundInvoke(ServiceInterceptor.java:85) ~[cuba-core-6.4.2.jar:6.4.2]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_131]
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_131]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_131]
        at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_131]
        at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:629) ~[spring-aop-4.3.3.RELEASE.jar:4.3.3.RELEASE]
        at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:618) ~[spring-aop-4.3.3.RELEASE.jar:4.3.3.RELEASE]
        at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70) ~[spring-aop-4.3.3.RELEASE.jar:4.3.3.RELEASE]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:168) ~[spring-aop-4.3.3.RELEASE.jar:4.3.3.RELEASE]
        at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) ~[spring-aop-4.3.3.RELEASE.jar:4.3.3.RELEASE]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.3.RELEASE.jar:4.3.3.RELEASE]
        at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213) ~[spring-aop-4.3.3.RELEASE.jar:4.3.3.RELEASE]
        at com.sun.proxy.$Proxy210.getGlobalService(Unknown Source) ~[na:na]
        at com.company.test.core.AppLifecycle.applicationStarted(AppLifecycle.java:34) ~[app-core-0.1-SNAPSHOT.jar:na]
        at com.haulmont.cuba.core.sys.AppContext.startContext(AppContext.java:234) ~[cuba-global-6.4.2.jar:6.4.2]
        at com.haulmont.cuba.core.sys.AppContext$Internals.startContext(AppContext.java:289) ~[cuba-global-6.4.2.jar:6.4.2]
        at com.haulmont.cuba.core.sys.AbstractWebAppContextLoader.contextInitialized(AbstractWebAppContextLoader.java:74) ~[cuba-global-6.4.2.jar:6.4.2]
        at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4727) [catalina.jar:8.5.9]
        at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5189) [catalina.jar:8.5.9]
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150) [catalina.jar:8.5.9]
        at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:752) [catalina.jar:8.5.9]
        at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:728) [catalina.jar:8.5.9]
        at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:734) [catalina.jar:8.5.9]
        at org.apache.catalina.startup.HostConfig.deployDirectory(HostConfig.java:1107) [catalina.jar:8.5.9]
        at org.apache.catalina.startup.HostConfig$DeployDirectory.run(HostConfig.java:1841) [catalina.jar:8.5.9]
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [na:1.8.0_131]
        at java.util.concurrent.FutureTask.run(FutureTask.java:266) [na:1.8.0_131]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_131]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_131]
        at java.lang.Thread.run(Thread.java:748) [na:1.8.0_131]
avr. 27, 2017 1:33:30 PM org.apache.catalina.core.StandardContext listenerStart
GRAVE: Exception lors de l'envoi de l'?®v?¿nement contexte initialis?® (context initialized) ?á l'instance de classe d'?®coute (listener) com.haulmont.cuba.core.sys.AppContextLoader
java.lang.RuntimeException: java.lang.SecurityException: No security context bound to the current thread
        at com.haulmont.cuba.core.sys.AbstractWebAppContextLoader.contextInitialized(AbstractWebAppContextLoader.java:78)

test1334.zip (222.2K)

Hi Michael,

You use *Service instance from AppLifecycle class. At this moment you have to obtain user session and set SecurityContext to be able to do it, since no one can call services from a client (in your case it is web client) without authentication. I think this behaviour is not related with your update.

It can be achieved using LoginService and system session:


@Inject
protected LoginService loginService;
@Inject
protected WebAuthConfig config; // contains trusted client password
...

try {
    // obtain system session
    UserSession systemSession = loginService.getSystemSession(config.getTrustedClientPassword());
    // set security context and perform secured action: Runnable or SecuredOperation<T>
    AppContext.withSecurityContext(new SecurityContext(systemSession), () -> {
        // your service call here
    });
} catch (LoginException e) {
    throw new IllegalStateException("Unable to login with trusted client password", e);
}

This approach is required only when your code does not have user context: it is not UI class or executed without user interaction (for instance as scheduler), and not initiated by user.

Promising, but in the core module (where AppLifecycle is) the code does not have access to WebAuthConfig class. Any alternative ?

Sorry, you was too fast. I finally tried to have the code in the correct class (needing a coffee!), but in the core module (where AppLifecycle is) the code does not have access to WebAuthConfig class. Any alternative ?

If your AppLifecycle is located in core module then you can use com.haulmont.cuba.security.app.Authentication bean:


@Inject
Authentication authentication;
...
authentication.begin();
try {
   // valid current thread's user session presents here
} finally {
   authentication.end();
}

Note that, you can also use @Authenticated annotation in case you use *Bean instead of *Service on core.

See also: System Authentication - CUBA Platform. Developer’s Manual

2 Likes

ExtAppMainWindow

Here you don’t need to use this approach. Since any screen has user context.

Why do you insert this code here and not to AppLifecycle class ?

Thanks Yuriy, we’re making progress here. I tried @Authenticated on AppLifeCycle applicationStarted() but it did not work (despite not being a Service as you said, maybe name should finish by Bean ?). However it works with Authentication injection.

Now I later run into an issue in ExtAppMainWindow when calling the getGlobalService() method of SampleService : java.lang.ClassCastException: com.company.test.service.GlobalServiceBean cannot be cast to com.company.test.service.GlobalService

What is strange is that for this sample project, I created both GlobalService and SampleService with Studio so following the exact template.

I also tried to remove completely the AppLifecycle class. Same issue.

Code below.


@Component("test_AppLifecycle")
public class AppLifecycle implements AppContext.Listener {

    @Inject
    private Logger log;

    @Inject
    private LoginService loginService;

    @Inject
    Authentication authentication;

    @Inject
    SampleService sampleService;

    //---

    public AppLifecycle() {
        AppContext.addListener(this);
    }

    @Override
    public void applicationStarted() {
        authentication.begin();
        try {
            log.info("*****************************************" + this.sampleService);
            log.info("*****************************************" + this.sampleService.getGlobalService());
        } finally {
            authentication.end();
        }
    }

    @Override
    public void applicationStopped() {

    }
}

public class ExtAppMainWindow extends AppMainWindow {

    @Inject
    private Logger log;

    @Inject
    private SampleService sampleService;

    public void ready() {
        log.info("*****************************************" + this.sampleService);
        log.info("*****************************************" + this.sampleService.getGlobalService());
    }

}

Mike

PS : I read the documentation, the subtility you mention about authentication in Service & Bean could benefit more attention into it. I’m not sure I understood it myself to be honest.

PS2 : what is the markdown to have names in red as you do ?

This is another issue, I open a new thread.

Why do you want to cast GlobalService to GlobalServiceBean? We actively use AOP and often instances of *Service cannot be cast to *ServiceBean since they are proxies with additional logging/security functionality. Actually, in web application there are only proxies that use remote invocations for core services. Thus, do not cast your service instances to implementations.

P.S. Use ` symbols to show red highlighted words.

If you look at the code, I do not cast to implementation, this is defeating the purposes of interfaces.

However I tried two ways of getting the same thing:

  • logging the instances I got through injection for understanding purposes
  • have a method returning an instance of Service interface, and log the return

I have made progress on the matter and opened a new thread, as this one was for the security stuff, which is solved.