Skip to the content.

Integrating MSTR with OIDC

I previously wrote about integrating MSTR with SAML.

In some cases, OpenID Connect (OIDC) may be a better option than SAML. In particular, OIDC gives you a way to authenticate and simultaneously obtain an access token for API calls.

MSTR does not support OIDC officially, but it looks like you can use a similar approach to implementing SAML. The SAML support wraps MSTR with an open-source library (Spring Security) that intercepts requests, redirects users who are not authenticated, and propagates the authentication context to MSTR through the HTTP session.

I found Spring Security too complex to set up for OIDC via XML; more importantly I could not find an example on the Web that used Spring Security for OIDC to wrap existing servlets, without Spring Boot. I did find the pac4j library which is much easier to get started with.

Here’s what I did as a proof-of-concept:

 <dependency>
      <groupId>org.pac4j</groupId>
      <artifactId>j2e-pac4j</artifactId>
      <version>3.0.0</version>
    </dependency>
    <dependency>
      <groupId>org.pac4j</groupId>
      <artifactId>pac4j-oidc</artifactId>
      <version>2.3.1</version>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <version>2.4</version>
      <scope>provided</scope>
    </dependency>
  • Add filters to web.xml:
  <!-- BEGIN Pac4j config -->
  <filter>
    <filter-name>OidcFilter</filter-name>
    <filter-class>org.pac4j.j2e.filter.SecurityFilter</filter-class>
    <init-param>
      <param-name>configFactory</param-name>
      <param-value>bill.OidcConfigurationFactory</param-value>
    </init-param>
    <init-param>
      <param-name>clients</param-name>
      <param-value>OidcClient</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>OidcFilter</filter-name>
    <url-pattern>/servlet/mstrWeb</url-pattern>
  </filter-mapping>
  <filter>
    <filter-name>callbackFilter</filter-name>
    <filter-class>org.pac4j.j2e.filter.CallbackFilter</filter-class>
    <init-param>
      <param-name>defaultUrl</param-name>
      <param-value>/</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>callbackFilter</filter-name>
    <url-pattern>/callback</url-pattern>
  </filter-mapping>  
  <filter>
    <filter-name>postLoginFilter</filter-name>
    <filter-class>bill.ProfileFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>postLoginFilter</filter-name>
    <url-pattern>/servlet/mstrWeb</url-pattern>
  </filter-mapping>  
  
  <!-- END Pac4j config -->
  • Create config class to set up pac4j OIDC client:
// bill.OidcConfigurationFactory
package bill;

public class OidcConfigurationFactory implements ConfigFactory {
	@Override
	public Config build(Object... arg0) {
		OidcConfiguration oidcConfig = new OidcConfiguration();
		oidcConfig.setClientId("YOUR CLIENT ID");
		oidcConfig.setSecret("YOUR CLIENT SECRET");
		oidcConfig.setDiscoveryURI("https://your-sso-server/.well-known/openid-configuration");
		
		OidcClient<OidcProfile> client = new OidcClient<>(oidcConfig);
		
        final Clients clients = new Clients("http://YOUR-URL/callback", client);

        final Config config = new Config(clients);
        return config;        
	}
}
  • Create filter to intercept request post-authentication and put object in session for MSTR:
package bill;

import com.microstrategy.auth.SSOUserInfo;

public class ProfileFilter implements Filter {

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		// get current user from pac4j post-authentication
		final WebContext context = new J2EContext((HttpServletRequest) request, (HttpServletResponse) response);
		final ProfileManager<OidcProfile> pm = new ProfileManager<>(context);
		final OidcProfile userProfile = pm.get(true).get();
		final String oidcUsername = userProfile.getUsername();
		
		HttpSession session = ((HttpServletRequest) request).getSession();
		SSOUserInfo obj = ((SSOUserInfo)session.getAttribute("SSOUserInfo"));
	
		if (obj == null || obj.getUserId() != oidcUsername) {
			// need to create SSOUserInfo object
			obj = new OidcSSOUserInfo(userProfile);
			session.setAttribute("SSOUserInfo", obj);
		}	
		
		chain.doFilter(request, response);
	}

	// + empty init, destroy
}

package bill;

import org.pac4j.oidc.profile.OidcProfile;
import com.microstrategy.auth.SSOUserInfo;

public class OidcSSOUserInfo implements SSOUserInfo {
	private static final long serialVersionUID = -7806359799358590301L;
	private String userId;
	private List<Group> groups;
	private String distinguishedName;
	private boolean isValid;
	private String displayName;
	private String email;
	
	public OidcSSOUserInfo(OidcProfile profile) {
		this.userId = profile.getUsername();
		this.groups = new ArrayList<Group>();
		this.distinguishedName = "";
		this.isValid = true;
		this.displayName = profile.getDisplayName();
		this.email = profile.getEmail();
	}
	
	@Override
	public String getUserId() {
		return this.userId;
	}

	@Override
	public List<Group> getGroups() {
		return this.groups;
	}

	@Override
	public String getDistinguishedName() {
		return this.distinguishedName;
	}

	@Override
	public boolean isValid() {
		return this.isValid;
	}

	@Override
	public String getDisplayName() {
		return this.displayName;
	}

	@Override
	public String getEmail() {
		return this.email;
	}
}

  • If you want to do any JIT provisioning of the user in MSTR, you can do it the same as with SAML: get the SSOUserInfo object from session and provision the user in handlesAuthenticationRequest:
public class ExternalSecurity extends DefaultExternalSecurity {
	@Override
	public int handlesAuthenticationRequest(RequestKeys reqKeys, ContainerServices cntSvcs, int arg2) {
		SSOUserInfo obj = ((SSOUserInfo)cntSvcs.getSessionAttribute("SSOUserInfo"));
		String userId = obj.getUserId();

		// ... provision user ...
		return super.handlesAuthenticationRequest(reqKeys, cntSvcs, arg2);
	}
}
Written on May 21, 2018