CAS單點登陸原理以及debug跟蹤登陸流程

CAS 原理和協議

基礎模式

基礎模式 SSO 訪問流程主要有如下步驟:html

1. 訪問服務: SSO 客戶端發送請求訪問應用系統提供的服務資源。web

2. 定向認證: SSO 客戶端會重定向用戶請求到 SSO 服務器。spring

3. 用戶認證:用戶身份認證。express

4. 發放票據: SSO 服務器會產生一個隨機的 Service Ticket 。瀏覽器

5. 驗證票據: SSO 服務器驗證票據 Service Ticket 的合法性,驗證經過後,容許客戶端訪問服務。緩存

6. 傳輸用戶信息: SSO 服務器驗證票據經過後,傳輸用戶認證結果信息給客戶端。安全

下面是 CAS 最基本的協議過程:服務器

 

基礎協議圖cookie

 

如 上圖: CAS Client 與受保護的客戶端應用部署在一塊兒,以 Filter 方式保護 Web 應用的受保護資源,過濾從客戶端過來的每個 Web 請求,同 時, CAS Client 會分析 HTTP 請求中是否包含請求 Service Ticket( ST 上圖中的 Ticket) ,若是沒有,則說明該用戶是沒有通過認證的;因而 CAS Client 會重定向用戶請求到 CAS Server ( Step 2 ),並傳遞 Service (要訪問的目的資源地址)。 Step 3 是用戶認證過程,若是用戶提供了正確的 Credentials , CAS Server 隨機產生一個至關長度、惟1、不可僞造的 Service Ticket ,並緩存以待未來驗證,而且重定向用戶到 Service 所在地址(附帶剛纔產生的 Service Ticket ) , 併爲客戶端瀏覽器設置一個 Ticket Granted Cookie ( TGC ) ; CAS Client 在拿到 Service 和新產生的 Ticket 事後,在 Step 5 和 Step6 中與 CAS Server 進行身份覈實,以確保 Service Ticket 的合法性。網絡

在該協議中,全部與 CAS Server 的交互均採用 SSL 協議,以確保 ST 和 TGC 的安全性。協議工做過程當中會有 2 次重定向 的過程。可是 CAS Client 與 CAS Server 之間進行 Ticket 驗證的過程對於用戶是透明的(使用 HttpsURLConnection )。

   CAS 請求認證時序圖以下:

 

  

CAS 如何實現 SSO

當用戶訪問另外一個應用的服務再次被重定向到 CAS Server 的時候, CAS Server 會主動獲到這個 TGC cookie ,而後作下面的事情:

1) 若是 User 持有 TGC 且其還沒失效,那麼就走基礎協議圖的 Step4 ,達到了 SSO 的效果;

2) 若是 TGC 失效,那麼用戶仍是要從新認證 ( 走基礎協議圖的 Step3) 。

 

以上是在網絡上找到的相關描述,詳細請參考:

http://www.open-open.com/lib/view/open1432381488005.html

 

可是光看文字描述仍是不夠清晰,不如Debug來看一下。

----------------------------------------------------------------------------------

前提:

有兩個web應用

app1.testcas.com

app2.testcas.com

Cas認證中心

demo.testcas.com

 

第一步:訪問目標應用app1

若是想要訪問app1的網頁

例如:app1.testcas.com/user/doWelcome

這時,該請求將會被事先配置好的CAS Filter所攔截

app1的web.xml配置以下:

 

<filter>
        <filter-name>CAS Filter</filter-name>
        <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
        <init-param>
            <param-name>casServerLoginUrl</param-name>
            <param-value>https://demo.testcas.com/cas/login</param-value>
        </init-param>
        <init-param>
            <param-name>serverName</param-name>
            <param-value>http://app1.testcas.com</param-value>
        </init-param>
</filter>
<filter-mapping>
    <filter-name>CAS Filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

 

斷點進入該類的doFilter方法

