zoukankan      html  css  js  c++  java
  • CAS单点登录原理以及debug跟踪登录流程

    CAS 原理和协议

    基础模式

    基础模式 SSO 访问流程主要有以下步骤:

    1. 访问服务: SSO 客户端发送请求访问应用系统提供的服务资源。

    2. 定向认证: SSO 客户端会重定向用户请求到 SSO 服务器。

    3. 用户认证:用户身份认证。

    4. 发放票据: SSO 服务器会产生一个随机的 Service Ticket 。

    5. 验证票据: SSO 服务器验证票据 Service Ticket 的合法性,验证通过后,允许客户端访问服务。

    6. 传输用户信息: SSO 服务器验证票据通过后,传输用户认证结果信息给客户端。

    下面是 CAS 最基本的协议过程:

    基础协议图

    如 上图: CAS Client 与受保护的客户端应用部署在一起,以 Filter 方式保护 Web 应用的受保护资源,过滤从客户端过来的每一个 Web 请求,同 时, CAS Client 会分析 HTTP 请求中是否包含请求 Service Ticket( ST 上图中的 Ticket) ,如果没有,则说明该用户是没有经过认证的;于是 CAS Client 会重定向用户请求到 CAS Server ( Step 2 ),并传递 Service (要访问的目的资源地址)。 Step 3 是用户认证过程,如果用户提供了正确的 Credentials , CAS Server 随机产生一个相当长度、唯一、不可伪造的 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);
            }
        }
    
    
    验证成功后,就可以正常访问了。
  • 相关阅读:
    业余爱好很有必要
    平和的心态
    合理预期
    有所为,有所不为
    iOS中使用UITextView设置不同文本部分点击事件小结
    一直在路上
    Windows平台交叉编译Arm Linux平台的QT5.7库
    UOS创建开机自启程序或脚本
    HTTP HTTP请求报文和响应报文的格式
    ObjectObject.prototype.toString.call()方法的使用
  • 原文地址:https://www.cnblogs.com/notDog/p/5252973.html
Copyright © 2011-2022 走看看