En esta página encontrará una descripción general del Building Block del lenguaje de marcado de aserción de seguridad (SAML) 2.0., además de los problemas comunes del inicio de sesión único (SSO) y las técnicas para solucionar los problemas del proveedor de la autenticación SAML.

Si por algún motivo se carga un archivo XML de metadatos del IdP nuevo o actualizado en la sección Ajustes del proveedor de identidades de la página Ajustes de la autenticación SAML en la GUI de Blackboard Learn para un proveedor de la autenticación SAML, el proveedor de la autenticación SAML B2 ese proveedor se deben alternar entre Inactivo/Disponible para asegurarse de que cualquier metadato del IdP almacenado en la caché se elimine y se utilicen los metadatos del IdP actualizados.


Términos clave

Son siguientes términos y abreviaturas se utilizan en toda esta guía:

  • SAML: Lenguaje de marcado de aserción de seguridad
  • IdP: Proveedor de identidad
  • SP: Proveedor de servicios
  • ADFS: Active Directory Federation Services
  • GUI: Interfaz gráfica del usuario. En el contexto de Blackboard Learn, se refiere a trabajar con el software.

Editar la configuración de SAML

Para ayudarlo a solucionar los problemas de la autenticación SAML, en la versión 3200.2.0 se actualizó el Building Block SAML, que ahora incluye estos ajustes y opciones de configuración:

  • Definir la duración límite de la sesión SAML
  • Elegir un tipo de algoritmo de firma
  • Regenerar los certificados
  • Cambiar el valor de ResponseSkew

Más información sobre cómo configurar los ajustes del Building Block SAML


Errores y excepciones

Los errores o excepciones relacionados con SAML se incluyen en estos registros:

  • /usr/local/blackboard/logs/bb-services-log.txt
  • /usr/local/blackboard/logs/tomcat/stdout-stderr-<date>.log
  • /usr/local/blackboard/logs/tomcat/catalina-log.txt

Cuando se investiga un problema de autenticación SAML informado siempre se deben consultar estos registros.


SAML Tracer

Debido a las iteraciones de la solución de problemas para la autenticación SAML 2.0, es posible que en algún momento deba confirmar o ver los atributos que se publican desde el IdP y se envían a Learn durante el proceso de autenticación. Si los atributos del IdP NO están cifrados en la respuesta de SAML, puede utilizar el complemento SAML Tracer del navegador Firefox o SAML Message Decoder de Chrome para verlos.


Atributo no asignado correctamente

Si el atributo que contiene el userName no está asignado correctamente como se indica en el campo ID del usuario remoto de la sección Asignar los atributos de SAML en la página Ajustes de la autenticación SAML de la GUI de Blackboard Learn, el siguiente evento iniciará sesión en el registro bb-services cuando intente acceder a Blackboard Learn mediante la autenticación SAML:

2016-06-28 12:48:12 -0400 - userName is null or empty

Un mensaje similar a Error de inicio de sesión (Sign On Error!) aparecerá en el navegador: Actualmente Blackboard Learn no puede iniciar sesión en su cuenta con el inicio de sesión único. Póngase en contacto con el administrador para obtener ayuda.

Se mostrará una entrada de Error de autenticación en el registro bb-services:

2016-06-28 12:48:12 -0400 - BbSAMLExceptionHandleFilter - javax.servlet.ServletException: Authentication Failure
    at blackboard.auth.provider.saml.customization.handler.BbAuthenticationSuccessHandler.checkAuthenticationResult(BbAuthenticationSuccessHandler.java:81)
    at blackboard.auth.provider.saml.customization.handler.BbAuthenticationSuccessHandler.onAuthenticationSuccess(BbAuthenticationSuccessHandler.java:57)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.successfulAuthentication(AbstractAuthenticationProcessingFilter.java:331)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:245)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:184)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:64) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:53) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:91)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:176)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
[SNIP]

Solución

Hay dos opciones para solucionar el problema. Primero, seleccione la opción Crear cuentas si estas no existen en el sistema en la página Ajustes de la autenticación SAML en la GUI de Blackboard Learn. O bien, puede intentar ver el valor de los atributos publicados por el IdP mediante SAML Tracer o el registro de depuración si estos atributos NO están cifrados:

<saml2:Attribute Name="urn:oid:0.9.2342.19200300.100.1.3">
    <saml2:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema"
                          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                          xsi:type="xs:anyType"
                          >bbuser_saml2@bbchjones.net</saml2:AttributeValue>
</saml2:Attribute>

y asignar el Attribute Name que contiene el AttributeValue deseado a la ID del usuario remoto en la página Ajustes de la autenticación SAML en la GUI de Blackboard Learn.


Origen de datos compatible no seleccionado

Los usuarios no podrán iniciar sesión en Blackboard Learn con la autenticación SAML si el Origen de datos para los usuarios no está seleccionado en la sección Ajustes del proveedor de servicios > Orígenes de datos compatibles en la página Ajustes de la autenticación SAML en la GUI de Blackboard Learn. El siguiente evento iniciará sesión en el registro bb-services cuando intente acceder a Blackboard Learn mediante la autenticación SAML:

2016-09-23 12:33:13 -0500 - userName is null or empty

Aparecerá el mensaje Error de inicio de sesión en el navegador, al igual que el Error de autenticación en el registro bb-services:

2016-09-23 12:33:13 -0500 - BbSAMLExceptionHandleFilter - javax.servlet.ServletException: Authentication Failure
    at blackboard.auth.provider.saml.customization.handler.BbAuthenticationSuccessHandler.checkAuthenticationResult(BbAuthenticationSuccessHandler.java:82)
    at blackboard.auth.provider.saml.customization.handler.BbAuthenticationSuccessHandler.onAuthenticationSuccess(BbAuthenticationSuccessHandler.java:58)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.successfulAuthentication(AbstractAuthenticationProcessingFilter.java:331)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:245)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:184)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:64)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:53)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter (SecurityContextPersistenceFilter.java:91)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:176)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
    at sun.reflect.GeneratedMethodAccessor3399.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
        [SNIP]

Solución

  1. Obtenga el nombre de usuario del usuario que no puede iniciar la sesión.
  2. En la GUI de Blackboard Learn, vaya a Admin. del sistema > Usuarios y busque el usuario.
  3. Copie la Clave del origen de datos del usuario.
  4. Vaya a Admin. del sistema > Autenticación > "nombre del proveedor" > Ajustes de SAML > Orígenes de datos compatibles.
  5. Coloque una marca junto a Origen de datos en la columna Nombre y haga clic en Enviar.

Mensaje de error "La URL proporcionada tiene un formato incorrecto"

Si OneLogin está configurado como el IdP para el proveedor de la autenticación SAML en Blackboard Learn, se mostrará en la página el error La URL proporcionada tiene un formato incorrecto después de introducir las credenciales OneLogin cuando intente iniciar sesión en Blackboard Learn.

Se mostrará lo siguiente en el registro bb-services:

2016-09-16 09:43:40 -0400 - Given URL is not well formed<P><span class="captionText">For reference, the Error ID is 17500f44-7809-4b9f-a272-3bed1d1af131.</span&gt; - java.lang.IllegalArgumentException: Given URL is not well formed
    at org.opensaml.util.URLBuilder.<init>(URLBuilder.java:120)
    at org.opensaml.util.SimpleURLCanonicalizer.canonicalize(SimpleURLCanonicalizer.java:87)
    at org.opensaml.common.binding.decoding.BasicURLComparator.compare(BasicURLComparator.java:57)
    at org.opensaml.common.binding.decoding.BaseSAMLMessageDecoder.compareEndpointURIs(BaseSAMLMessageDecoder.java:173)
    at org.opensaml.common.binding.decoding.BaseSAMLMessageDecoder.checkEndpointURI(BaseSAMLMessageDecoder.java:213)
    at org.opensaml.saml2.binding.decoding.BaseSAML2MessageDecoder.decode(BaseSAML2MessageDecoder.java:72)
    at org.springframework.security.saml.processor.SAMLProcessorImpl.retrieveMessage(SAMLProcessorImpl.java:105)
    at org.springframework.security.saml.processor.SAMLProcessorImpl.retrieveMessage(SAMLProcessorImpl.java:172)
    at org.springframework.security.saml.SAMLProcessingFilter.attemptAuthentication(SAMLProcessingFilter.java:80)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:217)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:184)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:64)
    [SNIP]
    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:745)
Caused by: java.net.MalformedURLException: no protocol: {recipient}
    at java.net.URL.<init>(URL.java:593)
    at java.net.URL.<init>(URL.java:490)
    at java.net.URL.<init>(URL.java:439)
    at org.opensaml.util.URLBuilder.<init>(URLBuilder.java:77)
        ... 203 more

Solución

  1. Active el complemento SAML Tracer del navegador Firefox y replique el problema del inicio de sesión.
  2. Revise el comienzo del evento SAML POST:

    <samlp:Response Destination="{recipient}"
            ID="R8afbfbfee7292613f98ad4ec4115de7c6b385be6"
            InResponseTo="a3g2424154bb0gjh3737ii66dadbff4"
            IssueInstant="2016-09-16T18:49:09Z"
            Version="2.0"
            xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
            xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
            >
        <saml:Issuer>https://app.onelogin.com/saml/metadata/123456</saml:Issuer>
        [SNIP]

  3. En la primera línea de la respuesta, observe que el campo Destination= está establecido solo para el destinatario.
  4. Haga que el cliente vaya a la sección Configuración de su OneLogin IdP.
  5. Confirme que el campo Destinatario esté en blanco.
  6. Copie el valor de la URL de ACS (Consumidor), péguelo en el campo Destinatario y haga clic en Guardar.

Posibles problemas del IdP/SP

  1. Si se produce un error antes que usted sea redirigido a la página de inicio de sesión del IdP, es posible que los metadatos del IdP no sean válidos.
  2. Si en cambio el error se produce después de que usted inicia sesión en la página del IdP, estos podrían ser los motivos:
    1. La asignación de los atributos entre el SP y el IdP no es correcta o el IdP no devolvió una ID del usuario remoto válida.
    2. El SP no validó la respuesta de SAML del IdP. Esto podría deberse a lo siguiente:
      • El IdP firma la respuesta de SAML con un certificado que no fue emitido por una autoridad válida, y este certificado no está incluido en el almacén de claves del SP.
      • El reloj del sistema del SP no es correcto.

Active Directory Federation Services (ADFS)