org.jasig.cas.client.authentication.AuthenticationFilter > doFilter
public final void doFilter(final ServletRequest servletRequest,
            final ServletResponse servletResponse, final FilterChain filterChain)
            throws IOException, ServletException
    {
        final HttpServletRequest request = (HttpServletRequest) servletRequest;
        final HttpServletResponse response = (HttpServletResponse) servletResponse;
        final HttpSession session = request.getSession(false);
        
        // 該變量爲判斷用戶是否已經登陸的標記,在用戶成功登陸後會被設置
        final Assertion assertion = session != null ? (Assertion) session.getAttribute(CONST_CAS_ASSERTION)
                : null;
        
        // 判斷是否登陸過,若是已經登陸過,進入if而且退出
        if (assertion != null)
        {
            filterChain.doFilter(request, response);
            return;
        }
        // 若是沒有登陸過,繼續後續處理
        
        // 構造訪問的URL,若是該Url包含tikicet參數,則去除參數
        final String serviceUrl = constructServiceUrl(request, response);
        // 若是ticket存在,則獲取URL後面的參數ticket
        final String ticket = CommonUtils.safeGetParameter(request,
                getArtifactParameterName());
        // 研究中
        final boolean wasGatewayed = this.gatewayStorage.hasGatewayedAlready(request,
                serviceUrl);
        
        // 若是ticket存在
        if (CommonUtils.isNotBlank(ticket) || wasGatewayed)
        {
            filterChain.doFilter(request, response);
            return;
        }
        
        final String modifiedServiceUrl;
        
        log.debug("no ticket and no assertion found");
        if (this.gateway)
        {
            log.debug("setting gateway attribute in session");
            modifiedServiceUrl = this.gatewayStorage.storeGatewayInformation(request,
                    serviceUrl);
        }
        else
        {
            modifiedServiceUrl = serviceUrl;
        }
        
        if (log.isDebugEnabled())
        {
            log.debug("Constructed service url: " + modifiedServiceUrl);
        }
        
        // 若是用戶沒有登陸過,那麼構造重定向的URL
        final String urlToRedirectTo = CommonUtils.constructRedirectUrl(this.casServerLoginUrl,
                getServiceParameterName(),
                modifiedServiceUrl,
                this.renew,
                this.gateway);
        
        if (log.isDebugEnabled())
        {
            log.debug("redirecting to \"" + urlToRedirectTo + "\"");
        }
        
        // 重定向跳轉到Cas認證中心
        response.sendRedirect(urlToRedirectTo);
    }
 

第二步:請求被重定向到CAS服務器端後

