cas client登錄流程源碼分析

一.前期準(zhǔn)備

1.cas源碼版本

<version>4.1.11-SNAPSHOT</version>

2.服務(wù)端

http://www.eric.cas.server.com:8080/cas-server

3.cas client

http://www.eric.cas.client.com:8081/cas/index.do

4.cas client web.xml配置

<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">

    <display-name>Archetype Created Web Application</display-name>


    <servlet>
        <servlet-name>cas-client-4.x</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>cas-client-4.x</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath*:/applicationContext.xml
        </param-value>
    </context-param>

    <!-- ****************** 單點(diǎn)登錄開始 ********************-->
    <!-- 用于實(shí)現(xiàn)單點(diǎn)登出功能  可選 -->
    <listener>
        <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
    </listener>
    <filter>
        <filter-name>CAS Single Sign Out Filter</filter-name>
        <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
        <init-param>
            <param-name>casServerUrlPrefix</param-name>
            <param-value>http://www.eric.cas.server.com:8080/</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CAS Single Sign Out Filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!-- 該過濾器負(fù)責(zé)用戶的認(rèn)證工作危纫,必須 -->
    <filter>
        <filter-name>CAS Authentication Filter</filter-name>
        <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
        <init-param>
            <param-name>casServerLoginUrl</param-name>
            <!--casServerLoginUrl:cas服務(wù)的登陸url -->
            <param-value>http://www.eric.cas.server.com:8080/login</param-value>
        </init-param>
        <init-param>
            <!--serverName:本項(xiàng)目的ip+port -->
            <param-name>serverName</param-name>
            <param-value>http://www.eric.cas.client.com:8081</param-value>
        </init-param>
        <init-param>
            <param-name>useSession</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
            <param-name>redirectAfterValidation</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CAS Authentication Filter</filter-name>
        <url-pattern>/cas/*</url-pattern>
    </filter-mapping>

    <!-- 該過濾器負(fù)責(zé)對(duì)Ticket的校驗(yàn)工作,必須-->
    <filter>
        <filter-name>CAS Validation Filter</filter-name>
        <filter-class>org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter</filter-class>
        <init-param>
            <param-name>casServerUrlPrefix</param-name>
            <param-value>http://www.eric.cas.server.com:8080/</param-value>
        </init-param>
        <init-param>
            <param-name>serverName</param-name>
            <param-value>http://www.eric.cas.client.com:8081</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CAS Validation Filter</filter-name>
        <!-- 對(duì)cas做登錄攔截-->
        <url-pattern>/cas/*</url-pattern>
    </filter-mapping>

    <!-- 該過濾器對(duì)HttpServletRequest請(qǐng)求包裝, 可通過HttpServletRequest的getRemoteUser()方法獲得登錄用戶的登錄名祸轮,可選 -->
    <filter>
        <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
        <filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>


    <!-- 該過濾器使得可以通過org.jasig.cas.client.util.AssertionHolder來獲取用戶的登錄名茁计。
     比如AssertionHolder.getAssertion().getPrincipal().getName()沃暗。
     這個(gè)類把Assertion信息放在ThreadLocal變量中冤馏,這樣應(yīng)用程序不在web層也能夠獲取到當(dāng)前登錄信息 -->
    <filter>
        <filter-name>CAS Assertion Thread Local Filter</filter-name>
        <filter-class>org.jasig.cas.client.util.AssertionThreadLocalFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>CAS Assertion Thread Local Filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!-- ****************** 單點(diǎn)登錄結(jié)束 ********************-->
</web-app>

登錄流程

當(dāng)從瀏覽器訪問配置了單點(diǎn)登錄的應(yīng)用系統(tǒng)時(shí)(http://www.eric.cas.client.com:8081/cas/index.do),
請(qǐng)求第一次到達(dá)SingleSignOutFilter過濾器

一. SingleSignOutFilter

SingleSignOutFilter的doFilter方法如下:

    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)servletRequest;
        HttpServletResponse response = (HttpServletResponse)servletResponse;
        if(!this.handlerInitialized.getAndSet(true)) {
            HANDLER.init();
        }
        // 處理請(qǐng)求方法
        if(HANDLER.process(request, response)) {
            filterChain.doFilter(servletRequest, servletResponse);
        }

    }

可以看到SingleSignOutFilter中處理請(qǐng)求的主要方法是

HANDLER.process(request, response)

實(shí)際調(diào)用的是SingleSignOutHandler的process方法

public boolean process(HttpServletRequest request, HttpServletResponse response) {
        // 是否是帶有token參數(shù)的請(qǐng)求 如ticket參數(shù)
        if(this.isTokenRequest(request)) {
            this.logger.trace("Received a token request");
            // 記錄session
            this.recordSession(request);
            return true;
        } else if(this.isBackChannelLogoutRequest(request)) {
            // 登出請(qǐng)求  銷毀session
            this.logger.trace("Received a back channel logout request");
            this.destroySession(request);
            return false;
        } else if(this.isFrontChannelLogoutRequest(request)) {
            this.logger.trace("Received a front channel logout request");
            // 登出請(qǐng)求  銷毀session
            this.destroySession(request);
            String redirectionUrl = this.computeRedirectionToServer(request);
            if(redirectionUrl != null) {
                CommonUtils.sendRedirect(response, redirectionUrl);
            }

            return false;
        } else {
            this.logger.trace("Ignoring URI for logout: {}", request.getRequestURI());
            return true;
        }
    }

以上源碼可以看到典徊,process方法主要是用于單點(diǎn)登錄時(shí)記錄登錄的session信息,單點(diǎn)登出時(shí),銷毀記錄的session内列。

由于是第一次請(qǐng)求撵术,直接返回true,跳轉(zhuǎn)到下一個(gè)過濾器AuthenticationFilter

二. AuthenticationFilter

AuthenticationFilter的doFilter方法如下:

public final void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)servletRequest;
        HttpServletResponse response = (HttpServletResponse)servletResponse;
        if(this.isRequestUrlExcluded(request)) {
            this.logger.debug("Request is ignored.");
            filterChain.doFilter(request, response);
        } else {
            //判斷登錄session是否存在
            HttpSession session = request.getSession(false);
            //從session中獲取名為"_const_cas_assertion_"的Assertion  
            Assertion assertion = session != null?(Assertion)session.getAttribute("_const_cas_assertion_"):null;
            if(assertion != null) {
                //登錄session存在 則執(zhí)行下一個(gè)filter
                filterChain.doFilter(request, response);
            } else {
                String serviceUrl = this.constructServiceUrl(request, response);
                String ticket = this.retrieveTicketFromRequest(request);
                boolean wasGatewayed = this.gateway && this.gatewayStorage.hasGatewayedAlready(request, serviceUrl);
                if(!CommonUtils.isNotBlank(ticket) && !wasGatewayed) {
                    this.logger.debug("no ticket and no assertion found");
                    String modifiedServiceUrl;
                    if(this.gateway) {
                        this.logger.debug("setting gateway attribute in session");
                        modifiedServiceUrl = this.gatewayStorage.storeGatewayInformation(request, serviceUrl);
                    } else {
                        modifiedServiceUrl = serviceUrl;
                    }

                    this.logger.debug("Constructed service url: {}", modifiedServiceUrl);
                    String urlToRedirectTo = CommonUtils.constructRedirectUrl(this.casServerLoginUrl, this.getServiceParameterName(), modifiedServiceUrl, this.renew, this.gateway);
                    this.logger.debug("redirecting to \"{}\"", urlToRedirectTo);
                    this.authenticationRedirectStrategy.redirect(request, response, urlToRedirectTo);
                } else {
                    //如果ticket不為空,本過濾器處理完成话瞧,處理下個(gè)過濾器 
                    filterChain.doFilter(request, response);
                }
            }
        }
    }

由以上源碼可知嫩与,當(dāng)請(qǐng)求到達(dá)之后執(zhí)行以下操作:

  1. 首先判斷當(dāng)前請(qǐng)求是否需要過濾,不需要?jiǎng)t直接跳轉(zhuǎn)到下一個(gè)過濾器交排。
  2. 從session中獲取名為“const_cas_assertion”的assertion對(duì)象划滋,判斷assertion是否存在,如果存在埃篓,說明已經(jīng)登錄处坪,執(zhí)行下一個(gè)過濾器。如果不存在架专,執(zhí)行第3步稻薇。
  3. 生成serviceUrl(http://www.eric.cas.client.com:8081/cas/index.do),并且從request中獲取票據(jù)參數(shù)ticket胶征,判斷ticket是否為空塞椎,如果不為空?qǐng)?zhí)行下一個(gè)過濾器。如果為空睛低,執(zhí)行第4步案狠。
  4. 生成重定向URL(http://www.eric.cas.server.com:8080/cas-server/login?service=http://www.eric.cas.client.com:8081/cas/index.do)。
  5. 執(zhí)行重定向钱雷,跳轉(zhuǎn)到單點(diǎn)登錄服務(wù)器骂铁,顯示登錄頁(yè)面

瀏覽器登錄頁(yè)面

  1. 登錄頁(yè)面輸入用戶名和密碼,執(zhí)行登錄操作罩抗,請(qǐng)求到達(dá)cas server,cas server做登錄校驗(yàn)之后拉庵,生成登錄所需要的ticket并重定向到cas client,具體重定向地址如下
http://www.eric.cas.client.com:8081/cas/index.do?ticket=ST-1-alt4ccCXxjOamzWU4Hid-cas01.example.org

此時(shí)已經(jīng)拿到了單點(diǎn)登錄的臨時(shí)票據(jù)Service Ticket套蒂,簡(jiǎn)稱ST钞支。

帶有ST參數(shù)的請(qǐng)求重新到達(dá)cas client,還是先到達(dá)SingleSignOutFilter,此時(shí)已經(jīng)拿到登錄的ST,則保存登錄的票據(jù)信息到session操刀,然后執(zhí)行下一個(gè)攔截器AuthenticationFilter

if(this.isTokenRequest(request)) {
            this.logger.trace("Received a token request");
            this.recordSession(request);
            return true;
        }
    private void recordSession(HttpServletRequest request) {
        HttpSession session = request.getSession(this.eagerlyCreateSessions);
        if(session == null) {
            this.logger.debug("No session currently exists (and none created).  Cannot record session information for single sign out.");
        } else {
            String token = CommonUtils.safeGetParameter(request, this.artifactParameterName, this.safeParameters);
            this.logger.debug("Recording session for token {}", token);

            try {
                this.sessionMappingStorage.removeBySessionById(session.getId());
            } catch (Exception var5) {
                ;
            }

            this.sessionMappingStorage.addSessionById(token, session);
        }
    }

帶有ST參數(shù)的請(qǐng)求到達(dá)AuthenticationFilter,此時(shí)assertion對(duì)象仍然為空但是ticket不為空烁挟,則直接跳轉(zhuǎn)到下一個(gè)過濾器Cas20ProxyReceivingTicketValidationFilter

三. Cas20ProxyReceivingTicketValidationFilter

Cas20ProxyReceivingTicketValidationFilter繼承了AbstractTicketValidationFilter類,doFilter方法如下

public final void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        if(this.preFilter(servletRequest, servletResponse, filterChain)) {
            HttpServletRequest request = (HttpServletRequest)servletRequest;
            HttpServletResponse response = (HttpServletResponse)servletResponse;
            /從request中獲取ST參數(shù) 
            String ticket = this.retrieveTicketFromRequest(request);
            if(CommonUtils.isNotBlank(ticket)) {
                this.logger.debug("Attempting to validate ticket: {}", ticket);

                try {
                    //驗(yàn)證ticket并產(chǎn)生Assertion對(duì)象骨坑,錯(cuò)誤拋出TicketValidationException異常
                    Assertion e = this.ticketValidator.validate(ticket, this.constructServiceUrl(request, response));
                    this.logger.debug("Successfully authenticated user: {}", e.getPrincipal().getName());
                   
                   //驗(yàn)證成功 保存認(rèn)證用戶信息到session 
                   request.setAttribute("_const_cas_assertion_", e);
                    if(this.useSession) {
                        request.getSession().setAttribute("_const_cas_assertion_", e);
                    }

                    this.onSuccessfulValidation(request, response, e);
                   //跳轉(zhuǎn)到請(qǐng)求地址
                   if(this.redirectAfterValidation) {
                        this.logger.debug("Redirecting after successful ticket validation.");
                        response.sendRedirect(this.constructServiceUrl(request, response));
                        return;
                    }
                } catch (TicketValidationException var8) {
                    this.logger.debug(var8.getMessage(), var8);
                    this.onFailedValidation(request, response);
                    if(this.exceptionOnValidationFailure) {
                        throw new ServletException(var8);
                    }

                    response.sendError(403, var8.getMessage());
                    return;
                }
            }

            filterChain.doFilter(request, response);
        }
    }

Cas20ProxyReceivingTicketValidationFilter撼嗓,執(zhí)行以下操作:

從request獲取ticket參數(shù),如果ticket為空,繼續(xù)處理下一個(gè)過濾器且警。如果參數(shù)不為空粉捻,驗(yàn)證ticket參數(shù)的合法性。
驗(yàn)證ticket斑芜,TicketValidator的validate方法通過httpClient訪問CAS服務(wù)器端

http://www.eric.cas.server.com:8080/serviceValidate?ticket=ST-6-okgjpFYrNhfVYbCx9xYQ-cas01.example.org&service=http%3A%2F%2Fwww.eric.cas.client.com%3A8081%2Fcas%2Findex.do

驗(yàn)證ticket是否正確杀迹,并返回assertion對(duì)象。

如果驗(yàn)證失敗押搪,拋出異常,跳轉(zhuǎn)到錯(cuò)誤頁(yè)面浅碾。

如果驗(yàn)證成功大州,則將Assertion對(duì)象保存到session中,s垂谢,繼續(xù)處理下一個(gè)過濾器厦画。

request.getSession().setAttribute("_const_cas_assertion_", e);

單點(diǎn)登錄流程結(jié)束


服務(wù)端具體驗(yàn)證ST流程

補(bǔ)充上面說的cas client請(qǐng)求cas server驗(yàn)證ticket的具體邏輯

驗(yàn)證請(qǐng)求如下:

http://www.eric.cas.server.com:8080/serviceValidate?ticket=ST-6-okgjpFYrNhfVYbCx9xYQ-cas01.example.org&service=http%3A%2F%2Fwww.eric.cas.client.com%3A8081%2Fcas%2Findex.do

查看服務(wù)端cas-servlet.xml文件配置

<bean
      id="handlerMappingC"
      class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"
      p:alwaysUseFullPath="true">
    <property name="mappings">
      <util:properties>
        <prop key="/serviceValidate">serviceValidateController</prop>
        <prop key="/proxyValidate">proxyValidateController</prop>
        <prop key="/p3/serviceValidate">v3ServiceValidateController</prop>
        <prop key="/p3/proxyValidate">v3ProxyValidateController</prop>
        <prop key="/validate">legacyValidateController</prop>
        <prop key="/proxy">proxyController</prop>
        <prop key="/authorizationFailure.html">passThroughController</prop>
      </util:properties>
    </property>
  </bean>

可以看到cas client發(fā)送的ST驗(yàn)證請(qǐng)求由serviceValidateController負(fù)責(zé)處理

@Override
    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) {
            logger.debug("Could not identify service and/or service ticket for service: [{}]", service);
            return generateErrorView(CasProtocolConstants.ERROR_CODE_INVALID_REQUEST,
                    CasProtocolConstants.ERROR_CODE_INVALID_REQUEST, null);
        }

        try {
            final Credential serviceCredential = getServiceCredentialsFromRequest(service, request);
            TicketGrantingTicket proxyGrantingTicketId = null;
            
            if (serviceCredential != null) {
                try {
                    proxyGrantingTicketId = this.centralAuthenticationService.delegateTicketGrantingTicket(serviceTicketId,
                                serviceCredential);
                    logger.debug("Generated PGT [{}] off of service ticket [{}] and credential [{}]",
                            proxyGrantingTicketId.getId(), serviceTicketId, serviceCredential);
                } catch (final AuthenticationException e) {
                    logger.info("Failed to authenticate service credential {}", serviceCredential);
                } catch (final TicketException e) {
                    logger.error("Failed to create proxy granting ticket for {}", serviceCredential, e);
                }
                
                if (proxyGrantingTicketId == null) {
                    return generateErrorView(CasProtocolConstants.ERROR_CODE_INVALID_PROXY_CALLBACK,
                            CasProtocolConstants.ERROR_CODE_INVALID_PROXY_CALLBACK,
                            new Object[] {serviceCredential.getId()});
                }
            }
            // 驗(yàn)證ST是否正確  并且生成Assertion對(duì)象
            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)) {
                logger.debug("Service ticket [{}] does not satisfy validation specification.", serviceTicketId);
                return generateErrorView(CasProtocolConstants.ERROR_CODE_INVALID_TICKET,
                        CasProtocolConstants.ERROR_CODE_INVALID_TICKET, null);
            }

            String proxyIou = null;
            if (serviceCredential != null && this.proxyHandler.canHandle(serviceCredential)) {
                proxyIou = this.proxyHandler.handle(serviceCredential, proxyGrantingTicketId);
                if (StringUtils.isEmpty(proxyIou)) {
                    return generateErrorView(CasProtocolConstants.ERROR_CODE_INVALID_PROXY_CALLBACK,
                            CasProtocolConstants.ERROR_CODE_INVALID_PROXY_CALLBACK,
                            new Object[] {serviceCredential.getId()});
                }
            }

            onSuccessfulValidation(serviceTicketId, assertion);
            logger.debug("Successfully validated service ticket {} for service [{}]", serviceTicketId, service.getId());
            return generateSuccessView(assertion, proxyIou, service, proxyGrantingTicketId);
        } catch (final TicketValidationException e) {
            final String code = e.getCode();
            return generateErrorView(code, code,
                    new Object[] {serviceTicketId, e.getOriginalService().getId(), service.getId()});
        } catch (final TicketException te) {
            return generateErrorView(te.getCode(), te.getCode(),
                new Object[] {serviceTicketId});
        } catch (final UnauthorizedProxyingException e) {
            return generateErrorView(e.getMessage(), e.getMessage(), new Object[] {service.getId()});
        } catch (final UnauthorizedServiceException e) {
            return generateErrorView(e.getMessage(), e.getMessage(), null);
        }
    }

查看ST驗(yàn)證的主要方法CentralAuthenticationServiceImpl類的validateServiceTicket方法

public Assertion validateServiceTicket(final String serviceTicketId, final Service service) throws TicketException {
        final RegisteredService registeredService = this.servicesManager.findServiceBy(service);
        verifyRegisteredServiceProperties(registeredService, service);
        // 從緩存中查看ST是否存在
        final ServiceTicket serviceTicket = this.ticketRegistry.getTicket(serviceTicketId, ServiceTicket.class);

        if (serviceTicket == null) {
            logger.info("Service ticket [{}] does not exist.", serviceTicketId);
            throw new InvalidTicketException(serviceTicketId);
        }

        try {
            synchronized (serviceTicket) {
                // ST是否過期
                if (serviceTicket.isExpired()) {
                    logger.info("ServiceTicket [{}] has expired.", serviceTicketId);
                    throw new InvalidTicketException(serviceTicketId);
                }
                // ST是否合法
                if (!serviceTicket.isValidFor(service)) {
                    logger.error("Service ticket [{}] with service [{}] does not match supplied service [{}]",
                            serviceTicketId, serviceTicket.getService().getId(), service);
                    throw new UnrecognizableServiceForServiceTicketValidationException(serviceTicket.getService());
                }
            }

            final TicketGrantingTicket root = serviceTicket.getGrantingTicket().getRoot();
            final Authentication authentication = getAuthenticationSatisfiedByPolicy(
                    root, new ServiceContext(serviceTicket.getService(), registeredService));
            final Principal principal = authentication.getPrincipal();

            final AttributeReleasePolicy attributePolicy = registeredService.getAttributeReleasePolicy();
            logger.debug("Attribute policy [{}] is associated with service [{}]", attributePolicy, registeredService);

            @SuppressWarnings("unchecked")
            final Map<String, Object> attributesToRelease = attributePolicy != null
                    ? attributePolicy.getAttributes(principal) : Collections.EMPTY_MAP;

            final String principalId = registeredService.getUsernameAttributeProvider().resolveUsername(principal, service);
            final Principal modifiedPrincipal = this.principalFactory.createPrincipal(principalId, attributesToRelease);
            final AuthenticationBuilder builder = DefaultAuthenticationBuilder.newInstance(authentication);
            builder.setPrincipal(modifiedPrincipal);

            return new ImmutableAssertion(
                    builder.build(),
                    serviceTicket.getGrantingTicket().getChainedAuthentications(),
                    serviceTicket.getService(),
                    serviceTicket.isFromNewLogin());
        } finally {
            if (serviceTicket.isExpired()) {
                this.ticketRegistry.deleteTicket(serviceTicketId);
            }
        }
    }

這里主要看下serviceTicket.isValidFor(service)方法

    @Override
    public boolean isValidFor(final Service serviceToValidate) {
        updateState();
        return serviceToValidate.matches(this.service);
    }

該方法實(shí)際調(diào)用的是AbstractWebApplicationService類的matches方法

    @Override
    public boolean matches(final Service service) {
        try {
            final String thisUrl = URLDecoder.decode(this.id, "UTF-8");
            final String serviceUrl = URLDecoder.decode(service.getId(), "UTF-8");

            logger.trace("Decoded urls and comparing [{}] with [{}]", thisUrl, serviceUrl);
            return thisUrl.equalsIgnoreCase(serviceUrl);
        } catch (final Exception e) {
            logger.error(e.getMessage(), e);
        }
        return false;
    }

可以看到驗(yàn)證ST就是驗(yàn)證的登錄請(qǐng)求保存的service的ID和驗(yàn)證請(qǐng)求的service的ID是否相等,相等就認(rèn)為ST合法滥朱。
這里也可以修改驗(yàn)證邏輯根暑,添加自己的驗(yàn)證邏輯,但前提是確保ST驗(yàn)證的合法性徙邻,安全性排嫌。

這里的Service到底是什么呢,其實(shí)Service就是cas server把收到的客戶端請(qǐng)求封裝之后產(chǎn)生的對(duì)象

具體配置文件是argumentExtractorsConfiguration.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns="http://www.springframework.org/schema/beans"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
    <description>
        Argument Extractors are what are used to translate HTTP requests into requests of the appropriate protocol (i.e.
        CAS, SAML, SAML2,
        OpenId, etc.). By default, only CAS is enabled.
    </description>

    <!--
    <bean id="samlArgumentExtractor" class="org.jasig.cas.support.saml.web.support.SamlArgumentExtractor" />
    -->

    <bean id="casArgumentExtractor" class="org.jasig.cas.web.support.CasArgumentExtractor"/>

    <util:list id="argumentExtractors">
        <!-- <ref bean="samlArgumentExtractor" /> -->
        <ref bean="casArgumentExtractor"/>
    </util:list>
</beans>

CasArgumentExtractor類

public final class CasArgumentExtractor extends AbstractArgumentExtractor {

    @Override
    public WebApplicationService extractServiceInternal(final HttpServletRequest request) {
        return SimpleWebApplicationServiceImpl.createServiceFrom(request);
    }
}

可以看到Service接口的實(shí)例是SimpleWebApplicationServiceImpl類的對(duì)象

/**
     * Creates the service from the request.
     *
     * @param request the request
     * @return the simple web application service impl
     */
    public static SimpleWebApplicationServiceImpl createServiceFrom(
        final HttpServletRequest request) {
        final String targetService = request.getParameter(CONST_PARAM_TARGET_SERVICE);
        final String service = request.getParameter(CONST_PARAM_SERVICE);
        final String serviceAttribute = (String) request.getAttribute(CONST_PARAM_SERVICE);
        final String method = request.getParameter(CONST_PARAM_METHOD);
        final String serviceToUse;
        if (StringUtils.hasText(targetService)) {
            serviceToUse = targetService;
        } else if (StringUtils.hasText(service)) {
            serviceToUse = service;
        } else {
            serviceToUse = serviceAttribute;
        }

        if (!StringUtils.hasText(serviceToUse)) {
            return null;
        }

        final String id = cleanupUrl(serviceToUse);
        final String artifactId = request.getParameter(CONST_PARAM_TICKET);

        return new SimpleWebApplicationServiceImpl(id, serviceToUse,
            artifactId, "POST".equals(method) ? Response.ResponseType.POST
                : Response.ResponseType.REDIRECT);
    }