Los nombres de los atributos en la sección Asignar los atributos de SAML en la página Ajustes de la autenticación SAML en la GUI de Blackboard Learn distinguen mayúsculas de minúsculas. Por lo tanto, si la ID del usuario remoto contiene el elemento sAMAccountName para el Nombre del atributo en la página de ajustes, y el SAML POST real del IdP contiene lo siguiente para el Nombre del atributo en el elemento AttributeStatement:

<AttributeStatement>
    <Attribute Name="SamAccountName>
        <AttributeValue>Test-User</AttributeValue>
    </Attribute>
</AttributeStatement>

El usuario no podrá iniciar sesión. El valor del nombre del atributo ID del usuario remoto en la página Ajustes de la autenticación SAML se deberá cambiar de sAMAccountName a SamAccountName.

Advertencia de "No se encontró el recurso" o "Error de inicio de sesión"

En esta sección, encontrará algunos de los problemas comunes por los que un usuario no puede iniciar sesión en Learn mediante la autenticación SAML con ADFS cuando aparece el mensaje No se encontró el recurso especificado o no tiene permiso para acceder a él o Error de inicio de sesión en la GUI de Blackboard Learn.

Problema n.º 1

Después de introducir las credenciales en la página de inicio de sesión de ADFS, se produce un error una vez que el usuario es redirigido a la GUI de Blackboard Learn: No se encontró el recurso especificado o no tiene permiso para acceder a él.

El error aparece junto a un mensaje correspondiente en el registro stdout-stderr:

INFO | jvm 1 | 2016/06/22 06:08:33 | - No mapping found for HTTP request with URI [/auth-saml/saml/SSO] in DispatcherServlet with name 'saml'

El problema se produce porque se utiliza el método noHandlerFound() en el código DispatcherServlet.java y no es posible ubicar o asignar la solicitud HTTP SSO.

/**
 * No handler found -&gt; set appropriate HTTP response status.
 * @param request current HTTP request
 * @param response current HTTP response
 * @throws Exception if preparing the response failed
 */
protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
 if (pageNotFoundLogger.isWarnEnabled()) {
  pageNotFoundLogger.warn("No mapping found for HTTP request with URI [" + getRequestUri(request) +
    "] in DispatcherServlet with name '" + getServletName() + "'");
 }
 if (this.throwExceptionIfNoHandlerFound) {
  throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request),
    new ServletServerHttpRequest(request).getHeaders());
 }
 else {
  response.sendError(HttpServletResponse.SC_NOT_FOUND);
 }
}

Solución

Comúnmente esto se produce porque la ID de la entidad para el SP configurado en la GUI de Blackboard Learn no es correcta. Para resolver este problema, vaya a Admin. del sistema > Autenticación > Ajustes de la autenticación SAML > Ajustes del proveedor de servicios y actualice la ID de la entidad. En el caso de ADFS, la configuración predeterminada para la ID de la entidad sería https://[Learn Server Hostname]/auth-saml/saml/SSO.

Si una escuela cambia su URL de la dirección predeterminada https://school.blackboard.com a https://their.school.edu, se debe actualizar la ID de la entidad a https://their.school.edu/auth-saml/saml/SSO en la GUI de Blackboard Learn, en la página Ajustes de la autenticación SAML.

Problema n.º 2

Después de introducir las credenciales en la página de inicio de sesión de ADFS, se produce un error una vez que el usuario es redirigido a la GUI de Blackboard Learn: No se encontró el recurso especificado o no tiene permiso para acceder a él.

El error aparece junto a un mensaje correspondiente en el registro stdout-stderr:

INFO  | jvm 1  | 2016/06/22 06:08:33 | - No mapping found for HTTP request with URI [/auth-saml/saml/SSO] in DispatcherServlet with name 'saml'

Además, se muestra este mensaje en el registro catalina:

ERROR 2016-06-27 10:47:03,664 connector-6: userId=_2_1, sessionId=62536416FB80462298C92064A7022E50 org.opensaml.xml.encryption.Decrypter - Error decrypting the encrypted data element
org.apache.xml.security.encryption.XMLEncryptionException: Illegal key size
Original Exception was java.security.InvalidKeyException: Illegal key size
    at org.apache.xml.security.encryption.XMLCipher.decryptToByteArray(XMLCipher.java:1822)
    at org.opensaml.xml.encryption.Decrypter.decryptDataToDOM(Decrypter.java:596)
    at org.opensaml.xml.encryption.Decrypter.decryptUsingResolvedEncryptedKey(Decrypter.java:795)
    at org.opensaml.xml.encryption.Decrypter.decryptDataToDOM(Decrypter.java:535)
    at org.opensaml.xml.encryption.Decrypter.decryptDataToList(Decrypter.java:453)
    at org.opensaml.xml.encryption.Decrypter.decryptData(Decrypter.java:414)
    at org.opensaml.saml2.encryption.Decrypter.decryptData(Decrypter.java:141)
    at org.opensaml.saml2.encryption.Decrypter.decrypt(Decrypter.java:69)
    at org.springframework.security.saml.websso.WebSSOProfileConsumerImpl.processAuthenticationResponse(WebSSOProfileConsumerImpl.java:199)
    at org.springframework.security.saml.SAMLAuthenticationProvider.authenticate(SAMLAuthenticationProvider.java:82)
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:167)
        [SNIP]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:745)
Caused by: java.security.InvalidKeyException: Illegal key size
    at javax.crypto.Cipher.checkCryptoPerm(Cipher.java:1039)
    at javax.crypto.Cipher.init(Cipher.java:1393)
    at javax.crypto.Cipher.init(Cipher.java:1327)
    at org.apache.xml.security.encryption.XMLCipher.decryptToByteArray(XMLCipher.java:1820)
        ... 205 more

Junto a este mensaje en el registro bb-services:

2016-06-27 10:47:03 -0400 - unsuccessfulAuthentication - org.springframework.security.authentication.AuthenticationServiceException: Error validating SAML message
    at org.springframework.security.saml.SAMLAuthenticationProvider.authenticate(SAMLAuthenticationProvider.java:100)
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:167)
    at org.springframework.security.saml.SAMLProcessingFilter.attemptAuthentication(SAMLProcessingFilter.java:87)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:217)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:184)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:64)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:53)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:91)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:176)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
    at sun.reflect.GeneratedMethodAccessor3422.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.apache.catalina.security.SecurityUtil$1.run(SecurityUtil.java:277)
    at org.apache.catalina.security.SecurityUtil$1.run(SecurityUtil.java:274)
    at java.security.AccessController.doPrivileged(Native Method)
    at javax.security.auth.Subject.doAsPrivileged(Subject.java:549)
    at org.apache.catalina.security.SecurityUtil.execute(SecurityUtil.java:309)
    at org.apache.catalina.security.SecurityUtil.doAsPrivilege(SecurityUtil.java:249)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:237)
    at org.apache.catalina.core.ApplicationFilterChain.access$000(ApplicationFilterChain.java:55)
    at org.apache.catalina.core.ApplicationFilterChain$1.run(ApplicationFilterChain.java:191)
    at org.apache.catalina.core.ApplicationFilterChain$1.run(ApplicationFilterChain.java:187)
    at java.security.AccessController.doPrivileged(Native Method)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:186)
    at blackboard.auth.provider.saml.customization.filter.BbSAMLExceptionHandleFilter.doFilterInternal(BbSAMLExceptionHandleFilter.java:30)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at sun.reflect.GeneratedMethodAccessor3421.invoke(Unknown Source)
        [SNIP]
    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:745)
Caused by: org.opensaml.common.SAMLException: Response doesn't have any valid assertion which would pass subject validation
    at org.springframework.security.saml.websso.WebSSOProfileConsumerImpl.processAuthenticationResponse(WebSSOProfileConsumerImpl.java:229)
    at org.springframework.security.saml.SAMLAuthenticationProvider.authenticate(SAMLAuthenticationProvider.java:87)
        ... 229 more

El problema se debe a que ADFS cifra los atributos que envía con AES-256 de forma predeterminada, y el entorno de ejecución de Java que Blackboard Learn utiliza no es compatible con AES-256 tal como está.

Solución

Una opción universal para solucionar este problema es abrir una instancia de PowerShell en el servidor de ADFS y establecer que el usuario de confianza creado para Blackboard Learn envíe los atributos sin cifrar. Dado que toda la comunicación se realiza a través de SSL, no afectará la seguridad de la autenticación. Además, hace que la depuración de cualquier problema sea más sencilla, ya que los atributos se pueden ver con las herramientas de depuración, tales como el complemento SAML Tracer del navegador Firefox, y no es necesario reiniciar el sistema de Blackboard Learn. Para que el usuario de confianza creado para Blackboard Learn envíe los atributos sin cifrar, abra PowerShell y ejecute el siguiente comando después de reemplazar TargetName por el nombre de la Relación de confianza para usuario autenticado que aparece en la Consola de administración de ADFS, en Relaciones de confianza > Relaciones de confianza para usuarios autenticados.

set-ADFSRelyingPartyTrust –TargetName "yourlearnserver.blackboard.com" –EncryptClaims $False

Después de efectuar este cambio, deberá reiniciar el servicio de ADFS con el siguiente comando: Restart-Service ADFSSRV

Problema n.º 3

Después de introducir las credenciales en la página de inicio de sesión de ADFS, se produce un error una vez que el usuario es redirigido a la GUI de Blackboard Learn: No se encontró el recurso especificado o no tiene permiso para acceder a él o Error de inicio de sesión.

En ambos casos, aparecen estos eventos relacionados de SAML similares en el registro stdout-stderr:

