[오리지널] CAS 가 정리 한 단일 탈퇴 편 (CAS 가 단일 탈퇴 를 실 현 했 습 니까?)


         CAS 총결산 의 단점 퇴출 편
 
CAS 는 과연 단일 탈퇴 를 이 루 었 을 까?본인 은 JA - SIG CAS v 3.3 과 JA - SIG CAS - CLIENT 3.1.9 의 소스 코드 를 읽 어 보 니 겉 으로 는 단일 탈퇴 가 이 루어 진 것 같 지만 실제로는 실현 되 지 않 았 다.
 
CAS 의 logout 인터페이스의 실현 을 다음 과 같이 정리 합 니 다.
 
우선 CAS logout 기능 의 시퀀스 를 살 펴 보 겠 습 니 다.
                                                 CAS logout 기능 의 시퀀스 맵
 
 
그림 에서 보 듯 이 CAS logout 기능 은 두 단계 가 있 는데 하 나 는 TGT 대상 중 각 Service 를 호출 하 는 logoutOFService 방법 이 고, 다른 하 나 는 캐 시 에서 TGT 대상 을 제거 하 는 것 이다.
 
CAS 의 AbstractWebApplicationService 에서 logoutOFService 방법의 실현 을 살 펴 보 겠 습 니 다.
 
public synchronized boolean logOutOfService(final String sessionIdentifier) {
        if (this.loggedOutAlready) {
            return true;
        }

        LOG.debug("Sending logout request for: " + getId());

        final String logoutRequest = "<samlp:LogoutRequest xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" ID=\""
            + GENERATOR.getNewTicketId("LR")
            + "\" Version=\"2.0\" IssueInstant=\"" + SamlUtils.getCurrentDateAndTime()
            + "\"><saml:NameID xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">@NOT_USED@</saml:NameID><samlp:SessionIndex>"
            + sessionIdentifier + "</samlp:SessionIndex></samlp:LogoutRequest>";
        
        this.loggedOutAlready = true;
        
        if (this.httpClient != null) {
            return this.httpClient.sendMessageToEndPoint(getOriginalUrl(), logoutRequest);
        }
        
        return false;
    }

 
 
    또한 HttpClient 클래스 에서 sendmessage ToEndPoint 방법 은 다음 과 같 습 니 다.
 
public boolean sendMessageToEndPoint(final String url, final String message) {
        HttpURLConnection connection = null;
        BufferedReader in = null;
        try {
            if (log.isDebugEnabled()) {
                log.debug("Attempting to access " + url);
            }
            final URL logoutUrl = new URL(url);
            final String output = "logoutRequest=" + URLEncoder.encode(message, "UTF-8");

            connection = (HttpURLConnection) logoutUrl.openConnection();
            connection.setDoInput(true);
            connection.setDoOutput(true);
            connection.setReadTimeout(this.readTimeout);
            connection.setConnectTimeout(this.connectionTimeout);
            connection.setRequestProperty("Content-Length", ""
                + Integer.toString(output.getBytes().length));
            connection.setRequestProperty("Content-Type",
                "application/x-www-form-urlencoded");
           
            final DataOutputStream printout = new DataOutputStream(connection
                .getOutputStream());
            printout.writeBytes(output);
            printout.flush();
            printout.close();

            in = new BufferedReader(new InputStreamReader(connection
                .getInputStream()));

            while (in.readLine() != null) {
                // nothing to do
            }
            
            if (log.isDebugEnabled()) {
                log.debug("Finished sending message to" + url);
            }
            
            return true;
        } catch (final Exception e) {
            log.error(e,e);
            return false;
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (final IOException e) {
                    // can't do anything
                }
            }
            if (connection != null) {
                connection.disconnect();
            }
        }
    }

 
 
