使用 spring-SAML 在 Pentaho 中进行多租户 SSO 登录后重定向

Redirect after multi-tenant SSO login in Pentaho with spring-SAML

我想使用 SAML 插件 Link(扩展 Spring SAML)为 Pentaho 配置多租户 SSO 登录。

现在我已经在 blueprint.xml 中声明了多个服务提供商 (SP) 和身份提供商 (IDP)(每个租户一个,外加一个公共 SP)。然而,在登录流程结束时,我没有被重定向到主页,而是被重定向到一个通用错误页面。


这是 SAML 插件中 blueprint.xml 设置的示例:

  <bean id="spResourceFactoryCommon" class="org.pentaho.platform.spring.security.saml.resources.MetadataResourceFactory">
    <argument>
      <map key-type="java.lang.String" value-type="java.lang.String">
        <entry key="org.opensaml.util.resource.FilesystemResource" value="${saml.sp.metadata.filesystem.common}" />
      </map>
    </argument>
    <argument value="${saml.sp.metadata.classpath.fallback}" />
  </bean>

  <bean id="spResourceFactoryTenant1" class="org.pentaho.platform.spring.security.saml.resources.MetadataResourceFactory">
    <argument>
      <map key-type="java.lang.String" value-type="java.lang.String">
        <entry key="org.opensaml.util.resource.FilesystemResource" value="${saml.sp.metadata.filesystem.tenant1}" />
      </map>
    </argument>
    <argument value="${saml.sp.metadata.classpath.fallback}" />
  </bean>

  <bean id="spResourceFactoryTenant2" class="org.pentaho.platform.spring.security.saml.resources.MetadataResourceFactory">
    <argument>
      <map key-type="java.lang.String" value-type="java.lang.String">
        <entry key="org.opensaml.util.resource.FilesystemResource" value="${saml.sp.metadata.filesystem.tenant2}" />
      </map>
    </argument>
    <argument value="${saml.sp.metadata.classpath.fallback}" />
  </bean>

  <bean id="idpResourceFactoryTenant1" class="org.pentaho.platform.spring.security.saml.resources.MetadataResourceFactory">
    <argument>
      <map key-type="java.lang.String" value-type="java.lang.String">
        <entry key="org.opensaml.util.resource.FilesystemResource" value="${saml.idp.metadata.filesystem.tenant1}" />
      </map>
    </argument>
    <argument value="${saml.idp.metadata.classpath.fallback}" />
  </bean>

  <bean id="idpResourceFactoryTenant2" class="org.pentaho.platform.spring.security.saml.resources.MetadataResourceFactory">
    <argument>
      <map key-type="java.lang.String" value-type="java.lang.String">
        <entry key="org.opensaml.util.resource.FilesystemResource" value="${saml.idp.metadata.filesystem.tenant2}" />
      </map>
    </argument>
    <argument value="${saml.idp.metadata.classpath.fallback}" />
  </bean>

  <!-- MetadataManager configuration - paths to metadata of IDPs and SP's  -->
  <bean id="metadata" class="org.springframework.security.saml.metadata.CachingMetadataManager" depends-on="pentahoSamlBootstrap">
    <argument>
      <list>
        <!-- sp metadata with extended metadata -->
        <bean class="org.springframework.security.saml.metadata.ExtendedMetadataDelegate">
          <argument>
            <bean class="org.opensaml.saml2.metadata.provider.ResourceBackedMetadataProvider">
              <argument>
                <bean class="java.util.Timer"/>
              </argument>
              <argument>
                <bean factory-ref="spResourceFactoryCommon" factory-method="factoryResource" />
              </argument>
              <property name="parserPool" ref="parserPool"/>
            </bean>
          </argument>
          <argument>
            <bean class="org.springframework.security.saml.metadata.ExtendedMetadata">
              <property name="idpDiscoveryEnabled" value="${saml.discovery.idp.enabled}"/>
              <property name="requireLogoutRequestSigned" value="${ensure.incoming.logout.request.signed}"/>
              <property name="alias" value="pentahoCommon"/>
              <property name="local" value="true"/>
            </bean>
          </argument>
        </bean>
        <bean class="org.springframework.security.saml.metadata.ExtendedMetadataDelegate">
          <argument>
            <bean class="org.opensaml.saml2.metadata.provider.ResourceBackedMetadataProvider">
              <argument>
                <bean class="java.util.Timer"/>
              </argument>
              <argument>
                <bean factory-ref="spResourceFactoryTenant1" factory-method="factoryResource" />
              </argument>
              <property name="parserPool" ref="parserPool"/>
            </bean>
          </argument>
          <argument>
            <bean class="org.springframework.security.saml.metadata.ExtendedMetadata">
              <property name="idpDiscoveryEnabled" value="${saml.discovery.idp.enabled}"/>
              <property name="requireLogoutRequestSigned" value="${ensure.incoming.logout.request.signed}"/>
              <property name="alias" value="tenant1sp"/>
              <property name="local" value="true"/>
            </bean>
          </argument>
        </bean>
        <bean class="org.springframework.security.saml.metadata.ExtendedMetadataDelegate">
          <argument>
            <bean class="org.opensaml.saml2.metadata.provider.ResourceBackedMetadataProvider">
              <argument>
                <bean class="java.util.Timer"/>
              </argument>
              <argument>
                <bean factory-ref="spResourceFactoryTenant2" factory-method="factoryResource" />
              </argument>
              <property name="parserPool" ref="parserPool"/>
            </bean>
          </argument>
          <argument>
            <bean class="org.springframework.security.saml.metadata.ExtendedMetadata">
              <property name="idpDiscoveryEnabled" value="${saml.discovery.idp.enabled}"/>
              <property name="requireLogoutRequestSigned" value="${ensure.incoming.logout.request.signed}"/>
              <property name="alias" value="tenant2sp"/>
              <property name="local" value="true"/>
            </bean>
          </argument>
        </bean>

        <!-- idp metadata -->
        <bean class="org.springframework.security.saml.metadata.ExtendedMetadataDelegate">
          <argument>
            <bean class="org.opensaml.saml2.metadata.provider.ResourceBackedMetadataProvider">
              <argument>
                <bean class="java.util.Timer"/>
              </argument>
              <argument>
                <bean factory-ref="idpResourceFactoryTenant1" factory-method="factoryResource" />
              </argument>
              <property name="parserPool" ref="parserPool"/>
            </bean>
          </argument>
          <argument>
            <bean class="org.springframework.security.saml.metadata.ExtendedMetadata">
              <property name="idpDiscoveryEnabled" value="${saml.discovery.idp.enabled}"/>
              <property name="requireLogoutRequestSigned" value="${ensure.outgoing.logout.request.signed}"/>
              <property name="requireLogoutResponseSigned" value="${ensure.outgoing.logout.response.signed}"/>
              <property name="alias" value="tenant1idp"/>
              <property name="local" value="true"/>
            </bean>
          </argument>
        </bean>
        <bean class="org.springframework.security.saml.metadata.ExtendedMetadataDelegate">
          <argument>
            <bean class="org.opensaml.saml2.metadata.provider.ResourceBackedMetadataProvider">
              <argument>
                <bean class="java.util.Timer"/>
              </argument>
              <argument>
                <bean factory-ref="idpResourceFactoryTenant2" factory-method="factoryResource" />
              </argument>
              <property name="parserPool" ref="parserPool"/>
            </bean>
          </argument>
          <argument>
            <bean class="org.springframework.security.saml.metadata.ExtendedMetadata">
              <property name="idpDiscoveryEnabled" value="${saml.discovery.idp.enabled}"/>
              <property name="requireLogoutRequestSigned" value="${ensure.outgoing.logout.request.signed}"/>
              <property name="requireLogoutResponseSigned" value="${ensure.outgoing.logout.response.signed}"/>
              <property name="alias" value="tenant2idp"/>
              <property name="local" value="true"/>
            </bean>
          </argument>
        </bean>

      </list>
    </argument>
    <property name="keyManager" ref="keyManager" />
    <property name="defaultIDP" value="${saml.idp.url}" />
  </bean>