INFO   | jvm 1    | 2016/09/06 20:33:04 | - /saml/login?apId=_107_1&redirectUrl=https%3A%2F%2Fbb.fraser.misd.net%2Fwebapps%2Fportal%2Fexecute%2FdefaultTab at position 1 of 10 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
INFO   | jvm 1    | 2016/09/06 20:33:04 | - No HttpSession currently exists
INFO   | jvm 1    | 2016/09/06 20:33:04 | - No SecurityContext was available from the HttpSession: null. A new one will be created.
INFO   | jvm 1    | 2016/09/06 20:33:04 | - /saml/login?apId=_107_1&redirectUrl=https%3A%2F%2Fbb.fraser.misd.net%2Fwebapps%2Fportal%2Fexecute%2FdefaultTab at position 2 of 10 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
INFO   | jvm 1    | 2016/09/06 20:33:04 | - /saml/login?apId=_107_1&redirectUrl=https%3A%2F%2Fbb.fraser.misd.net%2Fwebapps%2Fportal%2Fexecute%2FdefaultTab at position 3 of 10 in additional filter chain; firing Filter: 'HeaderWriterFilter'
INFO   | jvm 1    | 2016/09/06 20:33:04 | - /saml/login?apId=_107_1&redirectUrl=https%3A%2F%2Fbb.fraser.misd.net%2Fwebapps%2Fportal%2Fexecute%2FdefaultTab at position 4 of 10 in additional filter chain; firing Filter: 'FilterChainProxy'
INFO   | jvm 1    | 2016/09/06 20:33:04 | - Checking match of request : '/saml/login'; against '/saml/login/**'
INFO   | jvm 1    | 2016/09/06 20:33:04 | - /saml/login?apId=_107_1&redirectUrl=https%3A%2F%2Fbb.fraser.misd.net%2Fwebapps%2Fportal%2Fexecute%2FdefaultTab at position 1 of 1 in additional filter chain; firing Filter: 'SAMLEntryPoint'
INFO   | jvm 1    | 2016/09/06 20:33:04 | - Request for URI http://www.w3.org/2000/09/xmldsig#rsa-sha1
INFO   | jvm 1    | 2016/09/06 20:33:04 | - Request for URI http://www.w3.org/2000/09/xmldsig#rsa-sha1
INFO   | jvm 1    | 2016/09/06 20:33:04 | - SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
INFO   | jvm 1    | 2016/09/06 20:33:04 | - SecurityContextHolder now cleared, as request processing completed
INFO   | jvm 1    | 2016/09/06 20:33:07 | - /saml/SSO at position 1 of 10 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
INFO   | jvm 1    | 2016/09/06 20:33:07 | - HttpSession returned null object for SPRING_SECURITY_CONTEXT
INFO   | jvm 1    | 2016/09/06 20:33:07 | - No SecurityContext was available from the HttpSession: [email protected] A new one will be created.
INFO   | jvm 1    | 2016/09/06 20:33:07 | - /saml/SSO at position 2 of 10 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
INFO   | jvm 1    | 2016/09/06 20:33:07 | - /saml/SSO at position 3 of 10 in additional filter chain; firing Filter: 'HeaderWriterFilter'
INFO   | jvm 1    | 2016/09/06 20:33:07 | - /saml/SSO at position 4 of 10 in additional filter chain; firing Filter: 'FilterChainProxy'
INFO   | jvm 1    | 2016/09/06 20:33:07 | - Checking match of request : '/saml/sso'; against '/saml/login/**'
INFO   | jvm 1    | 2016/09/06 20:33:07 | - Checking match of request : '/saml/sso'; against '/saml/logout/**'
INFO   | jvm 1    | 2016/09/06 20:33:07 | - Checking match of request : '/saml/sso'; against '/saml/bbsamllogout/**'
INFO   | jvm 1    | 2016/09/06 20:33:07 | - Checking match of request : '/saml/sso'; against '/saml/sso/**'
INFO   | jvm 1    | 2016/09/06 20:33:07 | - /saml/SSO at position 1 of 1 in additional filter chain; firing Filter: 'SAMLProcessingFilter'
INFO   | jvm 1    | 2016/09/06 20:33:07 | - Authentication attempt using org.springframework.security.saml.SAMLAuthenticationProvider
INFO   | jvm 1    | 2016/09/06 20:33:07 | - Forwarding to /
INFO   | jvm 1    | 2016/09/06 20:33:07 | - DispatcherServlet with name 'saml' processing POST request for [/auth-saml/saml/SSO]
INFO   | jvm 1    | 2016/09/06 20:33:07 | - No mapping found for HTTP request with URI [/auth-saml/saml/SSO] in DispatcherServlet with name 'saml'
INFO   | jvm 1    | 2016/09/06 20:33:07 | - SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
INFO   | jvm 1    | 2016/09/06 20:33:07 | - Successfully completed request
INFO   | jvm 1    | 2016/09/06 20:33:07 | - Skip invoking on
INFO   | jvm 1    | 2016/09/06 20:33:07 | - SecurityContextHolder now cleared, as request processing completed

O bien, estas excepciones de SAML similares en el registro bb-services:

2016-11-29 09:04:24 -0500 - unsuccessfulAuthentication - org.springframework.security.authentication.AuthenticationServiceException: Error validating SAML message
    at org.springframework.security.saml.SAMLAuthenticationProvider.authenticate(SAMLAuthenticationProvider.java:100)
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:167)
    at org.springframework.security.saml.SAMLProcessingFilter.attemptAuthentication(SAMLProcessingFilter.java:87)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:217)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:184)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:64)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:53)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:91)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:176)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
    at sun.reflect.GeneratedMethodAccessor853.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.apache.catalina.security.SecurityUtil$1.run(SecurityUtil.java:282)
    at org.apache.catalina.security.SecurityUtil$1.run(SecurityUtil.java:279)
    at java.security.AccessController.doPrivileged(Native Method)
    at javax.security.auth.Subject.doAsPrivileged(Subject.java:549)
    at org.apache.catalina.security.SecurityUtil.execute(SecurityUtil.java:314)
    at org.apache.catalina.security.SecurityUtil.doAsPrivilege(SecurityUtil.java:253)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:190)
    at org.apache.catalina.core.ApplicationFilterChain.access$000(ApplicationFilterChain.java:46)
    at org.apache.catalina.core.ApplicationFilterChain$1.run(ApplicationFilterChain.java:148)
    at org.apache.catalina.core.ApplicationFilterChain$1.run(ApplicationFilterChain.java:144)
    at java.security.AccessController.doPrivileged(Native Method)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:143)
    at blackboard.auth.provider.saml.customization.filter.BbSAMLExceptionHandleFilter.doFilterInternal(BbSAMLExceptionHandleFilter.java:30)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at sun.reflect.GeneratedMethodAccessor853.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        [SNIP]
    at org.apache.catalina.valves.RemoteIpValve.invoke(RemoteIpValve.java:677)
    at blackboard.tomcat.valves.LoggingRemoteIpValve.invoke(LoggingRemoteIpValve.java:44)
    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:1110)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:785)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1425)
    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:745)
Caused by: org.opensaml.common.SAMLException: Response issue time is either too old or with date in the future, skew 60, time 2016-11-29T14:03:16.634Z
    at org.springframework.security.saml.websso.WebSSOProfileConsumerImpl.processAuthenticationResponse(WebSSOProfileConsumerImpl.java:126)
    at blackboard.auth.provider.saml.customization.consumer.BbSAMLWebSSOProfileConsumerImpl.processAuthenticationResponse(BbSAMLWebSSOProfileConsumerImpl.java:40)
    at org.springframework.security.saml.SAMLAuthenticationProvider.authenticate(SAMLAuthenticationProvider.java:87)
        ... 230 more

El problema se produce cuando el servidor de ADFS y el servidor de la aplicación Blackboard Learn tienen un desfase de tiempo de alrededor de 60 segundos o más.

Solución

Hay dos opciones para solucionar el problema:

  1. Sincronizar de forma manual los relojes de los servidores de la aplicación Blackboard Learn y el servidor de ADFS. En el caso de Blackboard Learn, la hora actual y la zona horaria del servidor puede verse en un navegador web. Para ello, agregue /webapps/portal/healthCheck al final de la URL de Blackboard Learn.

    Ejemplo: https://mhtest1.blackboard.com//webapps/portal/healthCheck

    Hostname: ip-10-145-49-11.ec2.internal
    Status: Active - Database connectivity established
    Running since: Sat, Dec 3, 2016 - 05:39:11 PM EST
    Time of request: Thu, Dec 8, 2016 - 05:12:43 PM EST

    La institución puede usar la URL anterior para comparar la zona horaria y el reloj del sistema de Blackboard Learn con los del servidor de ADFS y ajustarlos, de ser necesario, en el servidor de ADFS para sincronizarlos con el sitio de Blackboard Learn.

  2. Para solucionar el problema, ajuste la distorsión en el archivo securityContext.xml:
    1. Haga una copia de respaldo del archivo:

      /usr/local/blackboard/content/vi/BBLEARN/plugins/bb-auth-provider-saml/webapp/WEB-INF/config/saml/securityContext.xml

    2. Cambie:

      <!-- SAML 2.0 WebSSO Assertion Consumer -->
      <bean id="webSSOprofileConsumer" class="org.springframework.security.saml.websso.WebSSOProfileConsumerImpl"/>

      a:

      <!-- SAML 2.0 WebSSO Assertion Consumer -->
      <bean id="webSSOprofileConsumer" class="org.springframework.security.saml.websso.WebSSOProfileConsumerImpl">
      <property name="responseSkew"value="60000"/><!-- 100 hours -->
      </bean>

Problema n.º 4

Después de introducir las credenciales en la página de inicio de sesión de ADFS, se produce un error una vez que el usuario es redirigido a la GUI de Blackboard Learn: No se encontró el recurso especificado o no tiene permiso para acceder a él o Error de inicio de sesión.

Se mostrarán las excepciones siguientes en el registro bb-services:

2016-11-01 12:47:19 -0500 - unsuccessfulAuthentication - org.springframework.security.authentication.AuthenticationServiceException: Error validating SAML message
    at org.springframework.security.saml.SAMLAuthenticationProvider.authenticate(SAMLAuthenticationProvider.java:100)
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:167)
    at org.springframework.security.saml.SAMLProcessingFilter.attemptAuthentication(SAMLProcessingFilter.java:87)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:217)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:184)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:64)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:53)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:91)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:176)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
    at sun.reflect.GeneratedMethodAccessor929.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.apache.catalina.security.SecurityUtil$1.run(SecurityUtil.java:282)
    at org.apache.catalina.security.SecurityUtil$1.run(SecurityUtil.java:279)
    at java.security.AccessController.doPrivileged(Native Method)
    at javax.security.auth.Subject.doAsPrivileged(Subject.java:549)
    at org.apache.catalina.security.SecurityUtil.execute(SecurityUtil.java:314)
    at org.apache.catalina.security.SecurityUtil.doAsPrivilege(SecurityUtil.java:253)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:190)
    at org.apache.catalina.core.ApplicationFilterChain.access$000(ApplicationFilterChain.java:46)
    at org.apache.catalina.core.ApplicationFilterChain$1.run(ApplicationFilterChain.java:148)
    at org.apache.catalina.core.ApplicationFilterChain$1.run(ApplicationFilterChain.java:144)
    at java.security.AccessController.doPrivileged(Native Method)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:143)
    at blackboard.auth.provider.saml.customization.filter.BbSAMLExceptionHandleFilter.doFilterInternal(BbSAMLExceptionHandleFilter.java:30)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
 [SNIP]
    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:745)