根據CAS_Server端的login-webflow.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/webflow
                          http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">

    <var name="credentials"
        class="org.jasig.cas.authentication.principal.UsernamePasswordCredentials" />
    <on-start>
        <evaluate expression="initialFlowSetupAction" />
    </on-start>

    <decision-state id="ticketGrantingTicketExistsCheck">
        <if test="flowScope.ticketGrantingTicketId neq null" then="hasServiceCheck"
            else="gatewayRequestCheck" />
    </decision-state>

    <decision-state id="gatewayRequestCheck">
        <if
            test="externalContext.requestParameterMap['gateway'] neq '' &amp;&amp; externalContext.requestParameterMap['gateway'] neq null &amp;&amp; flowScope.service neq null"
            then="gatewayServicesManagementCheck" else="generateLoginTicket" />
    </decision-state>

    <decision-state id="hasServiceCheck">
        <if test="flowScope.service != null" then="renewRequestCheck" else="viewGenericLoginSuccess" />
    </decision-state>

    <decision-state id="renewRequestCheck">
        <if
            test="externalContext.requestParameterMap['renew'] neq '' &amp;&amp; externalContext.requestParameterMap['renew'] neq null"
            then="generateLoginTicket" else="generateServiceTicket" />
    </decision-state>

    <!-- The "warn" action makes the determination of whether to redirect directly 
        to the requested service or display the "confirmation" page to go back to 
        the server. -->
    <decision-state id="warn">
        <if test="flowScope.warnCookieValue" then="showWarningView" else="redirect" />
    </decision-state>

    <!-- <action-state id="startAuthenticate"> <action bean="x509Check" /> <transition 
        on="success" to="sendTicketGrantingTicket" /> <transition on="warn" to="warn" 
        /> <transition on="error" to="generateLoginTicket" /> </action-state> -->

    <action-state id="generateLoginTicket">
        <evaluate expression="generateLoginTicketAction.generate(flowRequestContext)" />
        <transition on="success" to="viewLoginForm" />
    </action-state>

    <view-state id="viewLoginForm" view="casMyLoginView" model="credentials">
        <binder>
            <binding property="username" />
            <binding property="password" />
            <binding property="imgverifycode" />
        </binder>
        <on-entry>
            <set name="viewScope.commandName" value="'credentials'" />
        </on-entry>
        <transition on="submit" bind="true" validate="true" to="imgverifycodeValidate">
            <evaluate
                expression="authenticationViaFormAction.doBind(flowRequestContext, flowScope.credentials)" />
        </transition>
    </view-state>

    <action-state id="imgverifycodeValidate">
        <evaluate
            expression="authenticationViaFormAction.validatorCode(flowRequestContext, flowScope.credentials, messageContext)" />
        <transition on="error" to="generateLoginTicket" />
        <transition on="success" to="realSubmit" />
    </action-state>

    <action-state id="realSubmit">
        <evaluate
            expression="authenticationViaFormAction.submit(flowRequestContext, flowScope.credentials, messageContext)" />
        <transition on="warn" to="warn" />
        <transition on="success" to="sendTicketGrantingTicket" />
        <transition on="error" to="generateLoginTicket" />
    </action-state>

    <action-state id="sendTicketGrantingTicket">
        <evaluate expression="sendTicketGrantingTicketAction" />
        <transition to="serviceCheck" />
    </action-state>

    <decision-state id="serviceCheck">
        <if test="flowScope.service neq null" then="generateServiceTicket"
            else="viewGenericLoginSuccess" />
    </decision-state>

    <action-state id="generateServiceTicket">
        <evaluate expression="generateServiceTicketAction" />
        <transition on="success" to="warn" />
        <transition on="error" to="generateLoginTicket" />
        <transition on="gateway" to="gatewayServicesManagementCheck" />
    </action-state>

    <action-state id="gatewayServicesManagementCheck">
        <evaluate expression="gatewayServicesManagementCheck" />
        <transition on="success" to="redirect" />
    </action-state>

    <action-state id="redirect">
        <evaluate
            expression="flowScope.service.getResponse(requestScope.serviceTicketId)"
            result-type="org.jasig.cas.authentication.principal.Response" result="requestScope.response" />
        <transition to="postRedirectDecision" />
    </action-state>

    <decision-state id="postRedirectDecision">
        <if test="requestScope.response.responseType.name() eq 'POST'"
            then="postView" else="redirectView" />
    </decision-state>

    <!-- the "viewGenericLogin" is the end state for when a user attempts to 
        login without coming directly from a service. They have only initialized 
        their single-sign on session. -->
    <end-state id="viewGenericLoginSuccess" view="casLoginGenericSuccessView" />

    <!-- The "showWarningView" end state is the end state for when the user 
        has requested privacy settings (to be "warned") to be turned on. It delegates 
        to a view defines in default_views.properties that display the "Please click 
        here to go to the service." message. -->
    <end-state id="showWarningView" view="casLoginConfirmView" />

    <end-state id="postView" view="postResponseView">
        <on-entry>
            <set name="requestScope.parameters" value="requestScope.response.attributes" />
            <set name="requestScope.originalUrl" value="flowScope.service.id" />
        </on-entry>
    </end-state>

    <!-- The "redirect" end state allows CAS to properly end the workflow while 
        still redirecting the user back to the service required. -->
    <end-state id="redirectView" view="externalRedirect:${requestScope.response.url}" />

    <end-state id="viewServiceErrorView" view="viewServiceErrorView" />

    <end-state id="viewServiceSsoErrorView" view="viewServiceSsoErrorView" />

    <global-transitions>
        <transition to="viewServiceErrorView"
            on-exception="org.springframework.webflow.execution.repository.NoSuchFlowExecutionException" />
        <transition to="viewServiceSsoErrorView"
            on-exception="org.jasig.cas.services.UnauthorizedSsoServiceException" />
        <transition to="viewServiceErrorView"
            on-exception="org.jasig.cas.services.UnauthorizedServiceException" />
    </global-transitions>