這里可以看到service的封裝過程,由此可見登錄時(shí)傳遞給cas server的service參數(shù)和ST驗(yàn)證時(shí)傳遞給cas server的service參數(shù)必須相同缰犁,ST才能驗(yàn)證成功

服務(wù)端驗(yàn)證ST流程結(jié)束淳地。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市帅容,隨后出現(xiàn)的幾起案子颇象,更是在濱河造成了極大的恐慌,老刑警劉巖并徘,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件遣钳,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡麦乞,警方通過查閱死者的電腦和手機(jī)蕴茴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來姐直,“玉大人荐开,你說我怎么就攤上這事〖螂龋” “怎么了晃听?”我有些...
    開封第一講書人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我能扒,道長(zhǎng)佣渴,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任初斑,我火速辦了婚禮辛润,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘见秤。我一直安慰自己砂竖,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開白布鹃答。 她就那樣靜靜地躺著乎澄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪测摔。 梳的紋絲不亂的頭發(fā)上置济,一...
    開封第一講書人閱讀 51,573評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音锋八,去河邊找鬼浙于。 笑死,一個(gè)胖子當(dāng)著我的面吹牛挟纱,可吹牛的內(nèi)容都是我干的羞酗。 我是一名探鬼主播,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼紊服,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼整慎!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起围苫,我...
    開封第一講書人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤裤园,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后剂府,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拧揽,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年腺占,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了淤袜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡衰伯,死狀恐怖铡羡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情意鲸,我是刑警寧澤烦周,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布尽爆,位于F島的核電站,受9級(jí)特大地震影響读慎,放射性物質(zhì)發(fā)生泄漏漱贱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一夭委、第九天 我趴在偏房一處隱蔽的房頂上張望幅狮。 院中可真熱鬧,春花似錦株灸、人聲如沸崇摄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽逐抑。三九已至,卻和暖如春杏死,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背捆交。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工淑翼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人品追。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓玄括,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親肉瓦。 傳聞我的和親對(duì)象是個(gè)殘疾皇子遭京,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容