Caused by: org.opensaml.common.SAMLException: Response has invalid status code urn:oasis:names:tc:SAML:2.0:status:Responder, status message is null
    at org.springframework.security.saml.websso.WebSSOProfileConsumerImpl.processAuthenticationResponse(WebSSOProfileConsumerImpl.java:113)
    at blackboard.auth.provider.saml.customization.consumer.BbSAMLWebSSOProfileConsumerImpl.processAuthenticationResponse(BbSAMLWebSSOProfileConsumerImpl.java:40)
    at org.springframework.security.saml.SAMLAuthenticationProvider.authenticate(SAMLAuthenticationProvider.java:87)
        ... 230 more
 2016-11-01 12:47:19 -0500 - BbSAMLExceptionHandleFilter - javax.servlet.ServletException: Unsuccessful Authentication
         at blackboard.auth.provider.saml.customization.filter.BbSAMLProcessingFilter.unsuccessfulAuthentication(BbSAMLProcessingFilter.java:31)
         at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:235)
         at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
         at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
         at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:184)
         at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
         at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:64)
         at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
         at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
         at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:53)
         at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
         at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
         at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:91)
         at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
         at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
         at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:176)
         at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
         at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
         at sun.reflect.GeneratedMethodAccessor929.invoke(Unknown Source)
         at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
         at java.lang.reflect.Method.invoke(Method.java:498)
         at org.apache.catalina.security.SecurityUtil$1.run(SecurityUtil.java:282)
         at org.apache.catalina.security.SecurityUtil$1.run(SecurityUtil.java:279)
         at java.security.AccessController.doPrivileged(Native Method)
         at javax.security.auth.Subject.doAsPrivileged(Subject.java:549)
         at org.apache.catalina.security.SecurityUtil.execute(SecurityUtil.java:314)
         at org.apache.catalina.security.SecurityUtil.doAsPrivilege(SecurityUtil.java:253)
         at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:190)
         at org.apache.catalina.core.ApplicationFilterChain.access$000(ApplicationFilterChain.java:46)
         at org.apache.catalina.core.ApplicationFilterChain$1.run(ApplicationFilterChain.java:148)
         at org.apache.catalina.core.ApplicationFilterChain$1.run(ApplicationFilterChain.java:144)
         at java.security.AccessController.doPrivileged(Native Method)
         at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:143)
         at blackboard.auth.provider.saml.customization.filter.BbSAMLExceptionHandleFilter.doFilterInternal(BbSAMLExceptionHandleFilter.java:30)
 [SNIP]

Solución
  1. Vaya al Panel del administrador.
  2. En Building Blocks, seleccione Building Blocks.
  3. Seleccione Herramientas instaladas.
  4. Busque la opción Proveedor de la autenticación: SAML en la lista. Abra el menú y seleccione Ajustes.
  5. En Ajustes del algoritmo de firmas, elija SHA-256 de la lista. Después de seleccionar la opción Tipo de algoritmo de firma, reinicie el Building Block SAML para que se apliquen los nuevos ajustes.
  6. Seleccione Enviar para guardar los cambios.

Problema n.º 5

Después de introducir las credenciales en la página de inicio de sesión de ADFS, se produce un error una vez que el usuario es redirigido a la GUI de Blackboard Learn: No se encontró el recurso especificado o no tiene permiso para acceder a él o Error de inicio de sesión.

Se mostrarán las excepciones siguientes en el registro bb-services:

2017-01-04 22:52:58 -0700 - unsuccessfulAuthentication - org.springframework.security.authentication.AuthenticationServiceException: Error validating SAML message
    at org.springframework.security.saml.SAMLAuthenticationProvider.authenticate(SAMLAuthenticationProvider.java:100)
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:167)
    at org.springframework.security.saml.SAMLProcessingFilter.attemptAuthentication(SAMLProcessingFilter.java:87)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:217)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:184)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:64)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:53)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:91)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:176)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
    at sun.reflect.GeneratedMethodAccessor935.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.apache.catalina.security.SecurityUtil$1.run(SecurityUtil.java:282)
    at org.apache.catalina.security.SecurityUtil$1.run(SecurityUtil.java:279)
    at java.security.AccessController.doPrivileged(Native Method)
    at javax.security.auth.Subject.doAsPrivileged(Subject.java:549)
    at org.apache.catalina.security.SecurityUtil.execute(SecurityUtil.java:314)
    at org.apache.catalina.security.SecurityUtil.doAsPrivilege(SecurityUtil.java:253)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:190)
    at org.apache.catalina.core.ApplicationFilterChain.access$000(ApplicationFilterChain.java:46)
    at org.apache.catalina.core.ApplicationFilterChain$1.run(ApplicationFilterChain.java:148)
    at org.apache.catalina.core.ApplicationFilterChain$1.run(ApplicationFilterChain.java:144)
    at java.security.AccessController.doPrivileged(Native Method)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:143)
    at blackboard.auth.provider.saml.customization.filter.BbSAMLExceptionHandleFilter.doFilterInternal(BbSAMLExceptionHandleFilter.java:30)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    [SNIP]
    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:745)
Caused by: org.opensaml.common.SAMLException: NameID element must be present as part of the Subject in the Response message, please enable it in the IDP configuration
    at org.springframework.security.saml.websso.WebSSOProfileConsumerImpl.processAuthenticationResponse(WebSSOProfileConsumerImpl.java:252)
    at blackboard.auth.provider.saml.customization.consumer.BbSAMLWebSSOProfileConsumerImpl.processAuthenticationResponse(BbSAMLWebSSOProfileConsumerImpl.java:40)
    at org.springframework.security.saml.SAMLAuthenticationProvider.authenticate(SAMLAuthenticationProvider.java:87)
        ... 214 more

Tal como se indicó en la excepción de SAML anterior, falta el elemento NameID en el Asunto del mensaje de la Respuesta. El problema se produce comúnmente cuando el elemento NameID no está establecido como un Tipo de notificación saliente en una Regla de notificaciones para la Relación de confianza para usuario autenticado en el IdP de ADFS de la institución o la Regla de notificaciones para NameID no está en el orden correcto para la Relación de confianza para usuario autenticado en el IdP de ADFS de la institución, que a su vez hace que el elemento NameID falte en el Asunto del mensaje de la respuesta.

Ejemplo: Falta el elemento NameID

<Subject>
    <SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
        <SubjectConfirmationData InResponseTo="a22ai8iig0f75ae22hd28748b12da50"
                                 NotOnOrAfter="2017-01-03T05:57:58.234Z"
                                 Recipient="https://yourschool.blackboard.com/auth-saml/saml/SSO"
                                 />
    </SubjectConfirmation>
</Subject>

Ejemplo: El elemento NameID está presente

<Subject>
    <NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">testadfs</NameID>
    <SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
        <SubjectConfirmationData InResponseTo="a5903d39if463ea87ieiab5135j9ji"
                                 NotOnOrAfter="2017-01-05T04:33:12.715Z"
                                 Recipient="https://yourschool.blackboard.com/auth-saml/saml/SSO"
                                 />
    </SubjectConfirmation>
</Subject>

Puede usar el complemento SAML Tracer de Firefox para ver el Asunto en el mensaje de la Respuesta.

Solución

Hay tres métodos para solucionas este problema.

  1. Confirme que se hayan seguido correctamente los pasos de la Guía de configuración de SAML B2 para ADFS y haga los cambios necesarios para transformar una notificación entrante para la Relación de confianza para usuario autenticado del IdP de ADFS:
    1. Seleccione Editar las reglas de notificaciones.
    2. Elija Agregar regla.
    3. En la página Seleccionar una plantilla para la regla, elija la opción Transformar una notificación entrante para la plantilla de la regla de notificaciones y seleccione Siguiente.
    4. En la página Configurar la regla, en el campo Nombre de la regla de notificaciones, escriba Transformar el correo electrónico en la ID del nombre.
    5. El tipo notificación entrante debe ser SamAccountName (debe coincidir con el Tipo de notificación saliente que se creó inicialmente en la regla Transformar el nombre de usuario en la ID del nombre).
    6. El Tipo de notificación saliente es la ID del nombre.
    7. El formato de la ID del nombre saliente es el correo electrónico.
    8. Confirme que se haya seleccionado Aplicar a todos los valores de notificaciones y seleccione Finalizar.
    9. Seleccione Aceptar para guardar la regla y, nuevamente, Aceptar para completar las asignaciones de los atributos.
  2. En lo que respecta al orden de las Reglas de notificaciones utilizadas para el IdP de ADFS, asegúrese de que no haya reglas opcionales que se ejecuten antes de la regla que contiene el elemento NameID.
  3. Si usa un atributo personalizado, controle que el elemento NameID esté en la Relación de confianza para usuario autenticado, puesto que Learn aún espera que el IdP de ADFS publique un valor NameID.

Problema n.º 6

Aparece el mensaje Error de inicio de sesión inmediatamente después de que el usuario que inició sesión en Blackboard Learn con la autenticación SAML, intenta salir de ella haciendo clic en el botón Cerrar sesión a la izquierda de la página y, luego, en el botón Finalizar sesión de SSO.

Sign On Error!
Blackboard Learn is currently unable to log into your account using single sign-on. Contact your administrator for assistance.
For reference, the Error ID is [error ID].

Se mostrará la excepción siguiente en el registro bb-services:

2017-05-08 15:10:46 -0400 - BbSAMLExceptionHandleFilter Error Id: f3299757-8d4e-4fab-98cf-49cd99f4891e - javax.servlet.ServletException: Incoming SAML message failed security validation
    at org.springframework.security.saml.SAMLLogoutProcessingFilter.processLogout(SAMLLogoutProcessingFilter.java:145)
    at org.springframework.security.saml.SAMLLogoutProcessingFilter.doFilter(SAMLLogoutProcessingFilter.java:104)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:184)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:64)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:53)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:91)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:176)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
    [SNIP]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:745)