</flow>
View Code

 首先會進入initialFlowSetupAction

 protected Event doExecute(final RequestContext context) throws Exception {
        final HttpServletRequest request = WebUtils.getHttpServletRequest(context);
        if (!this.pathPopulated) {
            final String contextPath = context.getExternalContext().getContextPath();
            final String cookiePath = StringUtils.hasText(contextPath) ? contextPath + "/" : "/";
            logger.info("Setting path for cookies to: "
                + cookiePath);
            this.warnCookieGenerator.setCookiePath(cookiePath);
            this.ticketGrantingTicketCookieGenerator.setCookiePath(cookiePath);
            this.pathPopulated = true;
        }

        // 獲取客戶端的名爲CASTGC的cookie
        context.getFlowScope().put(
            "ticketGrantingTicketId", this.ticketGrantingTicketCookieGenerator.retrieveCookieValue(request));
        context.getFlowScope().put(
            "warnCookieValue",
            Boolean.valueOf(this.warnCookieGenerator.retrieveCookieValue(request)));

        // 獲取要訪問的服務
        final Service service = WebUtils.getService(this.argumentExtractors,
            context);

        if (service != null && logger.isDebugEnabled()) {
            logger.debug("Placing service in FlowScope: " + service.getId());
        }

        context.getFlowScope().put("service", service);

        return result("success");
    }

 

 

以後根據webflow流程,主要有兩大分歧

若是TGC而且service存在,則發放ST(service ticket)並重定向回到客戶端應用

若是首次訪問,TGC不存在,則跳轉到CAS-server的登陸頁面,以下(本登陸頁面是從新繪製,不是CAS原生登陸頁)

由於我是首次登陸,因此會跳轉到該登陸頁進行認證。

 

第三步:用戶認證

輸入用戶名、密碼、驗證碼,點擊登陸

這時再來看login-webflow.xml

用戶提交登陸後,按流程依次是

1.authenticationViaFormAction.doBind

    <view-state id="viewLoginForm" view="casMyLoginView" model="credentials">
        <binder>
            <binding property="username" />
            <binding property="password" />
            <binding property="imgverifycode" />
        </binder>
        <on-entry>
            <set name="viewScope.commandName" value="'credentials'" />
        </on-entry>
        <transition on="submit" bind="true" validate="true" to="imgverifycodeValidate">
            <evaluate
                expression="authenticationViaFormAction.doBind(flowRequestContext, flowScope.credentials)" />
        </transition>
    </view-state>

 

=>

2.imgverifycodeValidate(驗證碼處理爲自定義的處理,不是原生邏輯)

    <action-state id="imgverifycodeValidate">
        <evaluate
            expression="authenticationViaFormAction.validatorCode(flowRequestContext, flowScope.credentials, messageContext)" />
        <transition on="error" to="generateLoginTicket" />
        <transition on="success" to="realSubmit" />
    </action-state>

 

=>

3.realSubmit

<action-state id="realSubmit">
        <evaluate
            expression="authenticationViaFormAction.submit(flowRequestContext, flowScope.credentials, messageContext)" />
        <transition on="warn" to="warn" />
        <transition on="success" to="sendTicketGrantingTicket" />
        <transition on="error" to="generateLoginTicket" />
    </action-state>

