Skip to main content

SAML 인증에서 발생하는 일반적인 문제

이 페이지에서는 SAML(Security Assertion Markup Language) 2.0의 기본 구성 요소와 SAML 인증 공급자에 관한 일반적인 단일 로그인(SSO) 문제 및 해결 방법을 소개합니다.

중요

어떤 이유로든 업데이트된/새 IdP 메타데이터 XML 파일을 SAML 인증 공급자의 ID 공급자 설정 섹션에 있는 Blackboard GUI의 SAML 인증 설정 페이지에 업로드한 경우, SAML B2와 해당 SAML 인증 공급자를 비활성/사용 가능으로 전환해야 하며, SAML 인증 공급자가 '활성' 상태일 때는 캐시된 IdP 메타데이터를 삭제하고 업데이트된 IdP 메타데이터를 완전히 활용하도록 해야 합니다.

: ‘주요 용어’를 정의하고 설명하는 중입니다.

이 가이드에서 사용되는 용어 및 약어는 다음과 같습니다.

  • SAML: 보안 주장 마크업 언어

  • ID 공급자(IdP)

  • 서비스 제공자

  • Active Directory 페더레이션 서비스(ADFS)

  • GUI: 그래픽 사용자 인터페이스입니다. Blackboard 맥락에서는 소프트웨어 내 작업을 가리킵니다.

SAML 구성 설정을 편집합니다.

SAML 인증 문제 해결을 위해 SAML 빌딩 블록이 릴리스 3200.2.0에서 업데이트되어 다음과 같은 구성 설정 및 옵션이 포함되었습니다.

  • SAML 세션 기간 제한을 정의합니다.

  • 서명 알고리즘 유형을 선택하세요.

  • 인증서 재발급

  • ResponseSkew값 변경

처리하기

SAML 관련 오류 및 예외는 다음 로그에 기록됩니다.

  • /usr/local/blackboard/logs/bb-서비스-log.txt

  • /usr/local/blackboard/logs/tomcat/stdout-stderr-<date>.log

  • /usr/local/blackboard/logs/tomcat/catalina-log.txt

SAML 인증 문제가 보고될 때 해당 로그를 반드시 검토해야 합니다.

SAML 추적기

SAML 2.0 인증 문제를 해결하기 위한 반복 사용 시 인증 과정에서 IdP에서 실제로 해제되어 Blackboard로 전송되는 속성을 확인해야 할 수 있습니다. IdP 속성이 SAML 응답에서 암호화되지 않은 경우 Firefox 브라우저 SAML 추적기 부가기능 또는 Chrome SAML 메시지 디코더를 사용해 속성을 확인할 수 있습니다.

특성 매핑이 제대로 되지 않음

userName이 포함된 속성이 Bb GUI의 SAML 인증 설정 페이지에 있는 SAML 속성 매핑 섹션의 원격 사용자 ID 필드에 지정된 대로 제대로 매핑되지 않으면, SAML 인증으로 Bb에 로그인 시도 시 bb-서비스 로그에 해당 이벤트가 기록됩니다.

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

로그인 오류가 발생했습니다! 브라우저에서 Blackboard가 현재 단일 로그인을 사용하여 계정에 접속할 수 없습니다. 도움이 필요하면 관리자에게 문의하십시오.

브라우저에 표시된 로그온 오류 메시지 이미지에서, Blackboard는 현재 단일 사인온을 사용하여 계정에 로그인할 수 없음을 알 수 있습니다. 도움이 필요하면 관리자에게 문의하십시오.

인증 실패 항목이 Bb-서비스 로그에 표시됩니다.

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]
해상도

문제 해결 방법은 두 가지가 있습니다. 먼저 Blackboard GUI의 SAML 인증 설정 페이지에서 시스템에 계정이 없는 경우 생성 옵션을 선택합니다. IdP에서 릴리스한 특성의 값은 SAML 추적기나 디버그 로깅을 통해 암호화되지 않은 경우 볼 수 있습니다.

<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>

속성 이름을 클릭하고 원하는 AttributeValue이 있는 Blackboard GUI의 SAML 인증 설정 페이지에 있는 원격 사용자 ID에 매핑하세요.

선택된 데이터 원본이 호환되지 않습니다.

Blackboard GUI의 SAML 인증 설정 페이지 내 서비스 제공자 설정 > 호환되는 데이터 소스 섹션에서 사용자의 데이터 소스가 선택되지 않으면, SAML 인증으로 Blackboard에 로그인할 수 없습니다. SAML 인증으로 Blackboard에 로그인 시 Bb-서비스 로그에 다음 이벤트가 기록됩니다.

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

로그인 오류! 메시지가 브라우저에 표시되며 bb-서비스 로그에 인증 실패가 기록됩니다.

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]
해상도
  1. 사용자 이름을 가져와 로그인할 수 없습니다.

  2. Blackboard GUI에서 시스템 관리자 > 사용자로 이동해 사용자를 검색하세요.

  3. 사용자 데이터 소스 키를 복사합니다.

  4. 시스템 관리자 > 인증 > ‘공급자 이름’ > SAML 설정 > 호환되는 데이터 소스로 이동합니다.

  5. 이름 열에 있는 해당 데이터 원본 옆에 확인 표시를 한 후 제출을 클릭합니다.