Caused by: org.opensaml.ws.security.SecurityPolicyException: Validation of request simple signature failed for context issuer
    at org.opensaml.common.binding.security.BaseSAMLSimpleSignatureSecurityPolicyRule.doEvaluate(BaseSAMLSimpleSignatureSecurityPolicyRule.java:139)
    at org.opensaml.common.binding.security.BaseSAMLSimpleSignatureSecurityPolicyRule.evaluate(BaseSAMLSimpleSignatureSecurityPolicyRule.java:103)
    at org.opensaml.ws.security.provider.BasicSecurityPolicy.evaluate(BasicSecurityPolicy.java:51)
    at org.opensaml.ws.message.decoder.BaseMessageDecoder.processSecurityPolicy(BaseMessageDecoder.java:132)
    at org.opensaml.ws.message.decoder.BaseMessageDecoder.decode(BaseMessageDecoder.java:83)
    at org.opensaml.saml2.binding.decoding.BaseSAML2MessageDecoder.decode(BaseSAML2MessageDecoder.java:70)
    at org.springframework.security.saml.processor.SAMLProcessorImpl.retrieveMessage(SAMLProcessorImpl.java:105)
    at org.springframework.security.saml.processor.SAMLProcessorImpl.retrieveMessage(SAMLProcessorImpl.java:172)
    at org.springframework.security.saml.SAMLLogoutProcessingFilter.processLogout(SAMLLogoutProcessingFilter.java:131)
    ... 244 more

El error se produce debido a la configuración del Tipo de servicio del cierre de sesión único en la página Ajustes de SAML.

Solución

Es necesario cambiar la configuración en Blackboard Learn y en el servidor de ADFS.

En el caso en que ADFS es el IdP, seleccione únicamente los ajustes de Publicación y elimine el extremo de redireccionamiento para la Relación de confianza para usuario autenticado de la instancia de Learn en el servidor de ADFS.

  1. En Learn, vaya a Admin. > Autenticación > (nombre del proveedor) > Ajustes de SAML > Tipo de servicio del cierre de sesión único.
  2. Seleccione Publicación y quite la marca de la casilla de verificación Redirigir.
  3. En el servidor de ADFS, busque la Relación de confianza para usuario autenticado para la instancia de Learn.
  4. Seleccione Propiedades > Extremos. Figuran dos extremos de cierre de sesión de SAML.
  5. Elimine el extremo Redireccionamiento. Seleccione Eliminar extremo para quitarlo y, luego, haga clic en Aplicar y Aceptar.

Una vez realizados los cambios anteriores en Learn y el servidor de ADFS, el botón de cierre de sesión Finalizar sesión de SSO funcionará correctamente.

Problema n.º 7

Después de introducir las credenciales en la página de inicio de sesión de ADFS, aparece el mensaje Error de inicio de sesión cuando se redirige al usuario a Learn.

Se mostrará la excepción siguiente de SAML en el registro bb-services:

2017-05-26 07:39:30 -0400 - unsuccessfulAuthentication - org.springframework.security.authentication.AuthenticationServiceException: Error validating SAML message
        at org.springframework.security.saml.SAMLAuthenticationProvider.authenticate(SAMLAuthenticationProvider.java:100)
        at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:167)
        at org.springframework.security.saml.SAMLProcessingFilter.attemptAuthentication(SAMLProcessingFilter.java:87)
        at blackboard.auth.provider.saml.customization.filter.BbSAMLProcessingFilter.attemptAuthentication(BbSAMLProcessingFilter.java:46)
        at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:217)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
        at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
        at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:184)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
        at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:64)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
        at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:53)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
        at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:91)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
        at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
        at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:176)
        at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
        at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
        at sun.reflect.GeneratedMethodAccessor380.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.apache.catalina.security.SecurityUtil$1.run(SecurityUtil.java:282)
        at org.apache.catalina.security.SecurityUtil$1.run(SecurityUtil.java:279)
        at java.security.AccessController.doPrivileged(Native Method)
        at javax.security.auth.Subject.doAsPrivileged(Subject.java:549)
        at org.apache.catalina.security.SecurityUtil.execute(SecurityUtil.java:314)
        at org.apache.catalina.security.SecurityUtil.doAsPrivilege(SecurityUtil.java:253)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:190)
        at org.apache.catalina.core.ApplicationFilterChain.access$000(ApplicationFilterChain.java:46)
        at org.apache.catalina.core.ApplicationFilterChain$1.run(ApplicationFilterChain.java:148)
        at org.apache.catalina.core.ApplicationFilterChain$1.run(ApplicationFilterChain.java:144)
        at java.security.AccessController.doPrivileged(Native Method)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:143)
        at blackboard.auth.provider.saml.customization.filter.BbSAMLExceptionHandleFilter.doFilterInternal(BbSAMLExceptionHandleFilter.java:37)
    [SNIP]
        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:745)
Caused by: org.opensaml.common.SAMLException: Response has invalid status code urn:oasis:names:tc:SAML:2.0:status:Responder, status message is null
        at org.springframework.security.saml.websso.WebSSOProfileConsumerImpl.processAuthenticationResponse(WebSSOProfileConsumerImpl.java:113)
        at blackboard.auth.provider.saml.customization.consumer.BbSAMLWebSSOProfileConsumerImpl.processAuthenticationResponse(BbSAMLWebSSOProfileConsumerImpl.java:56)
        at org.springframework.security.saml.SAMLAuthenticationProvider.authenticate(SAMLAuthenticationProvider.java:87)
        ... 247 more

Solución

A partir de la versión Blackboard Learn 3200.0.0, hay una opción para volver a generar el certificado de cifrado de SAML. Para utilizarla, vaya a Admin. del sistema > Building Blocks > Proveedor de la autenticación: SAML > Ajustes > Volver a generar el certificado. El problema del Error de inicio de sesión puede ocurrir si el usuario hace clic en el botón Volver a generar el certificado después de que los metadatos de SP ya se hayan cargado a la Relación de confianza para usuario autenticado para el sitio de Learn en el servidor de ADFS. Para solucionar el problema:

  1. Vaya a Admin. del sistema > Autenticación > [nombre del proveedor de SAML] > Ajustes de SAML.
  2. Elija la opción Generar junto a los metadatos del proveedor de servicios para guardar el archivo nuevo de metadatos.
  3. Acceda al servidor de ADFS y cargue los nuevos metadatos del SP a la Relación de confianza para usuario autenticado para el sitio de Learn.

Metadatos de federación

En el caso de Active Directory Federation Services (ADFS), dado que los metadatos para una federación de ADFS que se ubican habitualmente en https://[ADFS Server Hostname]/FederationMetadata/2007-06/FederationMetadata.xml incluyen un elemento que es incompatible con SAML 2.0, es necesario editar los metadatos para borrar dicho elemento antes de cargarlos en la sección Ajustes del proveedor de identidades de la página Ajustes de la autenticación SAML en la GUI de Blackboard Learn. Si se cargan los metadatos con el elemento que no es compatible, se producirá un error al seleccionar el enlace del inicio de sesión SAML en la página de inicio de Blackboard Learn: No se encontraron los metadatos para la entidad [entidad] y el rol {}. Para su referencia, la Id. del error es [ID del error].

Y el seguimiento de la pila de Java correspondiente para la ID del error en el registro bb-services es el siguiente:

2016-06-21 11:42:51 -0700 - Metadata for entity https://<Learn Server Hostname>/adfs/ls/ and role {urn:oasis:names:tc:SAML:2.0:metadata}SPSSODescriptor wasn&#39;t found<P><span class="captionText">For reference, the Error ID is c99511ae-1162-4941-b823-3dda19fea157.</span&gt; - org.opensaml.saml2.metadata.provider.MetadataProviderException: Metadata for entity https://ulvsso.laverne.edu/adfs/ls/ and role {urn:oasis:names:tc:SAML:2.0:metadata}SPSSODescriptor wasn't found
    at org.springframework.security.saml.context.SAMLContextProviderImpl.populateLocalEntity(SAMLContextProviderImpl.java:319)
    at org.springframework.security.saml.context.SAMLContextProviderImpl.populateLocalContext(SAMLContextProviderImpl.java:216)
    at org.springframework.security.saml.context.SAMLContextProviderImpl.getLocalAndPeerEntity(SAMLContextProviderImpl.java:126)
    at org.springframework.security.saml.SAMLEntryPoint.commence(SAMLEntryPoint.java:146)
    at org.springframework.security.saml.SAMLEntryPoint.doFilter(SAMLEntryPoint.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:184)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:64)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:53)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:91)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:176)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
    at sun.reflect.GeneratedMethodAccessor1652.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
        [SNIP]

Solución

Como la ubicación predeterminada de los metadatos para una federación de ADFS es https://[ADFS server hostname]/FederationMetadata/2007-06/FederationMetadata.xml:

  1. Descargue el archivo y ábralo en el editor de texto. Con cuidado, elimine la sección que comienza con <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"&gt; ... </X509Data></KeyInfo> y termina en </ds:Signature>
    <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">

    <ds:SignedInfo>
      <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
      <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
      <ds:Reference URI="#_43879f32-9a91-4862-bc87-e98b85b51158">
       <ds:Transforms>
        <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
        <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
       </ds:Transforms>
       <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
       <ds:DigestValue>z1H1[SNIP]jaYM=</ds:DigestValue>
      </ds:Reference>
      </ds:SignedInfo>
      <ds:SignatureValue&gt; FVj[SNIP]edrfNKWvsvk5A==
      </ds:SignatureValue>
      <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
       <X509Data>
        <X509Certificate>
        FDdd[SNIP]qTNKdk5F/vf1AocDaX
        </X509Certificate>
       </X509Data>
      </KeyInfo>
    </ds:Signature>

    .
  2. Cargue el archivo XML de metadatos actualizado en la GUI de Blackboard Learn, en la sección Ajustes del proveedor de identidades de la página Ajustes de la autenticación SAML.
  3. Utilice el botón de alternancia Inactivo/Disponible para el proveedor de la autenticación SAML y SAML B2.

Si una institución está probando la autenticación SAML en un sitio de Blackboard Learn y tiene varios proveedores de la autenticación SAML que comparten el mismo archivo XML de metadatos del IdP de ADFS subyacente en el sitio de Blackboard Learn, aún si la configuración del resto de los proveedores de la autenticación SAML está establecida en Inactivo, también será necesario cargar el archivo XML de metadatos actualizado en la GUI de Blackboard Learn en la sección Ajustes del proveedor de identidades de la página Ajustes de la autenticación SAML. Se deberá alternar la autenticación SAML B2 entre Inactivo/Disponible para asegurarse de que el archivo XML de metadatos actualizado sea reconocido en todo el sistema.

Método de búsqueda del usuario incorrecto

