MicroStrategy ONE

Spring Security SAML Customization for MicroStrategy Library

MicroStrategy ONE (June 2024) includes a Spring Security SAML provider upgrade to 6.2. This major upgrade includes deprecated classes and methods. The following topic illustrates the SAML workflow and beans you can leverage for customization.

SAML Login Workflow

The diagrams and workflows below illustrate how authentication-related requests are handled with different authentication configurations. The following points should be considered when using these workflow diagrams:

  • Double-line arrows represent HTTP requests and responses and single-line arrows represent Java cells.

  • The object names correspond to the bean IDs in the configuration XML files. You must view the configuration files to identify which Java classes define those beans.

  • Only beans involved in request authentication are included. Filters that simply pass the request along the filter chain or perform action not directly involved in request authentication are not included. Each request passes through multiple Spring Security filter, as described in Configuration files and bean classes.

Generate <saml2:AuthnRequest>

  1. The multi-mode login page submits a POST: {BasePath}/auth/login request, which is intercepted by the mstrMultiModeFilter bean.

  2. The multi-mode login filter recognizes this is a SAML login request and delegates the work to the mstrMultModeFilter SAML login filter bean.

  3. The SAML login filter delegates to the mstrSamlEntryPoint SAML entry point bean, which performs a redirection to saml/authenticate by default.

    The redirect supports multi-tenant scenarios. If you've configured more than one asseting party, you can first redirect the user to a picker or in most cases, leave it as is.

  4. The browser redirects and sends a GET: {BasePath}/saml/authenticate request, which is intercepted by the mstrSamlAuthnRequestFilter bean.

  5. The mstrSamlAuthnRequestFilter bean is <saml2:AuthnRequest>, which generates an endpoint that creates, sings, serializes, and encodes a <saml2:AuthnRequest> and redirects to the SSO login endpoint.

Bean Descriptions

Bean ID Java Class Description

mstrEntryPoint

com.microstrategy.auth.saml.authnrequest.SAMLEntryPointWrapper A subclass of LoginUrlAuthenticationEntryPoint that performs a redirect to where it is set in the constructor by the String redirectFilterUrl parameter.

mstrSamlAuthnRequestFilter

org.springframework.security.saml2.provider.service.web.Saml2WebSsoAuthenticationRequestFilter