"지정된 URL 형식이 올바르지 않습니다." 오류 메시지

OneLogin이 Blackboard의 SAML 인증 공급자로 설정되어 있을 때 로그인을 시도하면 OneLogin 자격 증명을 입력한 후 지정된 URL 형식이 올바르지 않습니다라는 오류 메시지가 나타날 수 있습니다.

bb-services-log에 다음 내용이 표시됩니다.

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> - 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
해상도
  1. Firefox 브라우저의 SAML 추적기를 활성화하고 로그인 문제를 재현하세요.

  2. 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. 줄 1에 응답이 있는 경우 Destination=이 수신자로만 설정되었는지 확인합니다.

  4. 클라이언트가 OneLogin IdP의 구성 섹션에 접근할 수 있도록 합니다.

  5. 수신자 필드가 비어 있는지 확인하세요.

  6. ACS(소비자) URL 값을 복사하여 받는 사람 필드에 붙여넣은 후 저장을 클릭합니다.

IdP/SP 문제 시나리오

  1. IdP 로그인 페이지로 리디렉션되기 전에 오류가 발생하면 IdP 메타데이터가 유효하지 않을 가능성이 있습니다.

  2. IdP 페이지에 로그인한 후 오류가 발생하면 다음과 같은 원인이 있을 수 있습니다.

    1. 속성 매핑이 SP와 IdP 간에 올바르지 않거나 IdP에서 유효한 원격 사용자 ID를 반환하지 않았습니다.

    2. SAML 응답이 SP에서 검증되지 않았습니다. 다음과 같은 원인으로 발생할 수 있습니다.

      • IdP는 유효한 인증 기관에서 발급받지 않은 인증서로 SAML 응답에 서명하고, SP의 키 저장소에 해당 인증서가 포함되어 있지 않습니다.

      • 시스템 클럭이 SP에서 올바르지 않습니다.

ADFS(Active Directory 페더레이션 서비스)

특성 이름은 Blackboard GUI의 SAML 인증 설정 페이지에 있는 SAML 속성 매핑 섹션에서 대소문자를 구분해야 합니다. 그러므로 원격 사용자 ID의 설정 페이지에서 속성 이름으로 sAMAccountName을 지정하고, IdP에서 실제 SAML POST를 할 때 AttributeStatement 안의 속성 이름에 해당 항목이 있다면:

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

사용자가 로그인할 수 없습니다. SAML 인증 설정 페이지에서 원격 사용자 ID 속성의 이름 값을 sAMAccountName에서 SamAccountName으로 변경해야 합니다.

"리소스를 찾을 수 없습니다" 또는 "사인 온 오류!" 경고가 표시됩니다.

이 섹션에서는 지정된 리소스를 찾을 수 없거나 접근 권한이 없습니다 또는 사인 온 오류! 메시지가 Blackboard GUI에 나타납니다.

문제 #1

ADFS 로그인 페이지에서 로그인 자격 증명을 입력한 후 Blackboard GUI로 리디렉션되면 다음 오류가 표시될 수 있습니다: 지정된 리소스를 찾을 수 없거나 접근 권한이 없습니다.

'지정된 리소스를 찾을 수 없음' 오류 메시지

‘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'

이 문제는 DispatcherServlet.java 코드의 noHandlerFound() 메서드에서 HTTP SSO 요청을 찾거나 매핑할 수 없을 때 발생합니다.