Después de introducir las credenciales en la página de inicio de sesión de ADFS, el usuario es redirigido a la GUI de Blackboard Learn pero no puede ingresar.

El ÚNICO evento relacionado con la autenticación SAML en el registro bb-services es el siguiente:

2016-10-18 13:03:28 -0600 - userName is null or empty

Solución

  1. Inicie sesión en Blackboard Learn como administrador mediante la autenticación interna de Blackboard Learn predeterminada.
  2. Vaya a Admin. del sistema > "nombre del proveedor de la autenticación SAML" > Editar.
  3. Cambie el Método de búsqueda de usuarios de UID de lote a Nombre de usuario.

Botón de cierre de la sesión SSO adicional

En ADFS es posible agregar un botón de cierre Finalizar la sesión SSO adicional en la página ¿Desea cerrar todas las sesiones? que aparece después de hacer clic en el botón de cierre de sesión en el extremo superior derecho de la GUI de Blackboard Learn.

Para ello, se debe agregar un elemento SingleLogoutService extra al archivo de metadatos del IdP:

<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://your.server.name/adfs/ls/"/>
<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://your.server.name/adfs/ls/"/>

Dado que se trata de una configuración opcional del IdP de SAML B2 y la firma que se proporciona en el extremo de redireccionamiento no es correcta, cuando haga clic en el botón Finalizar la sesión SSO adicional de la página ¿Desea cerrar todas las sesiones?, se producirá un error: El mensaje de SAML entrante no pasó la validación de seguridad. Se produjo un error en la firma simple de la solicitud de validación para el emisor del contexto. Para su referencia, la Id. del error es [ID del error].

El seguimiento de la pila de Java correspondiente para la ID del error en el registro bb-services es el siguiente:

2016-10-17 16:57:44 -0400 - Incoming SAML message failed security validation Validation of request simple signature failed for context issuer<P><span class="captionText">For reference, the Error ID is 930c7767-8710-475e-8415-2077152280e0.</span&gt; - org.opensaml.ws.security.SecurityPolicyException: Validation of request simple signature failed for context issuer
    at org.opensaml.common.binding.security.BaseSAMLSimpleSignatureSecurityPolicyRule.doEvaluate(BaseSAMLSimpleSignatureSecurityPolicyRule.java:139)
    at org.opensaml.common.binding.security.BaseSAMLSimpleSignatureSecurityPolicyRule.evaluate(BaseSAMLSimpleSignatureSecurityPolicyRule.java:103)
    at org.opensaml.ws.security.provider.BasicSecurityPolicy.evaluate(BasicSecurityPolicy.java:51)
    at org.opensaml.ws.message.decoder.BaseMessageDecoder.processSecurityPolicy(BaseMessageDecoder.java:132)
    at org.opensaml.ws.message.decoder.BaseMessageDecoder.decode(BaseMessageDecoder.java:83)
    at org.opensaml.saml2.binding.decoding.BaseSAML2MessageDecoder.decode(BaseSAML2MessageDecoder.java:70)
    at org.springframework.security.saml.processor.SAMLProcessorImpl.retrieveMessage(SAMLProcessorImpl.java:105)
    at org.springframework.security.saml.processor.SAMLProcessorImpl.retrieveMessage(SAMLProcessorImpl.java:172)
    at org.springframework.security.saml.SAMLLogoutProcessingFilter.processLogout(SAMLLogoutProcessingFilter.java:131)
    at org.springframework.security.saml.SAMLLogoutProcessingFilter.doFilter(SAMLLogoutProcessingFilter.java:104)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:184)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:64)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:53)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:91)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:176)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
    at sun.reflect.GeneratedMethodAccessor1652.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
        [SNIP]

Solución

  1. Acceda al servidor de ADFS y vaya a la Relación de confianza para usuario autenticado para la instancia de Blackboard Learn.
  2. Seleccione la ficha Propiedades > Extremos.
  3. En la ficha Extremos, encontrará 2 Extremos de cierre de sesión SAML.
  4. Elimine el extremo Redireccionamiento.
  5. Seleccione Eliminar extremo para quitarlo y, luego, haga clic en Aplicar y Aceptar.

Una vez que haya quitado el extremo Redireccionamiento, el botón Finalizar la sesión SSO funcionará correctamente cuando el usuario intente salir.

Ver los registros de la aplicación con el visor de eventos

Cuando intente solucionar un problema de autenticación SAML en ADFS, es posible que deba revisar los registros de la aplicación ADFS de la institución en el visor de eventos del servidor de ADFS para obtener información más detallada. Esto es necesario, en especial, cuando la respuesta SAML del servidor de ADFS tiene un estado de Solicitud rechazada, tal como se muestra a continuación:

<samlp:Status>
    <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Responder">
        <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:RequestDenied" />
    </samlp:StatusCode>
</samlp:Status>

Puede usar el complementos SAML Tracer del navegador Firefox para ver la respuesta SAML.

El estado de Solicitud rechazada en una respuesta comúnmente indica que se produjo un problema cuando el IdP (ADFS) intentó comprender la respuesta y procesar el resultado que proporcionó el SP (Blackboard Learn).

Para ver los registros de la aplicación con el visor de eventos:

  1. Abra el visor de eventos en el servidor de ADFS.
  2. En el menú Ver, elija la opción Mostrar los registros de depuración y el análisis.
  3. En el árbol de la consola, vaya a Registros de la aplicación y del servicio > Seguimiento de ADFS > Depurar.

Azure Active Directory

Azure AD es el servicio de administración de identidad y directorio en la nube de Microsoft (MS).

Enviar la primera parte de un correo electrónico

Si una institución utiliza Azure AD como su IdP y desea que solo se utilice la primera parte del nombre de usuario del correo electrónico de Azure AD en el nombre de usuario de Blackboard Learn, puede configurar el IdP de Azure AD para que utilice la función ExtractMailPrefix() especial a fin de quitar el sufijo del dominio, tanto del correo electrónico como del nombre principal del usuario. Como consecuencia, solo se transmitirá la primera parte del nombre de usuario (p. ej., "juanperez" en vez de juanperez@ejemplo.com).

Si la ID del usuario remoto de Blackboard Learn es urn:oid:1.3.6.1.4.1.5923.1.1.1.6, estos serán los ajustes del Atributo para el IdP de Azure:

Attribute Name:        urn:oid:1.3.6.1.4.1.5923.1.1.1.6
Attribute Value:    ExtractMailPrefix()
Mail:            user.userprincipalname

En el ejemplo del nombre de usuario de correo juanperez@ejemplo.com, en la aserción de SAML solo se transmitirá lo siguiente desde el IdP de Azure a Blackboard Learn:

<Attribute Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.6">
    <AttributeValue>joesmith</AttributeValue>

Obtenga más información sobre el uso de la función ExtractMailPrefix() en la página de documentación de MS Azure.

Certificado de actualización del IdP de Azure AD

Después de introducir las credenciales en la página de inicio de sesión de MS Azure AD, se produce un Error de inicio de sesión una vez que el usuario es redirigido a la GUI de Blackboard Learn.

Se mostrará la excepción siguiente en el registro bb-services:

2016-10-13 12:03:23 +0800 - unsuccessfulAuthentication - org.springframework.security.authentication.AuthenticationServiceException: Error validating SAML message
 at org.springframework.security.saml.SAMLAuthenticationProvider.authenticate(SAMLAuthenticationProvider.java:100)
 at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:167)
 at org.springframework.security.saml.SAMLProcessingFilter.attemptAuthentication(SAMLProcessingFilter.java:87)
 at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:217)
 at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
 at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
 at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:184)
 at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
 at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:64)
 at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
 at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
 at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:53)
 at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
 at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
 at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:91)
 at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
 at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
 at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:176)
 at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
 at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
 at sun.reflect.GeneratedMethodAccessor854.invoke(Unknown Source)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:498)
 at org.apache.catalina.security.SecurityUtil$1.run(SecurityUtil.java:282)
 at org.apache.catalina.security.SecurityUtil$1.run(SecurityUtil.java:279)
 at java.security.AccessController.doPrivileged(Native Method)
 at javax.security.auth.Subject.doAsPrivileged(Subject.java:549)
 at org.apache.catalina.security.SecurityUtil.execute(SecurityUtil.java:314)
 at org.apache.catalina.security.SecurityUtil.doAsPrivilege(SecurityUtil.java:253)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:190)
 at org.apache.catalina.core.ApplicationFilterChain.access$000(ApplicationFilterChain.java:46)
 at org.apache.catalina.core.ApplicationFilterChain$1.run(ApplicationFilterChain.java:148)
 at org.apache.catalina.core.ApplicationFilterChain$1.run(ApplicationFilterChain.java:144)
 at java.security.AccessController.doPrivileged(Native Method)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:143)
 at blackboard.auth.provider.saml.customization.filter.BbSAMLExceptionHandleFilter.doFilterInternal(BbSAMLExceptionHandleFilter.java:30)
 [SNIP]
 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:745)
Caused by: org.opensaml.common.SAMLException: Response doesn't have any valid assertion which would pass subject validation
 at org.springframework.security.saml.websso.WebSSOProfileConsumerImpl.processAuthenticationResponse(WebSSOProfileConsumerImpl.java:229)
 at blackboard.auth.provider.saml.customization.consumer.BbSAMLWebSSOProfileConsumerImpl.processAuthenticationResponse(BbSAMLWebSSOProfileConsumerImpl.java:40)
 at org.springframework.security.saml.SAMLAuthenticationProvider.authenticate(SAMLAuthenticationProvider.java:87)
 ... 230 more
Caused by: org.opensaml.xml.validation.ValidationException: Signature is not trusted or invalid
 at org.springframework.security.saml.websso.AbstractProfileBase.verifySignature(AbstractProfileBase.java:272)
 at org.springframework.security.saml.websso.WebSSOProfileConsumerImpl.verifyAssertionSignature(WebSSOProfileConsumerImpl.java:419)
 at org.springframework.security.saml.websso.WebSSOProfileConsumerImpl.verifyAssertion(WebSSOProfileConsumerImpl.java:292)
 at org.springframework.security.saml.websso.WebSSOProfileConsumerImpl.processAuthenticationResponse(WebSSOProfileConsumerImpl.java:214)
 ... 232 more

Esto se debe a que el IdP de MS Azure AD actualiza el certificado, pero el archivo XML de metadatos que utiliza el SP de Blackboard Learn no refleja el certificado nuevo.