By default, this filter responds to the /saml/authenticate/** endpoint and the result is a redirect that includes a SAMLRequest parameter that contains the signed, deflated, and encoded <saml2:AuthnRequest>

Customization

Before AuthnRequest is sent, you can leverage the mstrSamlEntryPoint or mstrSamlAuthnRequestFilter bean depending on the time you want your code to be executed, create a subclass, and override the corresponding method with your own logic.

Prior to /saml/authenticate

To customize before /saml/authenticate redirect:

  1. Create a MySAMLEntryPoint class that extends com.microstrategy.auth.saml.authnrequest.SAMLEntryPointWrapper and overrides the commence method.

  2. Execute your code before calling super.commence:

    Copy
    public class MySAMLEntryPoint extends SAMLEntryPointWrapper {
        MySAMLEntryPoint(String redirectFilterUrl){
            super(redirectFilterUrl);
        }
        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
            //>>> Your logic here
            super.commence(request, response, e);
        }
    }
  3. Configure your customized bean (Fully Qualified Class Name) in SAMLConfig.xml under the classes/auth/custom folder with a mstSamlEntryPoint bean ID to replace the existing bean:

    The constructor argument must be exactly the same as the original, if it is not customized.

    Copy
    <!--  Entry point for SAML authentication mode -->
    <bean id="mstrSamlEntryPoint" class="com.microstrategy.custom.MySAMLEntryPoint">
        <constructor-arg value="/saml/authenticate"/>
    </bean>

Prior to SSO IDP Redirect

To customize before the SSO IDP redirect:

  1. Create a MySAMLAuthenticationRequestFilter class that extends org.springframework.security.saml2.provider.service.web.Saml2WebSsoAuthenticationRequestFilter.

  2. Override the doFilterInternal method.

  3. Execute your code before calling super.doFilterInternal.

    Copy
    public class MySAMLAuthenticationRequestFilter extends Saml2WebSsoAuthenticationRequestFilter {
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            //>>> Your logic here
            super.doFilterInternal(request, response, filterChain);
        }
    }
  4. Configure your customized bean (Fully Qualified Class Name) in SAMLConfig.xml under the classes/auth/custom folder with a mstSamlAuthnRequestFilter bean ID to replace the existing bean:

    The constructor argument must be exactly the same as the original, if it is not customized.

    Copy
    <bean id="mstrSamlAuthnRequestFilter" class="MySAMLAuthenticationRequestFilter">
        <constructor-arg ref="samlAuthenticationRequestContextResolver"/>
    </bean>

Customize the AuthnRequest Object

The AuthnRequest object is constructed by mstrSamlAuthnRequestFilter as a part of the SAML request. If you want to customize the AuthnRequest object before it is sent to SSO IDP, you can extend SAMLAuthenticationAuthnRequestCustomizer:

In previous releases, AuthnRequest customization is performed by extending SAMLAuthenticationRequestContextConverter, which is deprecated and removed in MicroStrategy ONE (June 2024) in favor of SAMLAuthenticationAuthnRequestCustomizer.

  1. Create a MyAuthnRequestCustomizer class that extends com.microstrategy.auth.saml.authnreques.SAMLAuthenticationAuthnRequestCustomizer, and override the accept method:

    Copy
    package com.microstrategy.custom.auth;
    import ...;
    public class MyAuthnRequestCustomizer extends SAMLAuthenticationAuthnRequestCustomizer {
        @Override
        public void accept(OpenSaml4AuthenticationRequestResolver.AuthnRequestContext authnRequestContext) {
            super.accept(authnRequestContext);
            AuthnRequest authnRequest = authnRequestContext.getAuthnRequest();
            // Add your AuthnRequest customization here...
        }
    }
  2. Configure your customized bean (Fully Qualified Class Name) in SAMLConfig.xml under the classes/auth/custom folder with a authnRequestCustomizer bean ID to replace the existing bean:

    Copy
    <bean id="authnRequestCustomizer" class="com.microstrategy.custom.auth.MyAuthnRequestCustomizer"/>

Generate <saml2:Response>

  1. SSO redirects the user to the MicroStrategy Web application. The redirect request contains a SAML assertion that describes the authenticated user.

  2. The mstrSamlProcessingFilter SAML processing filter bean extracts the SAML assertion from the request and passes it to the samlAuthenticationProvider authentication provider bean.

  3. The samlAuthenticationProvider bean verifies the assertion then calls the Intelligence server credentials provider to build an Intelligence server credentials object from the SAML assertion information.

  4. The samlAuthenticationProvider bean passes the Intelligence server credentials to the Session Manager to create an Intelligence server session.

  5. The SAML processing filter calls the login success handler, which redirects the browser to the original request.

Bean Descriptions

Bean ID Java Class Description

mstrSamlProcessingFilter

org.springframework.security.saml2.provider.service.web.authentication.Saml2WebSsoAuthenticationFilter This is the core filter that is responsible for handling the SAML login response (SAML assertion) that comes from the IDP server.

samlAuthenticationProvider

com.microstrategy.auth.saml.response.SAMLAuthenticationProviderWrapper

This bean is responsible for authenticating a user based on information extracted from the SAML assertion.

samlIserverCredentialsProvider

com.microstrategy.auth.saml.SAMLIServerCredentialsProvider This bean is responsible for creating and populating an IServerCredentials instance that defines the credentials for creating Intelligence server sessions. The IServerCredentials object is passed to the Session Manager's login method, which creates the Intelligence server session.

Customization

The following content uses the real class name, instead of the bean name. You can find the bean name in SAMLConfig.xml.

You can perform the following customizations:

  • Retrieve more information from SAMLResponse

  • Customize the login process

  • Customize SAMLAssertion validation

Retrieve More Information from SAMLResponse

The mstrSamlProcessingFilter bean is the first layer that directly accesses the SAML response. The bean accepts the raw HttpServletRequest, which contains the samlResponse, and produces SAMLAuthenticationToken. It is then passed to SAMLAuthenticationProviderWrapper to perform authentication validation in later steps.

  1. MicroStrategy recommends that you create a MySAMLConverter class that extends the com.microstrategy.auth.saml.response.SAMLAuthenticationTokenConverter class.

  2. Override the convert method and call super.convert, which can get com.microstrategy.auth.saml.response.SAMLAuthenticationToken, a subclass of Saml2AuthenticationToken.

  3. Extract the information from the raw request, then return an instance that is a subclass of Saml2AuthenticationToken:

    Copy
    public class MySAMLConverter extends SAMLAuthenticationTokenConverter {
        public MySAMLConverter(Saml2AuthenticationTokenConverter delegate) {
            super(delegate);
        }
        @Override
        public Saml2AuthenticationToken convert(HttpServletRequest request) {
            Saml2AuthenticationToken samlAuthenticationToken = super.convert(request);
            // >>> Extract info from request that you are interested in
            return samlAuthenticationToken;
        }
    }
  4. Configure your customized bean (Fully Qualified Class Name) in SAMLConfig.xml under the classes/auth/custom folder with a samlAuthenticationConvertor bean ID:

    The constructor argument must be exactly the same as the original, if it is not customized.

    Copy
    <bean id="samlAuthenticationConverter" class="com.microstrategy.custom.MySAMLConverter">
        <constructor-arg ref="saml2AuthenticationConverter"/>
    </bean>

Customize the Login Process

To verify SAML 2.0 responses, mstrSamlProcessingFilter delegates authentication work to samlAuthenticationProvider. It authenticates a user based on information extracted from a SAML assertion and logs into the Intelligence sever by calling the internal login method.

You can customize this login process at the following three time points:

Point 1: When Pre-Processing the Assertion Before Validating the SAML Response
  1. Create a MySAMLAuthenticationProviderWrapper class that extends com.microstrategy.auth.saml.response.SAMLAuthenticationProviderWrapper and overrides the authenticate method:

    Copy
    public class MySAMLAuthenticationProviderWrapper extends SAMLAuthenticationProviderWrapper {
        @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
            // >>>> Do your own work before saml assertion validation ---> Point ① in the above diagram
            Authentication auth =  super.authenticate(authentication);
            return auth;
        }
    }
  2. Configure your customized bean (Fully Qualified Class Name) in SAMLConfig.xml under the classes/auth/custom folder with a samlAuthenticationProvider bean ID and keep the existing bean:

    The two constructor arguments must be exactly the same as the original, if it is not customized.

    Copy
    <bean id="samlAuthenticationProvider" class="com.microstrategy.custom.MySAMLAuthenticationProviderWrapper">
        <property name="assertionValidator" ref="samlAssertionValidator"/>
        <property name="responseAuthenticationConverter" ref="samlResponseAuthenticationConverter"/>
    </bean>
Point 2: A time between Point 1 and 3
  1. Create a MySAMLAuthenticationProviderWrapper class that extends com.microstrategy.auth.saml.response.SAMLAuthenticationProvider and overrides the authenticate method:

    Copy
    public class MySAMLAuthenticationProviderWrapper extends SAMLAuthenticationProvider {
        private @Autowired
        SessionManagerLocator sessionManagerLocator;
        private @Autowired
        HttpServletRequest request;

        private @Autowired(required = false)
        OAuthTokenProvider oAuthTokenProvider;

        @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        
            Authentication authResult =  super.authenticate(authentication);
            
            // >>>> Do something after assertion validation while before iserver login ---> Point ② in the above diagram
            
            IServerCredentials credentials = (IServerCredentials) authResult.getCredentials();
            if (! Util.isAdminSession(request)) {
                // No implicit OAuth after SAML login
                if (oAuthTokenProvider == null) {
                    SessionManager sessionManager = sessionManagerLocator.getSessionManager();
                    try {
                        sessionManager.login(credentials);
                    } catch (Exception ex) {
                        throw new AuthenticationServiceException("IServer authentication failed", ex);
                    }
                }
            }
            return new AuthenticationWithIServerCredentials(authResult, credentials);
        }
    }
  2. Configure your customized bean (Fully Qualified Class Name) in SAMLConfig.xml under the classes/auth/custom folder with the samlAuthenticationProvider bean ID and keep the existing bean:

    The two constructor arguments must be exactly the same as the original, if it is not customized.

    Copy
    <bean id="samlAuthenticationProvider" class="com.microstrategy.custom.MySAMLAuthenticationProviderWrapper">
        <property name="assertionValidator" ref="samlAssertionValidator"/>
        <property name="responseAuthenticationConverter" ref="samlResponseAuthenticationConverter"/>
    </bean>
Point 3: When Filtering Security Roles After Logging in to the Intelligence Server
  1. Create a MySAMLAuthenticationProviderWrapper class that extends com.microstrategy.auth.saml.response.SAMLAuthenticationProviderWrapper and overrides the authenticate method:

    Copy
    public class MySAMLAuthenticationProviderWrapper extends SAMLAuthenticationProviderWrapper {
        @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
            Authentication auth =  super.authenticate(authentication);
            // >>>> Do something after iserver login ---> Point ③ in the above diagram
            return auth;
        }
    }
  2. Configure your customized bean (Fully Qualified Class Name) in SAMLConfig.xml under the classes/auth/custom folder with a samlAuthenticationProvider bean ID and keep the existing bean:

    The two constructor arguments must be exactly the same as the original, if it is not customized.

    Copy
    <bean id="samlAuthenticationProvider" class="com.microstrategy.custom.MySAMLAuthenticationProviderWrapper">
        <property name="assertionValidator" ref="samlAssertionValidator"/>
        <property name="responseAuthenticationConverter" ref="samlResponseAuthenticationConverter"/>
    </bean>

Customize SAMLAssertion Validation

To verify SAML 2.0 responses, mstrSamlProcessingFilter delegates authentication work to the samlAuthenticationProvider bean, which is com.microstrategy.auth.saml.response.SAMLAuthenticationProviderWrapper.

You can configure this in the following ways:

  • Set a clock skew or authentication age for timestamp validation

  • Perform additional validation

  • Coordinate with UserDetailsService

Set a Clock Skew for Timestamp Validation

It is common for your web and IDP servers to have system clocks that are not perfectly synchronized. You can configure the default SAMLAssertionValidator assertion validator with some tolerance.

  1. Open the SAMLConfig.xml file under the classes/auth/custom folder.

  2. Set the responseSkew property to your custom value. By default, it is 300 seconds.

    Copy
    <bean id="samlAssertionValidator" class="com.microstrategy.auth.saml.response.SAMLAssertionValidator">
          <property name="responseSkew" value="300"/>
    </bean>
Set an Authentication Age for Timestamp Validation

By default, the system allows users to single sign on for up to 2,592,000 seconds since their initial authentication with the IDP (based on the AuthInstance value of the authentication statement). Some IDPs allow users to stay authenticated for longer periods of time and you may need to change the default value.

  1. Open the SAMLConfig.xml file under the classes/auth/custom folder.

  2. Set the maxAuthenticationAge property in the default SAMLAssertionValidator assertion validator to your customized value:

    Copy
    <bean id="samlAssertionValidator" class="com.microstrategy.auth.saml.response.SAMLAssertionValidator">
          <property name="maxAuthenticationAge" value="2592000"/><!-- 30 days -->
    </bean>
Perform Additional Validation

The new spring SAML framework performs minimal validation on SAML 2.0 assertions. After verifying the signature, the spring SAML framework:

  • Validates the <AudienceRestriction> and <DelegationRestriction> conditions.

  • Validate <SubjectConfirmation>, expect for any IP address information

MicroStrategy recommends to call super.convert(). You can skip this call if you don't need it to check the <AudienceRestriction> or <SubjectConfirmation> since you are checking those yourself.

  1. Configure your own assertion validator that extends com.microstrategy.auth.saml.response.SAMLAssertionValidator.

  2. Perform your own validation. For example, you can use OpenSAML's OneTimeUseConditionValidator to also validate a <OneTimeUse> condition:

    Copy
    public class MySAMLAssertionValidator extends SAMLAssertionValidator {
        @Override
        public Saml2ResponseValidatorResult convert(OpenSaml4AuthenticationProvider.AssertionToken token) {
            Saml2ResponseValidatorResult result = super.convert(token);
            OneTimeUseConditionValidator validator = ...;
            Assertion assertion = token.getAssertion();
            OneTimeUse oneTimeUse = assertion.getConditions().getOneTimeUse();
            ValidationContext context = new ValidationContext();
            try {
                if (validator.validate(oneTimeUse, assertion, context) == ValidationResult.VALID) {
                    return result;
                }
            } catch (Exception e) {
                return result.concat(new Saml2Error(INVALID_ASSERTION, e.getMessage()));
            }
            return result.concat(new Saml2Error(INVALID_ASSERTION, context.getValidationFailureMessage()));
        }
    }
  3. Configure your customized bean (Fully Qualified Class Name) in SAMLConfig.xml under the classes/auth/custom folder with the samlAssertionValidator bean ID to replace the existing one:

    Copy
    <bean id="samlAssertionValidator" class="com.microstrategy.custom.MySAMLAssertionValidator">
            <property name="maxAuthenticationAge" value="2592000"/><!-- 30 days -->
            <property name="responseSkew" value="300"/>
    </bean>

To set properties, see Set a Clock Skew for Timestamp Validation or Set an Authentication Age for Timestamp Validation.

Customize Intelligence Server Credentials Object with the SAML Assertion Information

You can overwrite SAMLUserDetailsService to customize Intelligence server credentials.

To make adjustments on Intelligence server credentials that you created, extend com.microstrategy.auth.saml.SAMLIServerCredentialsProvider:

  1. Create MySAMLUserDetailsService by extending the SAMLIServerCredentialsProvider interface and implement methods:

    Copy
    package com.microstrategy.custom.auth;
    import ...;
    public class MySAMLUserDetailsService extends SAMLIServerCredentialsProvider {
        @Override
        public Object loadUserBySAML(SAMLCredential samlCredential) throws AuthenticationException {
            SAMLIServerCredentials iServerCredentials = (SAMLIServerCredentials) super.loadUserBySAML(samlCredential);

            // customize iserver credentials object with saml credential object and other config properties

            return iServerCredentials;
        }
    }
  2. Configure your customized bean (Fully Qualified Class Name in SAMLConfig.xml under the classes/auth/custom folder with a samlIserverCredentialsProvider bean ID and keep the existing bean:

    The constructor arguments and properties must be exactly the same as the original, if you don't customize them.

    Copy
    <bean id="samlIserverCredentialsProvider" class="com.microstrategy.auth.saml.SAMLIServerCredentialsProvider">
        <!-- SAML Attribute mapping -->
        <property name="displayNameAttributeName" value="DisplayName" />
        <property name="dnAttributeName" value="DistinguishedName" />
        <property name="emailAttributeName" value="EMail" />
        <property name="groupAttributeName" value="Groups" />

        <!-- Parser for user group information -->
        <property name="groupParser" ref="samlGroupParser" />
        <!-- Bean responsible for mapping user groups to roles -->
        <property name="roleBuilder" ref="samlRoleBuilder"/>
    </bean>

To construct Intelligence server credentials on your own, directly implement com.microstrategy.auth.saml.SAMLUserDetailsService:

  1. Create MySAMLUserDetailsService by implementing SAMLUserDetailsService interface and implement methods:

    Copy
    package com.microstrategy.custom.auth;
    import ...;
    public class MySAMLUserDetailsService implements SAMLUserDetailsService {
        @Override
        public Object loadUserBySAML(SAMLCredential samlCredential) throws UsernameNotFoundException {
            SAMLIServerCredentials iServerCredentials = new SAMLIServerCredentials();

            // customize iserver credentials object with saml credential object and other config properties
            iServerCredentials.setUsername(samlCredential.getNameID().getValue());

            return iServerCredentials;
        }

        @Override
        public void loadSAMLProperties(SAMLConfig samlConfig) {
            // load attributes from MstrSamlConfig.xml from start up, so that it could be utilized by `loadUserBySAML(...)`
        }
    }
  2. Configure your customized bean (Fully Qualified Class Name) in SAMLConfig.xml under the classes/resources/SAML/custom folder with a samlIserverCredentialsProvider bean ID and keep the existing bean:

    Copy
    <bean id="samlIserverCredentialsProvider" class="com.microstrategy.custom.auth.MySAMLUserDetailsService">