How to set session attribute in sso app service provider

Hi guys,

Application A is Identity Provider (IDP) http://localhost:8080/app
Application B is Service Provider (SP) http://localhost:8082/app

Both applications have implemented the db-per-tenant approach explained in this example: https://github.com/cuba-labs/db-per-tenant and discussed here: https://www.cuba-platform.com/discuss/t/cuba-multi-tenant-with-database-per-tenant
After logging in, Application A will display an extended AbstractMainWindow, where an user can choose from an optionsList the database which he wants to work with.
After choosing the database, the user clicks a button and a new tab is opened with the application B. On button click, I used the folowing code to open app B in a new tab.


WindowManager wm = getWindowManager();
wm.showWebPage("http://localhost:8082/app");

This is working good, regarding SSO (it redirects to application B and no login page is displayed)
Also on button click, before opening app B with the code above, I have set the session attribute:


userSessionSource.getUserSession().setAttribute("tenantDbAddress","localhost/dbpt_tenant1")

After this, the Session Attribute tenantDbAddress is present in application A but it’s not present in application B so the connection to the additional datastore is not made.
I want to ask you if this is a normal behavior considering that authentication is made from Application A?
If yes, which is the way to send the database parameter from app A to app B and set the session attribute to trigger the connection creation?

Thank you.

2 Likes

Hi,

It is a really interesting system architecture. Thanks for sharing your experience!

First of all, let me clarify implementation details of IDP:

  • IDP SSO subsystem is developed to be useful not only for CUBA-based web applications. It can be used by any third party application that can use IDP as a simple web service to authenticate users.
  • IDP stores information about authentication using separate objects - IdpSession, it does not use standard UserSession.
  • IdpSession is a record about centralized authentication across applications. It is stored on a middle ware of a server that plays role of IDP.
  • If you use IDP with CUBA-based service provider then IdpSession ID is stored as a session attribute of UserSession object - “idpSessionId”.
  • IdpSession contains its own session attributes that can be updated only from a server with IDP role.

At the moment, there is no convenient API to update IdpSession attributes, but you can do it yourself:

  1. Create middle ware service
  2. Implement method that will obtain IdpSession for currently logged in user and update its attributes:

@Service(DemoIdpService.NAME)
public class DemoIdpServiceBean implements DemoIdpService {
    @Inject
    private IdpSessionStore idpSessionStore;
    @Inject
    private UserSessionSource userSessionSource;

    @Override
    public void setTenantToIdpSession(String tenant) {
        UserSession userSession = userSessionSource.getUserSession();
        String idpSessionId = userSession.getAttribute(IdpService.IDP_USER_SESSION_ATTRIBUTE);

        if (idpSessionId != null) {
            IdpSession idpSession = idpSessionStore.getSession(idpSessionId);
            if (idpSession != null) {
                Map<String, Serializable> attributes = idpSession.getAttributes();
                if (attributes == null) {
                    attributes = new HashMap<>();
                    idpSession.setAttributes(attributes);
                }
                attributes.put("tenantDbAddress","localhost/dbpt_tenant1");
            }
        }
    }
}
  1. Execute this method in application with IDP role
  • There is only one drawback with this solution: if you use middle ware cluster for IDP application then it will not update cluster state with for IDP session attributes. This has been fixed in https://youtrack.cuba-platform.com/issue/PL-9312

IdpSession attributes can be loaded in a service provider using the following way:

  1. Obtain IDP session id from UserSession
  2. Perform HTTP Get request to IDP and read IDP session details

public class ExtAppMainWindow extends AppMainWindow {
    @Inject
    private UserSession userSession;
    @Inject
    private WebAuthConfig webAuthConfig;