Solución

  • Deberá actualizar el nuevo archivo XML de metadatos que contiene el certificado nuevo en la página Ajustes de SAML de la GUI de Blackboard Learn en lo que respecta al proveedor de la autenticación.
  • Será necesario alternar el proveedor de la autenticación y SAML B2 entre el estado Inactivo/Disponible para que se apliquen los metadatos actualizados con el certificado nuevo.
  • Si un sitio de Blackboard Learn tiene varios proveedores de la autenticación que comparten el mismo certificado subyacente para la misma ID de la entidad del IdP subyacente, se deberán actualizar TODOS esos proveedores.

Microsoft ha informado que actualizará los certificados cada 6 semanas de ahora en adelante y que anunciará dichas actualizaciones.


Inicio se sesión único mediante IdP

Si un usuario inicia sesión en su portal y, luego, selecciona la aplicación en el sitio de Blackboard Learn, se abrirá una nueva ficha en el navegador con el siguiente mensaje: No se encontró el recurso especificado o no tiene permiso para acceder a él.

También aparecerán los eventos relacionados de SAML en el registro stdout-stderr.log:

INFO   | jvm 1    | 2016/08/16 10:49:22 | - /saml/SSO at position 1 of 10 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
INFO   | jvm 1    | 2016/08/16 10:49:22 | - HttpSession returned null object for SPRING_SECURITY_CONTEXT
INFO   | jvm 1    | 2016/08/16 10:49:22 | - No SecurityContext was available from the HttpSession: [email protected] A new one will be created.
INFO   | jvm 1    | 2016/08/16 10:49:22 | - /saml/SSO at position 2 of 10 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
INFO   | jvm 1    | 2016/08/16 10:49:22 | - /saml/SSO at position 3 of 10 in additional filter chain; firing Filter: 'HeaderWriterFilter'
INFO   | jvm 1    | 2016/08/16 10:49:22 | - /saml/SSO at position 4 of 10 in additional filter chain; firing Filter: 'FilterChainProxy'
INFO   | jvm 1    | 2016/08/16 10:49:22 | - Checking match of request : '/saml/sso'; against '/saml/login/**'
INFO   | jvm 1    | 2016/08/16 10:49:22 | - Checking match of request : '/saml/sso'; against '/saml/logout/**'
INFO   | jvm 1    | 2016/08/16 10:49:22 | - Checking match of request : '/saml/sso'; against '/saml/bbsamllogout/**'
INFO   | jvm 1    | 2016/08/16 10:49:22 | - Checking match of request : '/saml/sso'; against '/saml/sso/**'
INFO   | jvm 1    | 2016/08/16 10:49:22 | - /saml/SSO at position 1 of 1 in additional filter chain; firing Filter: 'SAMLProcessingFilter'
INFO   | jvm 1    | 2016/08/16 10:49:22 | - Forwarding to /
INFO   | jvm 1    | 2016/08/16 10:49:22 | - DispatcherServlet with name 'saml' processing POST request for [/auth-saml/saml/SSO]
INFO   | jvm 1    | 2016/08/16 10:49:22 | - No mapping found for HTTP request with URI [/auth-saml/saml/SSO] in DispatcherServlet with name 'saml'
INFO   | jvm 1    | 2016/08/16 10:49:22 | - SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
INFO   | jvm 1    | 2016/08/16 10:49:22 | - Successfully completed request
INFO   | jvm 1    | 2016/08/16 10:49:22 | - Skip invoking on
INFO   | jvm 1    | 2016/08/16 10:49:22 | - SecurityContextHolder now cleared, as request processing completed

Hubo cambios en la sección Ajustes del proveedor de servicios de la página Ajustes de la autenticación SAML, y ahora es necesario marcar la opción Activar el SSO automático para que el usuario pueda acceder a Blackboard Learn desde el portal. Si la opción no está activada, la URL de ACS también se modificará para incluir un alias.


Error de documento incorrecto

Después de introducir las credenciales en la página de inicio de sesión del proveedor de la autenticación SAML, se produce un Error de inicio de sesión una vez que el usuario es redirigido a la GUI de Blackboard Learn.

Se mostrará los siguientes elementos DOMException y WRONG_DOCUMENT_ERR en el registro bb-services:

2016-11-18 12:27:31 -0600 - WRONG_DOCUMENT_ERR: A node is used in a different document than the one that created it.<P><span class="captionText">For reference, the Error ID is 86ebb81d-d3a3-4da5-95ab-1c94505f4281.</span&gt; - org.w3c.dom.DOMException: WRONG_DOCUMENT_ERR: A node is used in a different document than the one that created it.
    at org.apache.xerces.dom.ParentNode.internalInsertBefore(Unknown Source)
    at org.apache.xerces.dom.ParentNode.insertBefore(Unknown Source)
    at org.apache.xerces.dom.NodeImpl.appendChild(Unknown Source)
    at org.opensaml.xml.encryption.Decrypter.parseInputStream(Decrypter.java:832)
    at org.opensaml.xml.encryption.Decrypter.decryptDataToDOM(Decrypter.java:610)
    at org.opensaml.xml.encryption.Decrypter.decryptUsingResolvedEncryptedKey(Decrypter.java:795)
    at org.opensaml.xml.encryption.Decrypter.decryptDataToDOM(Decrypter.java:535)
    at org.opensaml.xml.encryption.Decrypter.decryptDataToList(Decrypter.java:453)
    at org.opensaml.xml.encryption.Decrypter.decryptData(Decrypter.java:414)
    at org.opensaml.saml2.encryption.Decrypter.decryptData(Decrypter.java:141)
    at org.opensaml.saml2.encryption.Decrypter.decrypt(Decrypter.java:69)
    at org.springframework.security.saml.websso.WebSSOProfileConsumerImpl.processAuthenticationResponse(WebSSOProfileConsumerImpl.java:199)
    at org.springframework.security.saml.SAMLAuthenticationProvider.authenticate(SAMLAuthenticationProvider.java:82)
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:167)
    at org.springframework.security.saml.SAMLProcessingFilter.attemptAuthentication(SAMLProcessingFilter.java:87)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:217)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:184)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:64)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:53)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:91)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:176)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
    at sun.reflect.GeneratedMethodAccessor1209.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.apache.catalina.security.SecurityUtil$1.run(SecurityUtil.java:277)
    at org.apache.catalina.security.SecurityUtil$1.run(SecurityUtil.java:274)
    at java.security.AccessController.doPrivileged(Native Method)
    at javax.security.auth.Subject.doAsPrivileged(Subject.java:549)
    at org.apache.catalina.security.SecurityUtil.execute(SecurityUtil.java:309)
    at org.apache.catalina.security.SecurityUtil.doAsPrivilege(SecurityUtil.java:249)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:237)
    at org.apache.catalina.core.ApplicationFilterChain.access$000(ApplicationFilterChain.java:55)
    at org.apache.catalina.core.ApplicationFilterChain$1.run(ApplicationFilterChain.java:191)
    at org.apache.catalina.core.ApplicationFilterChain$1.run(ApplicationFilterChain.java:187)
    at java.security.AccessController.doPrivileged(Native Method)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:186)
    at blackboard.platform.servlet.DevNonceFilter.doFilter(DevNonceFilter.java:68)
    [SNIP]

El problema se produce porque otro B2/proyecto cambió el valor javax.xml.parsers.DocumentBuilderFactory de la propiedad del sistema de org.apache.xerces.jaxp.DocumentBuilderFactoryImpl a com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl..

Solución temporaria

Hasta que se publique una corrección, estas son las opciones para una solución temporaria:

  1. Reinicie los servicios de Blackboard en cada nodo.
  2. Desactive el cifrado de la respuesta SAML del lado del IdP. Dado que toda la comunicación se realiza a través de SSL, no afectará la seguridad de la autenticación.

No hay una opción para agregar SAML al orden de los proveedores

Al configurar la autenticación SAML, puede que la institución note que no hay una opción para agregar un proveedor de la autenticación SAML en la sección Orden de los proveedores en la GUI de Blackboard Learn cuando se accede a Admin. del sistema > Building Blocks: Autenticación > Orden de los proveedores.

The reason there is not an option to add a SAML authentication provider to the Provider Order is that redirect type providers such as CAS and SAML hand off authentication to the remote authentication source. Ese tipo de proveedores no se indican en la opción Orden de los proveedores, ya que se consideran el origen autoritativo de la autenticación y gestionan sus propios errores de autenticación.


Probar la conexión SAML

A partir de la versión de Blackboard Learn del cuarto trimestre de 2016, existe una opción para probar la conexión de un proveedor SAML en la sección Autenticación de la GUI de Blackboard Learn. Con la prueba de conexión podrá comprobar lo siguiente:

  • Análisis de los metadatos del IdP
  • Conexión al IdP
  • Recepción de la respuesta SAML
  • Análisis de la respuesta SAML
  • Coincidencia con la ID del usuario remoto
  • Inicio de sesión en Blackboard Learn

Para probar la conexión a un proveedor de la autenticación SAML:

  1. Inicie sesión en Blackboard Learn como administrador.
  2. Vaya a Admin. del sistema > Building Blocks: Autenticación > "nombre del proveedor de SAML" > Probar la conexión.
  3. Si se le solicita, introduzca las credenciales de inicio de sesión del IdP.

Por varios motivo, en lugar de activar el inicio de sesión de la depuración SAML en forma manual en Blackboard Learn, puede utilizar la función Probar la conexión.

El valor de la ID de la entidad del proveedor de identidades que aparece en la página de salida Probar la conexión se obtiene del elemento Emisor en SAML POST del IdP a Blackboard Learn después de que se haya autenticado al usuario:

<Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">http://bbpdcsi-adfs1.bbpdcsi.local/a...services/trust</Issuer>

Los valores del Atributo de SAML que aparecen en la página de salida Probar la conexión en la sección Respuesta SAML se obtienen de los elementos Asunto y AttributeStatement en SAML POST del IdP a Blackboard Learn después de que se haya autenticado al usuario:

<Subject>
    <NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">luke.skywalker</NameID>
    [SNIP]
</Subject>
<AttributeStatement>
    <Attribute Name="SamAccountName">
        <AttributeValue>luke.skywalker</AttributeValue>
    </Attribute>
    <Attribute Name="urn:oid:2.5.4.42">
        <AttributeValue>Luke</AttributeValue>
    </Attribute>
    <Attribute Name="urn:oid:2.5.4.4">
        <AttributeValue>Skywalker</AttributeValue>
    </Attribute>
</AttributeStatement>