realSubmit中執行的是authenticationViaFormAction.submit

 public final String submit(final RequestContext context,
            final Credentials credentials, final MessageContext messageContext)
            throws Exception
    {
        // Validate login ticket
        final String authoritativeLoginTicket = WebUtils.getLoginTicketFromFlowScope(context);
        final String providedLoginTicket = WebUtils.getLoginTicketFromRequest(context);
        if (!authoritativeLoginTicket.equals(providedLoginTicket))
        {
            this.logger.warn("Invalid login ticket " + providedLoginTicket);
            final String code = "INVALID_TICKET";
            messageContext.addMessage(new MessageBuilder().error()
                    .code(code)
                    .arg(providedLoginTicket)
                    .defaultText(code)
                    .build());
            return "error";
        }
        
        // 獲取TGT,首次登陸的話應該是不存在的,因此直接跳過該分歧
        final String ticketGrantingTicketId = WebUtils.getTicketGrantingTicketId(context);
        final Service service = WebUtils.getService(context);
        if (StringUtils.hasText(context.getRequestParameters().get("renew"))
                && ticketGrantingTicketId != null && service != null)
        {
            
            try
            {
                final String serviceTicketId = this.centralAuthenticationService.grantServiceTicket(ticketGrantingTicketId,
                        service,
                        credentials);
                WebUtils.putServiceTicketInRequestScope(context,
                        serviceTicketId);
                putWarnCookieIfRequestParameterPresent(context);
                return "warn";
            }
            catch (final TicketException e)
            {
                if (e.getCause() != null
                        && AuthenticationException.class.isAssignableFrom(e.getCause()
                                .getClass()))
                {
                    populateErrorsInstance(e, messageContext);
                    return "error";
                }
                this.centralAuthenticationService.destroyTicketGrantingTicket(ticketGrantingTicketId);
                if (logger.isDebugEnabled())
                {
                    logger.debug("Attempted to generate a ServiceTicket using renew=true with different credentials",
                            e);
                }
            }
        }
        
        try
        {
            // 首次登陸時,用戶輸入信息驗證成功後,建立一個新的TGT
            WebUtils.putTicketGrantingTicketInRequestScope(context,
                    this.centralAuthenticationService.createTicketGrantingTicket(credentials));
            putWarnCookieIfRequestParameterPresent(context);
            return "success";
        }
        catch (final TicketException e)
        {
            // 若是用戶輸入信息驗證不經過,會拋出異常,並在頁面上顯示
            populateErrorsInstance(e, messageContext);
            return "error";
        }
    }

 =>

4.用戶信息認證經過,而且建立了新的TGT後,緩存TGT,而且生成cookie,待後續把cookie寫入客戶端

    <action-state id="sendTicketGrantingTicket">
        <evaluate expression="sendTicketGrantingTicketAction" />
        <transition to="serviceCheck" />
    </action-state>
sendTicketGrantingTicketAction.doExecute
    protected Event doExecute(final RequestContext context) {
        final String ticketGrantingTicketId = WebUtils.getTicketGrantingTicketId(context); 
        final String ticketGrantingTicketValueFromCookie = (String) context.getFlowScope().get("ticketGrantingTicketId");
        
        if (ticketGrantingTicketId == null) {
            return success();
        }
        
        // 生成Cookie而且寫入response,最終在客戶端Cookie中保存了本TGT
        this.ticketGrantingTicketCookieGenerator.addCookie(WebUtils.getHttpServletRequest(context), WebUtils
            .getHttpServletResponse(context), ticketGrantingTicketId);

        if (ticketGrantingTicketValueFromCookie != null && !ticketGrantingTicketId.equals(ticketGrantingTicketValueFromCookie)) {
            this.centralAuthenticationService
                .destroyTicketGrantingTicket(ticketGrantingTicketValueFromCookie);
        }

        return success();
    }

 =>

5. 而後驗證是否存在Service,若是存在,生成ST,重定向用戶到 Service 所在地址(附帶該ST ) , 併爲客戶端瀏覽器設置一個 Ticket Granted Cookie ( TGC ) 

serviceCheck => generateServiceTicket => warn => redirect =>postRedirectDecision

 

第四步:拿着新產生的ST,到 CAS Server 進行身份覈實,以確保 Service Ticket 的合法性。

當cas Service重定向到客戶端所在service時,該重定向請求一樣會被客戶端配置的過濾器所攔截,又進入了第一步處的AuthenticationFilter

可是因爲本次請求已經帶回了ST(service ticket),因此處理與首次有所不一樣。