/**
 * No handler found -> 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);
 }
}
해상도

Blackboard GUI에 구성된 SP의 엔티티 ID가 올바르지 않아 일반적으로 발생하는 문제입니다. 이 문제는 시스템 관리자 > 인증 > SAML 인증 설정 > 서비스 공급자 설정으로 이동한 후 엔티티 ID를 업데이트함으로써 해결할 수 있습니다. ADFS의 경우 엔터티 ID의 기본 설정은 https://[Blackboard Server 호스트 이름]/auth-saml/saml/SSO로 되어 있습니다.

참고

학교에서 URL을 기본 https://school.blackboard.com에서 https://their.school.edu로 변경할 경우, SAML 인증 설정 페이지에 있는 Blackboard GUI의 엔터티 ID를 https://their.school.edu/auth-saml/saml/SSO로 업데이트해야 합니다.

문제 #2

ADFS 로그인 페이지에서 로그인 자격 증명을 입력한 후 Blackboard GUI로 리디렉션되면 다음 오류가 표시될 수 있습니다: 지정된 리소스를 찾을 수 없거나 접근 권한이 없습니다.

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'

'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

이 메시지는 Bb-서비스 로그에 표시됩니다.

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

이 문제는 기본적으로 ADFS가 AES-256을 사용해 전송하는 속성을 암호화하는데, Blackboard에서 사용하는 Java 런타임이 AES-256을 기본적으로 지원하지 않아 발생합니다.

해상도

ADFS 서버에서 PowerShell을 열고 Blackboard에 생성된 신뢰 당사자 특성이 암호화되지 않고 전송되도록 설정하는 것이 범용 확인 옵션입니다. 전체 의사소통이 SSL을 통해 이루어지기 때문에 인증 보안이 저하되지 않습니다. Firefox 브라우저의 SAML 추적기 애드온과 같은 디버깅 도구를 사용하면 속성을 확인할 수 있고, Blackboard 시스템을 재시작할 필요 없이 문제를 더 쉽게 해결할 수 있습니다. Blackboard에 대해 생성된 신뢰할 수 있는 당사자가 속성을 암호화되지 않은 상태로 보내도록 설정하려면 PowerShell을 열고 다음 명령을 실행하여 TargetName 을 (를) Trust Relationships > Relying Party Trusts 아래의 ADFS 관리 콘솔에 있는 Relying Party Trust 의 이름으로 바꿉니다.

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

이 변경을 한 후에는 Restart-Service ADFSSRV 명령을 사용하여 ADFS 서비스를 재시작해야 합니다.

문제 #3

ADFS 로그인 페이지에서 로그인 자격 증명을 입력한 후 Blackboard GUI로 리디렉션되면 다음 오류가 표시될 수 있습니다: 지정된 리소스를 찾을 수 없거나 접근 권한이 없습니다 또는 로그온 오류!

SAML 관련 이벤트가 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: org.apache.catalina.session.StandardSessionFacade@6708a718. 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

Bb-서비스 로그에서 발견된 유사한 SAML 예외:

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

이 문제는 ADFS 서버 및 Blackboard 앱 서버의 시간 드리프트가 기본값인 60초에 근접하거나 초과할 때 발생합니다.

해상도

이 문제를 해결할 수 있는 방법은 두 가지입니다.

  • Blackboard 앱 서버와 ADFS 서버의 시계를 수동으로 동기화하세요. 블랙보드의 경우 블랙보드 URL 끝에 /webapps/portal/healthCheck를 추가하면 웹 브라우저에서 서버의 현재 시간과 표준 시간대를 확인할 수 있습니다.

    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

    참고

    교육 기관은 위의 URL을 사용하여 Blackboard 시스템의 시간대 및 시계를 ADFS 서버의 것과 비교한 후 필요에 따라 ADFS 서버에서 해당 항목을 조정하여 Blackboard 사이트와 동기화할 수 있습니다.

문제 #4

로그인 자격 증명을 ADFS 로그인 페이지에 입력하고 Blackboard GUI로 리디렉션된 후 지정된 리소스를 찾을 수 없거나 접근 권한이 없습니다 혹은 로그인 오류! 메시지가 표시됩니다.

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]
해상도
  1. 관리자 패널로 이동하세요.

  2. 통합에서 빌딩 블록을 선택합니다.

  3. 설치된 도구 를 선택합니다.

  4. 목록에서 인증 공급자 - SAML을 찾습니다. 메뉴를 열고 설정을 선택하세요.

  5. SHA-256을 '서명 알고리즘 설정' 목록에서 선택하세요. 서명 알고리즘 유형을 선택한 후 SAML 빌딩 블록을 재시작하여 새 설정을 적용합니다.

  6. 제출을 선택하여 변경 사항을 저장합니다.

문제 #5

ADFS 로그인 페이지에서 로그인 자격 증명을 입력한 후 Blackboard GUI로 리디렉션되면 다음 오류가 표시될 수 있습니다: 지정된 리소스를 찾을 수 없거나 접근 권한이 없습니다 또는 로그온 오류!

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

SAML 예외에 설명된 바와 같이 응답 메시지의 Subject에서 NameID 요소가 누락되었습니다. 이 문제는 일반적으로 NameID가 교육기관의 ADFS IdP의 신뢰 당사자 신뢰에 대한 클레임 규칙에서 발신 클레임 유형으로 설정되지 않았거나 NameID에 대한 클레임 규칙이 교육기관의 ADFS IdP의 신뢰 당사자 신뢰에 대해 적절한 순서로 설정되지 않은 경우에 발생합니다. 이로 인해 응답 메시지의 Subject에 NameID 요소가 누락될 수 있습니다.

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>
NameID 요소가 있습니다.
<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>

참고

Firefox SAML 추적기 애드온을 사용하면 응답 메시지의 제목을 볼 수 있습니다.

해상도

이 문제를 해결할 수 있는 방법은 세 가지입니다.

  1. SAML B2 설정 가이드에 명시된 ADFS 단계를 정확히 준수했는지 확인하고 필요에 따라 ADFS IdP의 신뢰 당사자 트러스트에 대한 들어오는 클레임을 조정하세요.

    1. 클레임 규칙 편집을 선택하십시오.

    2. 규칙 추가를 선택합니다.

    3. 규칙 템플릿 선택 페이지에서 클레임 규칙 템플릿을 선택한 후 들어오는 클레임 변환을 클릭하고 다음을 누릅니다.

    4. 규칙 구성 페이지에서 클레임 규칙 이름 필드에 전자 메일을 이름 ID로 변환을 입력하세요.

    5. 수신 클레임 유형은 SamAccountName이어야 하며(사용자 이름을 NameID로 변환 규칙에서 처음 생성된 발신 클레임 유형과 일치해야 함).

    6. 발신 클레임 유형이름 ID로 되어 있습니다.

    7. 발신자 이름 ID 형식은 이메일이다.

    8. 모든 클레임 값이 통과되었는지 확인한 후 마침을 선택합니다.

    9. 확인을 눌러 규칙을 저장한 후 확인을 다시 눌러 특성 매핑을 완료하세요.

  2. 클레임 규칙의 순서에서 NameID 요소를 포함하는 규칙에 선택적인 규칙이 없는지 ADFS IdP에서 확인합니다.

  3. Blackboard가 사용자 정의 속성을 사용할 때에도 ADFS IdP가 NameID 값을 제공할 것으로 기대하기 때문에 신뢰 당사자 신뢰에 NameID 요소가 있는지 확인해야 합니다.

문제 #6

SAML 인증을 통해 Blackboard에 로그인한 경우, 사용자는 페이지 왼쪽에 있는 로그아웃 버튼을 클릭해 로그아웃을 시도하고 나서 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].

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

SAML 설정 페이지에서 단일 로그아웃 서비스 유형을 설정할 때 오류가 발생합니다.

해상도

Blackboard와 ADFS 서버에서 설정을 구성해야 합니다.

Adfs를 IdP로 사용할 때 게시 설정만 선택하고 ADFS 서버에서 Blackboard 인스턴스의 신뢰 당사자 트러스트에 대한 리디렉션 엔드포인트를 제거하세요.

  1. Blackboard에서 관리자 > 인증 > (제공자 이름) > SAML 설정 > 단일 로그아웃 서비스 유형으로 이동합니다.

  2. 게시를 선택한 다음 리디렉션 확인란의 선택을 해제합니다.

  3. Blackboard 인스턴스에 대한 신뢰 당사자 트러스트를 ADFS 서버에서 이동합니다.

  4. 속성 > 엔드포인트를 선택합니다. SAML 로그아웃 엔드포인트 두 개가 나열됩니다.

  5. 리디렉션 엔드포인트를 삭제합니다. 엔드포인트 제거를 선택하여 제거한 다음 적용 및 확인을 선택합니다.

Blackboard 및 ADFS 서버에서 위와 같은 내용을 변경한 후 SSO 세션 종료 로그아웃 버튼을 누르면 사용자가 올바르게 로그아웃됩니다.

문제 #7

로그인 자격 증명을 ADFS 로그인 페이지에 입력한 후 'Sign On 오류!' 메시지가 Blackboard로 리디렉션될 때 표시됩니다.

bb-services 로그에 다음과 같은 SAML 예외가 있습니다.

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
해상도

시스템 관리 > 빌딩 블록 > 인증 공급자 - SAML > 설정 > 인증서 재생성로 이동하여 SAML 암호화 인증서를 재생성할 수 있는 옵션이 있습니다. 로그인 오류! SP 메타데이터가 ADFS 서버의 Blackboard 사이트에 대한 신뢰 당사자 트러스트에 이미 업로드된 후 인증서 재생성 버튼을 선택하면 문제가 발생할 수 있습니다. 문제 해결을 위해서는:

  1. [SAML 공급자 이름] > 시스템 관리자 > 인증 > SAML 설정으로 이동합니다.

  2. 새 메타데이터 파일을 저장하려면 서비스 공급자 메타데이터 옆에 있는 생성을 클릭하세요.

  3. ADFS 서버에 접근하여 Blackboard 사이트의 신뢰 당사자 트러스트에 새 SP 메타데이터를 업로드하세요.

참고

B2 설정에서 새 인증서를 생성할 때는 SAML B2를 비활성으로 설정한 후 다시 활성으로 바꿔야 합니다. 그런 다음 공급자 설정으로 돌아가 IDP에서 가져올 새 메타데이터를 생성할 수 있습니다. 설정을 변경하지 않으면 새 메타데이터를 생성할 때 기존 인증서가 계속 포함될 수 있습니다. IDP가 업데이트되지 않으며, 다음에 Blackboard를 다시 시작할 때 새 인증서가 표시됩니다. SAML 인증이 이 불일치로 인해 중단됩니다.

페더레이션 메타데이터

ADFS(Active Directory Federation Services)의 경우, 일반적으로 https://[ADFS Server Hostname]/FederationMetadata/2007-06/FederationMetadata.xml에 있는 ADFS 페더레이션 메타데이터에 SAML 2.0과 호환되지 않는 요소가 포함되어 있어 Blackboard GUI의 SAML 인증 설정 페이지 ID 공급자 설정 섹션에 업로드하기 전에 이러한 요소를 삭제하기 위해 메타데이터를 편집해야 합니다. 메타데이터에 호환되지 않는 요소가 포함되어 있을 경우, Blackboard 로그인 페이지에서 SAML 로그인 링크를 선택하면 '엔터티 [엔티티] 및 역할 에 대한 메타데이터를 찾을 수 없습니다.'라는 메시지가 표시됩니다. 참고용 오류 ID는 [오류 ID]입니다.

Bb-서비스 로그의 오류 ID에 해당하는 Java 스택 추적에는 다음 내용이 포함되어 있습니다.

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> - 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]
해상도

ADFS 페더레이션의 기본 메타데이터 위치는 https://[ADFS server hostname]/FederationMetadata/2007-06/FederationMetadata.xml입니다.

  1. 텍스트 편집기에서 이 파일을 다운로드하여 열어보세요. <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> ... </X509Data></KeyInfo></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> 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. Blackboard GUI의 ID 제공자 설정 섹션에 있는 SAML 인증 설정 페이지에 업데이트된 메타데이터 XML 파일을 업로드하세요.

  3. SAML 인증 공급자를 '활성' 상태로 유지하며 SAML 인증 공급자 및 SAML B2를 비활성화하거나 사용 가능하게 전환합니다.

중요

교육 기관에서 Blackboard 사이트에서 SAML 인증을 테스트할 때 및 Blackboard 사이트에서 동일한 기본 ADFS IdP 메타데이터 XML 파일을 사용하는 여러 SAML 인증 공급자가 있을 경우, 다른 SAML 인증 공급자가 비활성화되어 있어도 ID 공급자 설정 섹션에 있는 SAML 인증 설정 페이지의 Blackboard GUI에 업데이트된 메타데이터 XML 파일을 업로드해야 합니다. 그런 다음 SAML 인증 공급자가 '활성' 상태일 때 SAML B2를 비활성화/사용 가능으로 전환하여 업데이트된 메타데이터 XML 파일이 시스템 전체에서 인식될 수 있도록 해야 합니다.

사용자 조회 방법 오류

로그인 자격 증명을 ADFS 로그인 페이지에 입력하면 사용자는 Blackboard GUI로 리디렉션되나 Blackboard에는 로그인되지 않습니다.

bb-services 로그에서 발견할 수 있는 유일한 SAML 인증 관련 이벤트는 다음과 같습니다.

2016-10-18 13:03:28 -0600 - userName is null or empty
해상도
  1. Blackboard 기본 내부 인증을 사용하여 관리자로 Blackboard에 로그인합니다.

  2. 시스템 관리자로 이동 > "SAML 인증 공급자 이름" 선택 > 편집

  3. 사용자 조회 방법배치 UID에서 사용자명으로 변경합니다.

SSO 세션 종료 추가 로그아웃 버튼

ADFS는 Blackboard GUI의 오른쪽 상단에 있는 로그아웃 버튼을 먼저 선택한 후 표시되는 모든 세션을 종료하시겠습니까? 페이지에 추가 SSO 세션 종료 로그아웃 버튼을 추가하려고 합니다.

이 작업은 IdP 메타데이터 파일에 SingleLogoutService를 추가함으로써 수행됩니다.

<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/"/>

선택적 SAML B2 IdP 구성에서 리디렉션 엔드포인트에 제공된 서명이 유효하지 않아 '모든 세션 종료?'에서 'SSO 세션 종료' 버튼을 추가로 선택하면 오류가 발생합니다. 페이지: '수신 SAML 메시지가 보안 유효성 검사에 실패했습니다.' 컨텍스트 발급자에 대한 요청이 단순 서명 유효성 검사에 실패했습니다. 참고용 오류 ID는 [오류 ID]입니다.

bb-services 로그에서 발견된 오류 ID에 해당하는 Java 스택 추적에는 다음과 같은 내용이 포함되어 있습니다.

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> - 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]
해상도
  1. ADFS 서버에 접근하여 Blackboard 인스턴스에 대한 신뢰 당사자 트러스트를 설정합니다.

  2. 속성 > 엔드포인트 탭을 선택합니다.

  3. 엔드포인트 탭에는 SAML 로그아웃 엔드포인트가 2개 있습니다.

  4. 리디렉션 엔드포인트를 삭제합니다.

  5. 엔드포인트 제거를 선택하여 제거한 다음 적용확인을 누르십시오.

리디렉션 엔드포인트를 제거한 후 SSO 세션 종료 버튼이 제대로 작동하여 사용자가 로그아웃되었습니다.

이벤트 뷰어를 사용해 적용 로그 확인하기

ADFS SAML 인증 문제를 해결하기 위해 교육기관은 ADFS 서버의 이벤트 뷰어에서 ADFS 적용 로그를 검토하여 추가적인 통찰력을 얻을 수 있습니다. ADFS 서버의 SAML 응답이 요청 거부됨 상태일 때 특히 필요합니다.

<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>

참고

SAML 응답은 Firefox 브라우저의 SAML 추적기 애드온을 사용해 볼 수 있습니다.

요청이 거부되었습니다 상태는 일반적으로 IdP(ADFS)가 응답을 이해하고, SP(Blackboard)가 제공한 결과를 처리하려 할 때 문제가 발생했음을 나타냅니다.

이벤트 뷰어에서 ADFS 적용 로그를 확인하려면 다음 단계를 따르세요.

  1. ADFS 서버에서 이벤트 뷰어를 여십시오.

  2. 보기 메뉴에서 분석 및 디버그 로그 표시를 선택합니다.

  3. 콘솔 트리에서 적용 및 서비스 로그 > AD FS 추적 > 디버그로 이동하세요.

Azure Active Directory

Azure AD는 Microsoft(MS)의 클라우드 기반 디렉터리 및 ID 관리 서비스입니다.

이메일 첫 부분을 보내는 중

교육 기관에서 Azure AD를 IdP로 활용하며 Blackboard 사용자 이름으로 Azure AD 이메일 사용자 이름의 앞부분만 적용하고자 할 때, 특수 ExtractMailPrefix() 함수를 활용해 이메일이나 사용자 계정 이름에서 도메인 접미사를 빼내도록 Azure AD IdP를 설정해 사용자 이름의 앞부분만을 전달하게 할 수 있습니다(예: "joesmith@example.com"이 아닌 "joesmith").

Blackboard원격 사용자 IDurn:oid:1.3.6.1.4.1.5923.1.1.1.6일 때 Azure IdP의 속성 설정은 다음과 같습니다.

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

따라서 전자메일 사용자 이름 joesmith@example.com 예시를 사용할 경우, Azure IdP에서 Blackboard로 SAML 어설션을 통해 다음과 같이 전달됩니다.

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

추가 정보가 필요하면 MS Azure 설명서 페이지에서 ExtractMailPrefix() 함수 사용법을 확인할 수 있습니다.

Azure AD IdP 인증서 업데이트

MS Azure AD 로그인 페이지에서 로그인 자격 증명을 입력한 후 Sign On 오류! Blackboard GUI로 리디렉션된 후 표시될 수 있습니다.

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

MS Azure AD IdP에서 인증서를 업데이트하지만 Blackboard SP에서 사용하는 메타데이터 XML이 새 인증서를 반영하도록 조정되지 않아 이런 문제가 발생합니다.

해상도
  • Blackboard GUI의 SAML 설정 페이지에서 인증 제공자에 대한 새로운 인증서가 포함된 새 메타데이터 XML 파일로 업데이트해야 합니다.

  • 그런 다음 SAML B2 및 인증 공급자를 비활성화/활성화하여 SAML 인증 공급자가 '활성' 상태일 때 새 인증서가 적용된 업데이트된 메타데이터를 사용할 수 있도록 해야 합니다.

  • Blackboard 사이트에 같은 기본 IdP 엔티티 ID를 가진 여러 인증 공급자가 동일한 기본 인증서를 공유할 경우, 해당 인증 공급자를 모두 업데이트해야 합니다.

IdP 단일 로그인 시작

사용자가 사용자 포털에 로그인한 후 Blackboard 사이트용 앱을 선택하면 새 브라우저 탭이 열리고 지정된 리소스를 찾을 수 없거나 접근할 수 있는 권한이 없습니다라는 메시지가 표시됩니다.

stdout-stderr.log에서 관련 SAML 이벤트를 확인하세요:

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: org.apache.catalina.session.StandardSessionFacade@58c53845. 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

SAML 인증 설정 페이지의 서비스 제공자 설정 섹션이 변경되어 사용자가 포털에서 Blackboard에 접속할 수 있게 자동 SSO 활성화 옵션을 선택해야 합니다. 이 기능을 활성화하면 ACS URL에도 별칭이 포함되도록 변경됩니다.

문서 오류

SAML 인증 공급자 로그인 페이지에서 로그인 자격 증명을 입력하고 Sign On 오류!가 Blackboard GUI로 리디렉션된 후 나타날 수 있습니다.

다음 DOMException과 Bb-서비스 로그에서 WRONG_DOCUMENT_ERR을 사용합니다.

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> - 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]

문제가 발생하는 이유는 다른 B2/Project에서 시스템 속성 javax.xml.parsers.DocumentBuilderFactory의 값을 org.apache.xerces.jaxp.DocumentBuilderFactoryImpl에서 com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl로 변경했기 때문입니다.

책임을 전가하지 않고 문제를 해결하는 중입니다.

임시 해결 방안은 수정사항이 릴리스될 때까지 다음과 같습니다.

  1. Bb 서비스를 각 노드에서 재시작합니다.

  2. IdP에서 SAML 응답 암호화 사용을 중지합니다. 전체 의사소통이 SSL을 통해 이루어지기 때문에 인증 보안이 저하되지 않습니다.

SAML을 공급자 주문에 추가하는 옵션이 없습니다.

교육기관이 SAML 인증을 구성할 때 시스템 관리자 > 빌딩 블록: 인증 > 제공자 순서로 이동하면 Blackboard GUI의 제공자 순서 섹션에서 SAML 인증 제공자를 추가할 수 있는 옵션이 없다는 것을 확인할 수 있습니다.

공급자 순서에 SAML 인증 공급자를 추가할 수 없는 이유는 CAS 및 SAML과 같은 리디렉션 유형의 공급자가 원격 인증 원본으로 인증을 전달하기 때문입니다. 이러한 인증서는 신뢰할 수 있는 인증 출처로 간주되며 자체 인증 실패를 처리하기 때문에 공급자 순서에는 나열되지 않습니다.

SAML 연결 테스트

Blackboard GUI의 인증 섹션에서는 SAML 공급자와의 연결을 테스트할 수 있는 옵션이 있습니다. 연결 테스트를 진행할 때 다음 사항을 확인합니다.

  • IdP 메타데이터 구문 분석 중

  • IdP에 연결

  • SAML 응답을 수신했습니다.

  • SAML 응답 구문 분석 중

  • 원격 사용자 ID가 일치합니다.

  • 하여 수업 자료를 확인하세요.

SAML 인증 공급자와의 연결을 테스트하려면 다음 단계를 따르십시오.

  1. Blackboard에 관리자 계정으로 로그인하세요.

  2. 시스템 관리자 > 구성 요소: 인증 > “SAML 공급자 이름” > 연결 테스트로 이동합니다.

  3. IdP 로그인 자격 증명을 요청하는 메시지가 표시되면 입력하세요.

Blackboard에서 SAML 디버그 로깅을 수동으로 활성화하는 대신 다양한 이유로 연결 테스트 기능을 사용할 수 있습니다.

연결 테스트 출력 페이지에 표시되는 ID 제공자 엔티티 ID 값은 사용자 인증 후 IdP에서 Blackboard로 SAML POST의 발급자 요소로부터 가져옵니다.

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

SAML 응답 섹션에 있는 연결 테스트 결과 페이지에 나타나는 SAML 속성 값은 사용자 인증 후 IdP가 Blackboard로 전송하는 SAML POST의 SubjectAttributeStatement 요소에서 추출합니다.

<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>

SAML 인증 공급자 및 IdP를 테스트용으로 만드는 방법

Centrify의 무료 SSO 인증 솔루션을 사용하여 ID 공급자(IdP)를 생성하는 단계는 아래와 같습니다.

해당 IdP를 Blackboard 서비스 제공자(SP)에서 SAML 인증 제공자로 구성할 수 있습니다.

Blackboard 서비스 제공업체
  1. Blackboard GUI에 관리자로 로그인한 후 ‘시스템 관리자’로 이동하여 ‘인증’을 선택합니다.

  2. SAML> '공급자 만들기'를 선택합니다.

  3. 입력하신 설정은 다음과 같습니다.

    • 이름 > SAML 또는 원하는 다른 이름.

    • 인증 공급자 > 비활성(현재).

    • 사용자 조회 방법 > 사용자 이름

    • 호스트 이름으로 제한 > 모든 호스트 이름에 이 공급자를 사용합니다.

    • 링크 텍스트 > SAML Centrify 로그인

  4. ‘저장 및 구성’을 선택합니다.

  5. 엔티티 ID 필드를 원하는 대로 설정하세요(단, 변경 시 업데이트된 서비스 공급자 메타데이터를 ID 공급자에게 제공해야 함). ACS URL을 복사하여 붙여넣기만 하면 됩니다.

  6. 서비스 공급자 메타데이터에서 생성을 선택하여 파일을 데스크톱에 저장하세요.

  7. 데이터 소스에서 'CENTRIFY'라는 새 데이터 소스를 생성하거나, 그렇지 않다면 'SYSTEM' 혹은 원하는 데이터 소스를 사용하는 것이 좋습니다.

  8. JIT 프로비저닝 활성화 옆에 있는 확인란을 선택하면 사용자가 존재하지 않을 경우 이 SAML 인증 공급자를 통한 로그인 시도 시 계정이 자동으로 생성됩니다. Blackboard에서 JIT 프로비저닝을 선택하지 않을 경우 사용자를 수동으로 생성해야 합니다.

  9. 호환되는 데이터 원본 목록에서 이 인증 공급자와 호환되는 데이터 원본을 선택해야 합니다.

  10. ID 공급자 유형에서 포인트 ID 공급자를 선택하세요.

  11. ID 공급자 메타데이터를 건너뛰고 현재 Centrify IdP 섹션에서 파일을 생성한 후 업로드하세요.

  12. SAML 속성 매핑 섹션에서는 원격 사용자 IDNameID를 사용하게 됩니다.

  13. 제출을 선택합니다.

Centrify ID 공급자
  1. Centrify 웹사이트로 이동한 후 지금 시작을 클릭하세요.

  2. 정보를 등록하고 지금 시작을 클릭합니다.

  3. 환영 이메일을 받게 되며, 이는 관리자 자격 증명을 포함하고 있습니다. https://클라우드.centrify.com에 로그인하는 데 사용하십시오.

  4. Centrify 확인 서비스에 오신 것을 환영합니다. 창에서 건너뛰기를 선택하세요.

  5. 탭을 페이지 상단에서 선택한 후 웹앱 추가 버튼을 클릭합니다.

  6. 사용자 지정 탭에서 아래로 스크롤하여 SAML에 대한 추가 버튼을 선택하세요. 를 선택하십시오.

  7. Web Apps 추가 창 하단에서 닫기를 선택하세요.

  8. 탭으로 이동하세요. 적용 설정 섹션에서 SP 메타데이터 업로드 버튼을 클릭하여 Blackboard SP 섹션의 6단계에서 생성된 파일을 업로드하세요.

  9. 어설션 소비자 서비스 URL은 SP 메타데이터를 업로드한 후 자동으로 채워져야 한다.

  10. Encrypt Assertion(어설션을 암호화) 선택을 취소합니다. IdP에서 해제되어 Blackboard로 전송되는 속성은 Firefox 브라우저의 SAML 추적기 애드온이나 Chrome SAML 메시지 디코더를 사용하여 확인할 수 있습니다. 전체 의사소통이 SSL을 통해 이루어지기 때문에 인증의 보안이 저하되지 않습니다.

  11. 스크롤하여 ID 공급자 SAML 메타데이터를 선택하세요. 데스크탑에 파일을 저장합니다.

  12. 저장을 선택한 후 다음 섹션으로 이동합니다.

  13. 설명 섹션의 이름을 입력하세요. 저장을 선택한 후 다음 섹션으로 이동합니다.

  14. 사용자 액세스 섹션에서 모든 사람시스템 관리자를 선택합니다. 저장을 선택합니다.

  15. 정책 섹션에서는 아무 것도 선택하지 마십시오.

  16. 계정 매핑 섹션에서는 디렉터리 서비스 필드 이름에 userprincipalname이 입력되었는지 확인하세요.

  17. 고급 섹션에서는 SAML 어설션을 생성하는 데 사용되는 애플리케이션 스크립트 맨 아래에 다음과 같은 줄을 추가하세요.

    전체 스크립트는 다음과 같습니다.

    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"));

    Centrify IdP는 SAML POST에서 사용자 ID를 활용해 AttributeStatement를 릴리스할 수 있다.

    예:

    <AttributeStatement>
        <Attribute Name="NameID"
                   NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
                   >
            <AttributeValue>luke.skywalker@blackboard.com.47</AttributeValue>
        </Attribute>
    </AttributeStatement>
  18. 저장을 선택합니다.

  19. 나머지 섹션(앱 게이트웨이, 변경 로그워크플로)를 변경할 필요가 없습니다.

  20. 탭에서 SAML 앱이 자동으로 배포되었는지 확인하세요.

  21. 사용자 탭에서 사용자 추가를 선택한 후 계정 정보를 입력하고 사용자 만들기를 클릭합니다.

  22. Blackboard GUI에서 관리자로 다시 로그인하여 시스템 관리자 > 인증 > SAML 인증 제공자 이름 > SAML 설정 > ID 제공자 설정으로 이동한 다음, 13단계에서 데스크톱에 저장된 IdP 메타데이터 파일을 업로드하고 제출을 클릭합니다.

생성된 Centrify IdP 사용자는 이제 로그인 페이지에서 해당 인증 공급자를 선택해 SAML을 통해 Blackboard에 로그인할 수 있고, 모든 세션 종료?에서 추가된 SSO 세션 종료 로그아웃 버튼을 사용해 Blackboard에서 로그아웃할 수 있습니다. 이는 Blackboard 오른쪽 상단에 있는 로그아웃 버튼을 선택한 후 나타나는 페이지입니다.

SSO 세션 종료 시 로그아웃 페이지 텍스트 변경

교육 기관은 SSO 세션 종료 시 로그아웃 페이지의 텍스트를 변경할 수 있는지 문의할 수 있습니다. SSO 세션 종료 로그아웃 페이지의 텍스트를 변경하기 위해 언어 팩을 편집할 수 있습니다.

  1. 언어 팩 파일을 열어 주세요.

  2. auth-provider-saml/src/main/webapp/WEB-INF/bundles/bb-manifest-ko_KR.properties로 이동하세요.

  3. 메시지 키를 업데이트하는 중입니다.

    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

사용자를 IdP 로그인 페이지로 리디렉션합니다.

Blackboard 표준 로그인 페이지에는 기본 Learn 내부 인증 제공자를 위한 사용자 이름 및 비밀번호 필드가 있습니다. SAML 인증을 활성화하면 "다음을 사용하여 로그인..."이라는 작은 SAML 링크가 페이지 하단에 나타나 사용자가 Blackboard 로그인 페이지에 접근할 때 IdP의 인증 서버로 자동으로 리디렉션됩니다.

이를 달성하기 위한 한 가지 방법은 시스템 관리 > 인증으로 이동해 기본 Learn 내부 인증을 비활성화하는 것입니다. 이로 인해 로그인 페이지가 더 이상 표시되지 않고 사용자가 즉시 SAML 로그인으로 리디렉션되게 됩니다. 이 옵션의 문제점은 기본 로그인 URL을 재정의하며 SAML을 사용하지 않는 사용자가 로그인할 수 없게 만든다는 것입니다.

사용자 지정 로그인 페이지를 사용하면 이 문제를 방지하고 거의 동일한 결과를 얻을 수 있습니다. 사용자는 SAML 인증 공급자의 IdP 로그인 페이지로 리디렉션되지만 기본 로그인 링크도 이용 가능합니다.

  1. 기본 Learn 내부 인증이 활성화되어 있는지 확인합니다.

  2. 공급자 리디렉션 위치는 기본 로그인 페이지에서 복사하세요(예: 로그인에 다음 사용...). SAML입니다. 마우스 오른쪽 버튼으로 링크를 클릭한 후 링크 위치 복사를 선택하세요.

  3. 시스템 관리자 > 커뮤니티 > 브랜드 및 테마로 이동 > 로그인 페이지 사용자 지정.

  4. 기본 로그인 페이지 옆에 있는 다운로드를 클릭하여 기본 로그인 JSP 파일을 다운로드하세요.

  5. JSP 파일을 텍스트 편집기로 엽니다. 로그인 JSP 파일에 다음 샘플 HTML을 추가하고, URL 텍스트를 2단계에서 복사한 URL로 변경하세요.

    <!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> if you are not automatically redirected.
    </BODY>
    </HTML>
  6. Learn에서 로그인 페이지 사용자 지정으로 돌아갑니다. 사용자 지정 페이지 사용을 선택하고 업데이트된 로그인 JSP 파일을 업로드하세요.

  7. 로그인 페이지 사용자 지정에서 미리보기를 선택한 후 리디렉션이 제대로 작동하는지 확인합니다.

사용자가 기본 URL로 이동하면 SAML 인증 공급자의 로그인 페이지로 리디렉션됩니다. 관리자는 기본 로그인 페이지(/webapps/login/?action=default_login 또는 /webapps/login/login.jsp)를 통해 Learn 내부 인증으로 로그인할 수 있습니다.