    @Override
    public void init(Map<String, Object> params) {
        super.init(params);

        String idpSessionId = userSession.getAttribute(IdpService.IDP_USER_SESSION_ATTRIBUTE);

        // now we have to use HTTP request to get whole session object from IDP
        // see com.haulmont.idp.controllers.IdpServiceController
        String idpBaseURL = webAuthConfig.getIdpBaseURL();
        if (!idpBaseURL.endsWith("/")) {
            idpBaseURL += "/";
        }
        String idpSessionGetUrl = idpBaseURL + "service/get";

        URI sessionGetUri;
        try {
            sessionGetUri = new URIBuilder(idpSessionGetUrl)
                    .addParameter("idpSessionId", idpSessionId)
                    .addParameter("trustedServicePassword", webAuthConfig.getIdpTrustedServicePassword())
                    .build();
        } catch (URISyntaxException e) {
            throw new RuntimeException("incorrect URL");
        }

        HttpGet httpGet = new HttpGet(sessionGetUri);

        HttpClientConnectionManager connectionManager = new BasicHttpClientConnectionManager();
        HttpClient client = HttpClientBuilder.create()
                .setConnectionManager(connectionManager)
                .build();

        String idpSessionJson;
        try {
            HttpResponse httpResponse = client.execute(httpGet);
            int statusCode = httpResponse.getStatusLine().getStatusCode();
            if (statusCode != 200) {
                // error handling
            }
            idpSessionJson = new BasicResponseHandler()
                    .handleResponse(httpResponse);
        } catch (IOException e) {
            // error handling
        } finally {
            connectionManager.shutdown();
        }

        // now we can parse idpSessionJson content and read session attributes
    }
}

I’ve created a small demo of this approach, see https://github.com/cuba-labs/idp-session-attributes

Please note, that it is not a production ready way to work with IDP and you have to test it and adopt to your needs.

We are planning to introduce convenient APIs for such a IDP<->SP interaction in the future after we collect feedback from the community.

Hi Yuriy,

Thanks for the detailed answer.

I tested the demo and i see the attribute set on idpsession on idp app.
If i open the sp app I get the error bellow.

I’ve attached both archives

Thank you


29-Jun-2017 21:21:24.111 SEVERE [http-nio-8082-exec-26] org.apache.catalina.core.StandardWrapperValve.invoke Servlet.service() for servlet [app_servlet] in context with path [/app] threw exception
 java.lang.RuntimeException: Unable to invoke no-args constructor for interface java.io.Serializable. Register an InstanceCreator with Gson for this type may fix this problem.
	at com.google.gson.internal.ConstructorConstructor$14.construct(ConstructorConstructor.java:226)
	at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:210)
	at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.read(TypeAdapterRuntimeTypeWrapper.java:41)
	at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.read(MapTypeAdapterFactory.java:187)
	at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.read(MapTypeAdapterFactory.java:145)
	at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:129)
	at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:220)
	at com.google.gson.Gson.fromJson(Gson.java:887)
	at com.google.gson.Gson.fromJson(Gson.java:852)
	at com.google.gson.Gson.fromJson(Gson.java:801)
	at com.google.gson.Gson.fromJson(Gson.java:773)
	at com.haulmont.cuba.web.auth.IdpAuthProvider.getIdpSession(IdpAuthProvider.java:243)
	at com.haulmont.cuba.web.auth.IdpAuthProvider.doFilter(IdpAuthProvider.java:151)
	at com.haulmont.cuba.web.sys.CubaHttpFilter.filterByAuthProvider(CubaHttpFilter.java:114)
	at com.haulmont.cuba.web.sys.CubaHttpFilter.doFilter(CubaHttpFilter.java:91)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:474)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
	at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:624)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:349)
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:783)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:789)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1437)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.UnsupportedOperationException: Interface can't be instantiated! Interface name: java.io.Serializable
	at com.google.gson.internal.UnsafeAllocator.assertInstantiable(UnsafeAllocator.java:117)
	at com.google.gson.internal.UnsafeAllocator.access$000(UnsafeAllocator.java:31)
	at com.google.gson.internal.UnsafeAllocator$1.newInstance(UnsafeAllocator.java:49)
	at com.google.gson.internal.ConstructorConstructor$14.construct(ConstructorConstructor.java:223)

idp-demo.zip (25.7K)

sp.zip (20.8K)

Hi,
Any updates about this issue?

Thanks

Hi,

I’ve reproduced the issue. It is caused by our usage of Gson library that can perfectly deserialize Map<String, Object> but fails with Map<String, Serializable>. I’ll fix it for the upcoming 6.6 release, see this issue: https://youtrack.cuba-platform.com/issue/PL-9401

Thank you! It’s ok now.