public final void doFilter(final ServletRequest servletRequest,
            final ServletResponse servletResponse, final FilterChain filterChain)
            throws IOException, ServletException
    {
        final HttpServletRequest request = (HttpServletRequest) servletRequest;
        final HttpServletResponse response = (HttpServletResponse) servletResponse;
        final HttpSession session = request.getSession(false);
        final Assertion assertion = session != null ? (Assertion) session.getAttribute(CONST_CAS_ASSERTION)
                : null;
        
        if (assertion != null)
        {
            filterChain.doFilter(request, response);
            return;
        }
        final String serviceUrl = constructServiceUrl(request, response);
        final String ticket = CommonUtils.safeGetParameter(request,
                getArtifactParameterName());
        final boolean wasGatewayed = this.gatewayStorage.hasGatewayedAlready(request,
                serviceUrl);
        
        // 因爲本次已經能夠取到cas service返回的新的service ticket
        if (CommonUtils.isNotBlank(ticket) || wasGatewayed)
        {
// 因此直接進入本代碼塊,而後退出 filterChain.doFilter(request, response);
return; } // 不會再一次被重定向會cas 認證中心

final String modifiedServiceUrl; log.debug("no ticket and no assertion found"); if (this.gateway) { log.debug("setting gateway attribute in session"); modifiedServiceUrl = this.gatewayStorage.storeGatewayInformation(request, serviceUrl); } else { modifiedServiceUrl = serviceUrl; } if (log.isDebugEnabled()) { log.debug("Constructed service url: " + modifiedServiceUrl); } // 若是用戶沒有登陸過,那麼構造重定向的URL final String urlToRedirectTo = CommonUtils.constructRedirectUrl(this.casServerLoginUrl, getServiceParameterName(), modifiedServiceUrl, this.renew, this.gateway); if (log.isDebugEnabled()) { log.debug("redirecting to \"" + urlToRedirectTo + "\""); } // 重定向跳轉到Cas認證中心 response.sendRedirect(urlToRedirectTo); }

 

 

以後,又會被web.xml中的CAS Validation Filter(Cas20ProxyReceivingTicketValidationFilter)所攔截

該攔截器用來與CAS Server 進行身份覈實,以確保 Service Ticket 的合法性

因爲 Cas20ProxyReceivingTicketValidationFilter 沒有重寫doFilter方法,因此會進入父類AbstractTicketValidationFilter的doFilter方法

AbstractTicketValidationFilter.doFilter

 public final void doFilter(final ServletRequest servletRequest,
            final ServletResponse servletResponse, final FilterChain filterChain)
            throws IOException, ServletException
    {
        
        if (!preFilter(servletRequest, servletResponse, filterChain))
        {
            return;
        }
        
        final HttpServletRequest request = (HttpServletRequest) servletRequest;
        final HttpServletResponse response = (HttpServletResponse) servletResponse;
        final String ticket = CommonUtils.safeGetParameter(request,
                getArtifactParameterName());
        
        if (CommonUtils.isNotBlank(ticket))
        {
            if (log.isDebugEnabled())
            {
                log.debug("Attempting to validate ticket: " + ticket);
            }
            
            try
            {
                // 構造驗證URL,向cas server發起驗證請求
                final Assertion assertion = this.ticketValidator.validate(ticket, constructServiceUrl(request, response)); if (log.isDebugEnabled())
                {
                    log.debug("Successfully authenticated user: "
                            + assertion.getPrincipal().getName());
                }
                
                // 若是驗證成功,設置assertion,當再一次發起訪問請求時,若是發現assertion已經被設置,因此已經經過驗證,不過再次重定向會cas認證中心
                request.setAttribute(CONST_CAS_ASSERTION, assertion);
                
                if (this.useSession)
                {
                    request.getSession().setAttribute(CONST_CAS_ASSERTION,
                            assertion);
                }
                onSuccessfulValidation(request, response, assertion);
                
                if (this.redirectAfterValidation)
                {
                    log.debug("Redirecting after successful ticket validation.");
                    response.sendRedirect(constructServiceUrl(request, response));
                    return;
                }
            }
            catch (final TicketValidationException e)
            {
                response.setStatus(HttpServletResponse.SC_FORBIDDEN);
                log.warn(e, e);
                
                onFailedValidation(request, response);
                
                if (this.exceptionOnValidationFailure)
                {
                    throw new ServletException(e);
                }
                
                return;
            }
        }
        
        filterChain.doFilter(request, response);
        
    }

 

this.ticketValidator.validate(..) 代碼以下
 public Assertion validate(final String ticket, final String service)
            throws TicketValidationException
    {
        
        // 生成驗證URL,若是你debug會發現,此處會構造一個相似如下的URL,訪問的是cas server的serviceValidate方法
        // https://demo.testcas.com/cas/serviceValidate?ticket=ST-31-cioaDNxSpUWIgeYEn4yK-cas&service=http%3A%2F%2Fapp1.testcas.com%2Fb2c-haohai-server%2Fuser%2FcasLogin
        final String validationUrl = constructValidationUrl(ticket, service);
        if (log.isDebugEnabled())
        {
            log.debug("Constructing validation url: " + validationUrl);
        }
        
        try
        {
            log.debug("Retrieving response from server.");
            // 獲得cas service響應,驗證成功或者失敗
            final String serverResponse = retrieveResponseFromServer(new URL(
                    validationUrl), ticket);
            
            if (serverResponse == null)
            {
                throw new TicketValidationException(
                        "The CAS server returned no response.");
            }
            
            if (log.isDebugEnabled())
            {
                log.debug("Server response: " + serverResponse);
            }
            
            return parseResponseFromServer(serverResponse);
        }
        catch (final MalformedURLException e)
        {
            throw new TicketValidationException(e);
        }
    }

能夠看一下,cas server側的serverValidate的具體實現
在cas server的cas-servlet.xml中,能夠看到以下配置:
    <bean
        id="handlerMappingC"
        class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property
            name="mappings">
            <props>
                <prop
                    key="/logout">
                    logoutController
                </prop>
                <prop
                    key="/serviceValidate"> serviceValidateController </prop>
...

 

指向serviceValidateController

ServiceValidateController.handleRequestInternal(...)
 
 protected final ModelAndView handleRequestInternal(final HttpServletRequest request, final HttpServletResponse response) throws Exception {
        final WebApplicationService service = this.argumentExtractor.extractService(request);
        final String serviceTicketId = service != null ? service.getArtifactId() : null;

        if (service == null || serviceTicketId == null) {
            if (logger.isDebugEnabled()) {
                logger.debug(String.format("Could not process request; Service: %s, Service Ticket Id: %s", service, serviceTicketId));
            }
            return generateErrorView("INVALID_REQUEST", "INVALID_REQUEST", null);
        }

        try {
            final Credentials serviceCredentials = getServiceCredentialsFromRequest(request);
            String proxyGrantingTicketId = null;

            // XXX should be able to validate AND THEN use
            if (serviceCredentials != null) {
                try {
                    proxyGrantingTicketId = this.centralAuthenticationService
                        .delegateTicketGrantingTicket(serviceTicketId,
                            serviceCredentials);
                } catch (final TicketException e) {
                    logger.error("TicketException generating ticket for: "
                        + serviceCredentials, e);
                }
            }

            final Assertion assertion = this.centralAuthenticationService.validateServiceTicket(serviceTicketId, service);

            final ValidationSpecification validationSpecification = this.getCommandClass();
            final ServletRequestDataBinder binder = new ServletRequestDataBinder(validationSpecification, "validationSpecification");
            initBinder(request, binder);
            binder.bind(request);

            if (!validationSpecification.isSatisfiedBy(assertion)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("ServiceTicket [" + serviceTicketId + "] does not satisfy validation specification.");
                }
                return generateErrorView("INVALID_TICKET", "INVALID_TICKET_SPEC", null);
            }

            onSuccessfulValidation(serviceTicketId, assertion);

            final ModelAndView success = new ModelAndView(this.successView);
            success.addObject(MODEL_ASSERTION, assertion);

            if (serviceCredentials != null && proxyGrantingTicketId != null) {
                final String proxyIou = this.proxyHandler.handle(serviceCredentials, proxyGrantingTicketId);
                success.addObject(MODEL_PROXY_GRANTING_TICKET_IOU, proxyIou);
            }

            if (logger.isDebugEnabled()) {
                logger.debug(String.format("Successfully validated service ticket: %s", serviceTicketId));
            }

            return success;
        } catch (final TicketValidationException e) {
            return generateErrorView(e.getCode(), e.getCode(), new Object[] {serviceTicketId, e.getOriginalService().getId(), service.getId()});
        } catch (final TicketException te) {
            return generateErrorView(te.getCode(), te.getCode(),
                new Object[] {serviceTicketId});
        } catch (final UnauthorizedServiceException e) {
            return generateErrorView(e.getMessage(), e.getMessage(), null);
        }
    }
 
驗證成功後,就能夠正常訪問了。
相關文章
相關標籤/搜索