有了这个配置,当我去 url https://my.application.com/pentaho/alias/tenant1sp/sp?idp=tenant.1.name 我被重定向到 IDP 为 tenant1 公开的登录页面。登录后,我被重定向到之前的 url 并收到一般错误:see the screenshot

Sorry, something went wrong. Please try again or contact your system administrator.

如果我去 URL https://my.application.com/pentaho/Home I am logged to the Pentaho dashboard. This makes me think that the login process has been successful but something went wrong with the redirect at the end of the flow. Indeed I would expect to be redirected to the URL https://my.application.com/pentaho/Home我可以配置这个重定向 somewhere/somehow 吗?

一种解决方法似乎可以解决这个问题,但是我发现在使用 Pentaho 的公开 API 时它会与 SAML 身份验证产生冲突。要使用那些 API,此解决方案并不好。


解决方法


更改 defaultTargetUrlalwaysUseDefaultTargetUrl 属性 successRedirectHandler bean (在 Pentaho-SAML 插件的 blueprint.xml 文件中声明)

  <!-- Handler deciding where to redirect user after successful login -->
  <bean id="successRedirectHandler" class="org.pentaho.platform.spring.security.saml.PentahoSamlAuthenticationSuccessHandler" 
                        init-method="afterPropertiesSet">
    <property name="defaultTargetUrl" value="https://my.application.com/pentaho/Home"/>
    <property name="alwaysUseDefaultTargetUrl" value="true"/>
    <property name="requireProxyWrapping" value="false"/>
  </bean>

编辑:解决 API 登录问题 我用自定义的 org.pentaho.platform.spring.security.saml.PentahoSamlAuthenticationSuccessHandler class 扩展了 onAuthenticationSuccess 方法

private String contextPath;

@Override
public void onAuthenticationSuccess(HttpServletRequest request,
                                    HttpServletResponse response,
                                    Authentication authentication) throws ServletException, IOException {

    SavedRequest savedRequest = requestCache.getRequest(request, response);
    
    //Apply the redirect to the Pentaho console Home if and only if the original targetUrl is not a Pentaho exposed API but the home (contextPath/Home)
    if (!savedRequest.getRedirectUrl().contains("API")
            && savedRequest.getRedirectUrl().contains(contextPath+"/Home")) {
        
        //The Pentaho console Home is set as defaultTargetUrl in the blueprint.xml
        this.setAlwaysUseDefaultTargetUrl(true);
        log.info("The request is not on a Pentaho API. Forcing the target URL to redirect to the defaultTargetUrl");
    }
    super.onAuthenticationSuccess(request, response, authentication);
    
    //retore the original value of alwaysUseDefaultTargetUrl
    this.setAlwaysUseDefaultTargetUrl(false);
}