Crear un proveedor de la autenticación SAML y un IdP para pruebas

Siga los pasos a continuación para crear un Proveedor de identidad (IdP) con la solución de autenticación SSO gratuita de Centrify.

Podrá configurar el IdP como el proveedor de la autenticación SAML en un Proveedor de servicios (SP) de Blackboard Learn:

Proveedor de servicios de Blackboard Learn

  1. Inicie sesión como administrador en la GUI de Blackboard Learn y vaya a Admin. del sistema > Autenticación.
  2. Elija la opción Crear un proveedor > SAML.
  3. Introduzca los siguientes ajustes:
    • Nombre > SAML o el que desee.
    • Proveedor de la autenticación > Inactivo (por el momento).
    • Método de búsqueda de los usuarios > Nombre de usuario
    • Restringir por nombre de host > Usar este proveedor para cualquier nombre de host
    • Vincular el texto > Inicio de sesión de SAML Centrify
  4. Seleccione Guardar y configurar.
  5. En el campo ID de la identidad, establezca la opción que desee (pero si la modifica, deberá proporcionar los metadatos del proveedor de servicios actualizados al proveedor de identidades). Copie y pegue la URL de ACS.
  6. En metadatos del proveedor de servicios, elija la opción Generar y guarde el archivo en el escritorio.
  7. En Origen de datos, se recomienda que cree un nuevo Origen de los datos con el nombre CENTRIFY; de lo contrario, use SISTEMA o el nombre que desee.
  8. Marque la casilla de verificación que se encuentra junto a Activar el aprovisionamiento JIT. De este modo, se creará automáticamente una cuenta cuando intente iniciar sesión mediante este proveedor de la autenticación SAML si el usuario no existe. Si no se selecciona el aprovisionamiento JIT, se deberá crear el usuario de forma manual en Blackboard Learn.
  9. En la lista Orígenes de datos compatibles, asegúrese de seleccionar los orígenes de datos con los que este proveedor de la autenticación debe ser compatible.
  10. Seleccione Señalar el proveedor de identidades para el Tipo de proveedor de identidades.
  11. Por el momento, omita el paso de los Metadatos del proveedor de identidades; más adelante, cargará el archivo que ha creado en la sección IdP de Centrify.
  12. Para la sección Asignar los atributos de SAML, use NameID para la ID del usuario remoto.
  13. Haga clic en Enviar.

Proveedor de identidad de Centrify

  1. Vaya a https://www.centrify.com/express/identity-service y elija la opción Comenzar ahora en la columna Express, en la parte inferior de la página.
  2. Regístrese con su información y seleccione Comenzar ahora.
  3. Recibirá un mensaje de bienvenida por correo electrónico con las credenciales de administrador. Úselas para iniciar sesión en https://cloud.centrify.com.
  4. En la ventana Bienvenido al servicio de identidad de Centrify, seleccione Omitir.
  5. En la ficha Aplicaciones, en la parte superior de la página, haga clic en el botón Agregar aplicaciones web.
  6. En la ficha Personalizar, desplácese hasta la parte inferior y presione el botón Agregar para SAML. Seleccione .
  7. Elija Cerrar en la parte inferior de la ventana Agregar aplicaciones web.
  8. Vaya a la ficha Aplicaciones. En la sección Ajustes de las aplicaciones, haga clic en el botón Cargar los metadatos del SP y cargue el archivo que se generó en el Paso 6 de la sección SP de Blackboard Learn.
  9. La URL del Servicio de consumidor de aserciones debería completarse automáticamente después de cargar los metadatos del SP.
  10. Desactive la opción Cifrar aserción. Esto permite que los atributos publicados desde el IdP y enviados a Blackboard Learn se puedan ver con el complemento SAML Tracer del navegador Firefox o con SAML Message Decoder de Chrome. Dado que toda la comunicación se realiza a través de SSL, no afectará la seguridad de la autenticación.
  11. Desplácese hacia abajo y seleccione Descargar los metadatos del proveedor de identidades SAML. Guarde el archivo en el escritorio.
  12. Seleccione Guardar y vaya a la sección siguiente.
  13. Introduzca un nombre para la sección Descripción. Seleccione Guardar y vaya a la sección siguiente.
  14. En la sección Acceso del usuario, elija la opción Todos y Administrador del sistema. Seleccionar Guardar.
  15. No seleccione ninguna opción en la sección Política.
  16. En la sección Asignación de cuentas, confirme que se haya ingresado userprincipalname en el campo Servicio de directorio.
  17. En la sección Opciones avanzadas, agregue la línea siguiente en la parte inferior del script utilizado para generar una aserción SAML para la aplicación:

    Este será el script completo:

    setIssuer(Issuer);
    setSubjectName(UserIdentifier);
    setAudience('https://YourLearnServer.blackboard.c...saml/saml/SSO');
    setRecipient(ServiceUrl);
    setHttpDestination(ServiceUrl);
    setSignatureType('Assertion');
    setNameFormat('emailaddress');
    setAttribute("NameID", LoginUser.Get("userprincipalname"));

    Esto permitirá que el IdP de Centrify publique un elemento AttributeStatement con la ID del usuario en SAML POST.

    Ejemplo:

    <AttributeStatement>
        <Attribute Name="NameID"
                   NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
                   >
            <AttributeValue>luke.skywalker@blackboard.com.47</AttributeValue>
        </Attribute>
    </AttributeStatement>

    Más información sobre cómo indicar los elementos de aserción en el script de SAML de Centrify

  18. Seleccionar Guardar.
  19. El resto de las secciones (Puerta de enlace de la aplicación, Registro de cambios y Flujo de trabajo) no necesitan cambios.
  20. En la ficha Aplicaciones, confirme que la aplicación SAML se haya implementado de forma automática.
  21. En la ficha Usuarios, elija la opción Agregar usuarios, escriba la información de la cuenta de un usuario y seleccione Crear un usuario.
  22. Vuelva a iniciar sesión como administrador en la GUI de Blackboard Learn, vaya a Admin. del sistema > Autenticación > Nombre del proveedor de la autenticación SAML > Ajustes de SAML > Ajustes del proveedor de identidades, a continuación, cargue el archivo de metadatos del IdP que guardó en el escritorio en el Paso 13 y haga clic en Enviar.

Ahora el usuario del IdP de Centrify que se creó podrá iniciar sesión en Blackboard Learn mediante SAML. Para ello, deberá seleccionar ese proveedor de la autenticación en la página de inicio y cerrar la sesión de Blackboard Learn con el botón de cierre Finalizar la sesión SSO adicional en la página ¿Desea cerrar todas las sesiones? que aparece después de hacer clic en el botón de cierre de sesión en el extremo superior derecho de Blackboard Learn.


Cambiar el texto de la página de cierre Finalizar la sesión SSO

Puede que una institución se pregunte si es posible cambiar el texto de la página de cierre Finalizar la sesión SSO. Esto es posible si se edita el paquete de idiomas:

  1. Abra el archivo del paquete de idiomas.
  2. Vaya a auth-provider-saml/src/main/webapp/WEB-INF/bundles/bb-manifest-en_US.properties.
  3. Actualice las claves del mensaje:

    saml.single.logout.warning.conent.description // the first line
    saml.single.logout.warning.conent.recommend // second line
    saml.single.logout.warning.endsso.title // third line
    saml.single.logout.warning.endsso.button // the button
    saml.single.logout.warning.backtolearn // the cancel button


Redirigir a los usuarios a la página de inicio de sesión del IdP

En la página de inicio de sesión estándar de Blackboard Learn, encontrará los campos "nombre de usuario" y "contraseña" para el proveedor de la autenticación interno predeterminado de Learn. Cuando activa la autenticación SAML, se muestra un breve enlace "Acceder mediante..." para SAML en la parte inferior de la página. De este modo, podrá redirigir a los usuarios al servidor de la autenticación del IdP de forma automática cuando ingresen a la página de inicio de sesión de Learn.

Una de las opciones es ir a Admin. del sistema > Autenticación y establecer la autenticación interna predeterminada de Learn como Inactiva. Ya no aparecerá una página de inicio de sesión, y el usuario será inmediatamente redirigido a la página de inicio de SAML. El problema que presenta esta opción es que reemplaza la URL de inicio de sesión predeterminada e impide que un usuario que no utiliza SAML pueda acceder.

Para evitar este inconveniente y proporcionar prácticamente el mismo resultado, use una página de inicio de sesión personalizada. Se redirigirá a los usuarios a la página de inicio de sesión del IdP de la autenticación SAML, pero también se podrá utilizar el enlace a la página predeterminada.

  1. Asegúrese de que la autenticación interna de Learn predeterminada esté activa.
  2. En la página de inicio de sesión predeterminada, copie la ubicación del redireccionamiento del proveedor, p. ej., Acceder mediante... SAML. Haga clic con el botón derecho en el enlace y seleccione Copiar la ubicación del enlace.
  3. Vaya a Admin. del sistema > Comunidades > Marcas y temas > Página de inicio de sesión personalizada.
  4. Seleccione Descargar junto a la opción Página de inicio de sesión predeterminada para descargar el archivo JSP.
  5. Abra el archivo JSP con un editor de texto. Agregue el siguiente archivo HTML de muestra al archivo JSP de inicio de sesión y reemplace el texto de la URL por la URL que copió en el Paso 2.

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
    <html>
    <head>
    <title>Blackboard Learn - Redirect</title>
    <meta http-equiv="REFRESH" content="0;url=https://URL_Goes_Here"></HEAD>
    <BODY style="font-family: arial,sans-serif;font-size: small; color: grey; padding: 1em; ">
    Redirecting... <a style="color:grey" href="https://URL_Goes_Here">Go to login page</a&gt; if you are not automatically redirected.
    </BODY>
    </HTML>

  6. Nuevamente, vaya a la opción Página de inicio de sesión personalizada en Learn. Seleccione Usar la página personalizada y cargue el archivo JSP de inicio de sesión actualizado.
  7. Después de efectuar los cambios, elija la opción Vista previa en la Página de inicio de sesión personalizada para verificar que el redireccionamiento funcione correctamente.

Ahora, los usuarios que vayan a la URL principal serán redirigidos a la página de inicio de sesión del proveedor de la autenticación SAML. Los administradores pueden seguir iniciando sesión con la autenticación interna de Learn a través de la página predeterminada: /webapps/login/?action=default_login o/webapps/login/login.jsp.

Más información sobre cómo personalizar la página de inicio de sesión en la experiencia Ultra