코드 를 읽 어 보면 logOutOfService 방법 은 serivce 의 origin Url 인 터 페 이 스 를 호출 하고 HttpURLConnection 방식 으로 종료 요청 을 service 에 보 내 는 것 으로 HttpURLConnection 에 requestMethod 를 설정 하지 않 았 음 을 주의 하 므 로 기본 GET 방법 을 사용 합 니 다.session Identifier 의 값 은 ST 의 값 입 니 다.서 비 스 는 response 에서 logoutRequest 매개 변수 에 있 는 session Identifier 의 값 을 분석 한 다음 session Identifier 표지 의 session kill 을 없 애 면 됩 니 다.이때 우 리 는 원리 적 으로 한 점 에서 물 러 날 수 있다 는 것 을 발견 했다.
 
클 라 이언 트 의 실현 을 살 펴 보면 클 라 이언 트 와 단일 지점 종료 와 관련 된 종 류 는 다음 과 같 습 니 다.
  • SingleSignOutFilter: logoutRequest 인 자 를 분석 하 는 데 사 용 됩 니 다.
  • Session MappingStorage: 하나의 인터페이스 로 Session 메모리 의 방법 을 정의 합 니 다. 
  • HashMapBacked Session MappingStorage: Session 메모리 의 실현 류 는 2 개의 Map 을 정의 하여 Session 을 저장 합 니 다.

  • MANAGED_SESSIONS: key 는 ST 의 값 이 고 value 는 session 입 니 다.
    ID_TO_SESSION_KEY_MAPPING: key 는 sessionId 이 고 value 는 ST 의 값 입 니 다.
     
  • SingleSignOutHttp Session Listener: 이 Listener 는 session destroy 의 사건 을 감청 한 후 session Id 로 상기 IDTO_SESSION_KEY_MAPPING 에서 ST 의 값 을 추출 하고 ST 의 값 에 따라 MANAGEDSESSIONS 에서 session 을 꺼 내 면 invalidate 방법 을 실행 할 수 있 습 니 다.

  •  
    싱글 Sign OutFilter 의 doFilter 방법 을 살 펴 보 겠 습 니 다.
     
    public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException {
            final HttpServletRequest request = (HttpServletRequest) servletRequest;
           
            if ("POST".equals(request.getMethod())) {
                final String logoutRequest = CommonUtils.safeGetParameter(request, "logoutRequest");
    
                if (CommonUtils.isNotBlank(logoutRequest)) {
    
                    if (log.isTraceEnabled()) {
                        log.trace ("Logout request=[" + logoutRequest + "]");
                    }
                    
                    final String sessionIdentifier = XmlUtils.getTextForElement(logoutRequest, "SessionIndex");
    
                    if (CommonUtils.isNotBlank(sessionIdentifier)) {
                    	final HttpSession session = SESSION_MAPPING_STORAGE.removeSessionByMappingId(sessionIdentifier);
    
                    	if (session != null) {
                            String sessionID = session.getId();
    
                            if (log.isDebugEnabled()) {
                                log.debug ("Invalidating session [" + sessionID + "] for ST [" + sessionIdentifier + "]");
                            }
                            
                            try {
                            	session.invalidate();
                            } catch (final IllegalStateException e) {
                            	log.debug(e,e);
                            }
                    	}
                      return;
                    }
                }
            } else {
            	final String artifact = CommonUtils.safeGetParameter(request, this.artifactParameterName);
                final HttpSession session = request.getSession(false);
    
                if (session != null) {
                    if (log.isDebugEnabled()) {
                        log.debug("Storing session identifier for " + session.getId());
                    }
                    if (CommonUtils.isNotBlank(artifact)) {
                        try {
                            SESSION_MAPPING_STORAGE.removeBySessionById(session.getId());
                        } catch (final Exception e) {
                            // ignore if the session is already marked as invalid.  Nothing we can do!
                        }
                        SESSION_MAPPING_STORAGE.addSessionById(artifact, session);
                    }
                } else {
                    log.debug("No Session Found, so ignoring.");
                }
            }
    
            filterChain.doFilter(servletRequest, servletResponse);
    }
    
    
     
           이상 하 게 도 이 방법 은 request 의 방법 을 먼저 판단 하 였 습 니 다. POST 라면 session. invalidate 방법 을 실행 하여 단일 지점 에서 종료 합 니 다. GET 라면 ticket 매개 변수 가 존재 하 는 상황 에서 session 을 Session MappingStorage 에 저장 하고 session. invalidate 방법 을 영원히 실행 하지 않 으 며 단일 지점 에서 종료 할 수 없습니다.CAS 의 logoutRequest 요청 은 GET 방법 으로 보 내 왔 기 때문에 단일 로그 인 기능 이 구현 되 지 않 았 습 니 다.
     
           저 는 SingleSignOutFilter 의 doFilter 방법 에 대해 다시 썼 습 니 다. 코드 는 다음 과 같 습 니 다.경험 증, 확실히 단일 탈퇴 를 실현 하 였 습 니 다.
     
    public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException {
            
        	final HttpServletRequest request = (HttpServletRequest) servletRequest;
            final String logoutRequest = CommonUtils.safeGetParameter(request, "logoutRequest");
            Enumeration ff = request.getParameterNames();
            String a = request.getQueryString();
            if (CommonUtils.isNotBlank(logoutRequest)) {
            	 final String sessionIdentifier = XmlUtils.getTextForElement(logoutRequest, "SessionIndex");
    
                 if (CommonUtils.isNotBlank(sessionIdentifier)) {
                 	final HttpSession session = SESSION_MAPPING_STORAGE.removeSessionByMappingId(sessionIdentifier);
    
                 	if (session != null) {
                         String sessionID = session.getId();                   
                         try {
                         	session.invalidate();
                         } catch (final IllegalStateException e) {
                         	
                         }
                 	}
                 }
             }
            
            else{
            	final String artifact = CommonUtils.safeGetParameter(request, this.artifactParameterName);
                final HttpSession session = request.getSession(false);
                
                if (CommonUtils.isNotBlank(artifact) && session!=null) {
                    try {
                        SESSION_MAPPING_STORAGE.removeBySessionById(session.getId());
                    } catch (final Exception e) {
                        
                    }
                    SESSION_MAPPING_STORAGE.addSessionById(artifact, session);
                }
            }
    
            filterChain.doFilter(servletRequest, servletResponse);
        }
    

     
          또한 주의해 야 할 것 은 클 라 이언 트 가 세 개의 Filter: AuthenticationFilter, ServiceValidationFilter, SingleSignOutFilter 를 배 치 했 기 때문에 세 개의 Filter 순 서 는 주의해 야 합 니 다. 제 순 서 는 AuthenticationFilter, ServiceValidationFilter, SingleSignOutFilter 입 니 다. 처음에는 안 됩 니 다. 종료 기능 을 실행 할 때CAS 서버 는 HttpURLConnection 으로 클 라 이언 트 를 방문 합 니 다. sessionId 를 대신 하지 않 았 기 때문에 AuthenticationFilter 에서 CAS 로 리 디 렉 션 되 었 습 니 다. SingleSign OutFilter 에 도착 하지 못 했 습 니 다. 제 가 변경 한 것 은 AuthenticationFilter 의 redirectUrl 입 니 다. 그 다음 에 session ID 의 값 을 추 가 했 습 니 다. 형식 은 "; jsessionid =" 입 니 다.이렇게 CAS 에서 서비스 파 라미 터 를 분석 하여 웹 응용 프로그램 서 비 스 를 생 성 할 때 orginUrl 에 sessionId 가 있 습 니 다.
     
    이렇게 해서 단일 탈퇴 가 실현 되 었 지만 CAS 의 이러한 Filter 방식 은 너무 번 거 로 운 것 같 습 니 다. 고객 애플 리 케 이 션 에 콜 백 url 을 제공 하 는 것 이 좋 습 니 다. CAS 는 이 콜 백 url 을 직접 호출 하여 종료 하 는 것 이 좋 습 니 다. 그러나 이렇게 하면 CAS 에 대한 변경 이 매우 큽 니 다.
     
         본인 블 로그:http://zhenkm0507.iteye.com

    좋은 웹페이지 즐겨찾기