• cas 服务端认证流程


    CAS服务端流程分析

    'CAS单点登录服务器端的登录流程'
    
    -----流程的配置在/WEB-INF/login-webflow.xml文件中
     <var name="credential" class="org.jasig.cas.authentication.UsernamePasswordCredential"/>
    -----首先,设置一个变量,用来存储用户名和密码信息
     <on-start>
        <evaluate expression="initialFlowSetupAction"/>
     </on-start>
     
    -------从此开始整个登录流程,initialFlowSetupAction,的配置信息/WEB-INF/cas-servlet.xml中
      <bean id="initialFlowSetupAction" class="org.jasig.cas.web.flow.InitialFlowSetupAction"
            p:argumentExtractors-ref="argumentExtractors"
            p:warnCookieGenerator-ref="warnCookieGenerator"
            p:ticketGrantingTicketCookieGenerator-ref="ticketGrantingTicketCookieGenerator"
            p:servicesManager-ref="servicesManager"
            p:enableFlowOnAbsentServiceRequest="${create.sso.missing.service:true}"  />
    
    ---------'argumentExtractors'
    其中'argumentExtractors'配置文件在/WEB-INF/spring-configuration/argumentExtractorsConfiguration.xml           
       <bean id="casArgumentExtractor"  class="org.jasig.cas.web.support.CasArgumentExtractor"/>
       <util:list id="argumentExtractors">
            <!-- <ref bean="samlArgumentExtractor" /> -->
            <ref bean="casArgumentExtractor"/>
        </util:list>           
    
    ------'warnCookieGenerator'
    其中warnCookieGenerator的配置文件在/WEB-INF/spring-configuration/warnCookieGenerator.xml
        <bean id="warnCookieGenerator" class="org.jasig.cas.web.support.CookieRetrievingCookieGenerator"
              p:cookieSecure="true"
              p:cookieMaxAge="-1"
              p:cookieName="CASPRIVACY"
              p:cookiePath="/cas"/>
    
    ------'ticketGrantingTicketCookieGenerator'
    其中ticketGrantingTicketCookieGenerator的配置文件在/WEB-INF/spring-configuration/ticketGrantingTicketCookieGenerator.xml
    <bean id="ticketGrantingTicketCookieGenerator"
              class="org.jasig.cas.web.support.CookieRetrievingCookieGenerator"
              c:casCookieValueManager-ref="cookieValueManager"
              p:cookieSecure="false"
              p:cookieMaxAge="-1"
              p:cookieName="TGC"
              p:cookiePath="/cas"/>
              
    初始化部分会调用initialFlowSetupAction的doExecute方法,如果有特殊需求,可以在此方法中增加相应的逻辑。
    -->InitialFlowSetupAction--doExecute
    
     /**
      * doExecute的目的就是把ticketGrantingTicketId,warnCookieValue和service放到FlowScope的作用域
      * 以便在登陆流程中的state进行判断,初始化完成后,登陆流程转到第一个state-->ticketGrantingTicketExistsCheck
      */
     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;
            }
            //获取并设置ticketGrantingTicketId,即TGT,用于证明用户已经登录
            WebUtils.putTicketGrantingTicketInScopes(context, this.ticketGrantingTicketCookieGenerator.retrieveCookieValue(request));
            //将warnCookieValue放在FlowScope中
            WebUtils.putWarningCookie(context, Boolean.valueOf(this.warnCookieGenerator.retrieveCookieValue(request)));
            //获取service参数,即用户重定向/cas/login之前访问的URL
            final Service service = WebUtils.getService(this.argumentExtractors, context);
            if (service != null) {
                logger.debug("Placing service in context scope: [{}]", service.getId());
                final RegisteredService registeredService = this.servicesManager.findServiceBy(service);
                if (registeredService != null && registeredService.getAccessStrategy().isServiceAccessAllowed()) {
                    logger.debug("Placing registered service [{}] with id [{}] in context scope",
                            registeredService.getServiceId(),
                            registeredService.getId());
                    WebUtils.putRegisteredService(context, registeredService);
                }
            } else if (!this.enableFlowOnAbsentServiceRequest) {
                logger.warn("No service authentication request is available at [{}]. CAS is configured to disable the flow.",
                        WebUtils.getHttpServletRequest(context).getRequestURL());
                throw new NoSuchFlowExecutionException(context.getFlowExecutionContext().getKey(),
                        new UnauthorizedServiceException("screen.service.required.message", "Service is required"));
            }
            //将service放在FlowScope的作用域中
            WebUtils.putService(context, service);
            //成功初始化,进入下一个流程
            return result("success");
        }
        
    ----初始化完成后,登陆流程转到第一个state,ticketGrantingTicketCheck /WEB-INF/webflow/login/login-webfolw.xml
         <action-state id="ticketGrantingTicketCheck">
            <evaluate expression="ticketGrantingTicketCheckAction"/>
            <transition on="notExists" to="gatewayRequestCheck"/>
            <transition on="invalid" to="terminateSession"/>
            <transition on="valid" to="hasServiceCheck"/>
        </action-state>
        
        
     --->TicketGrantingTicketCheckAction-->doExecute   
        @Override
        protected Event doExecute(final RequestContext requestContext) throws Exception {
            final String tgtId = WebUtils.getTicketGrantingTicketId(requestContext);
            if (!StringUtils.hasText(tgtId)) {
                return new Event(this, NOT_EXISTS);
            }
            String eventId = INVALID;
            try {
                final Ticket ticket = this.centralAuthenticationService.getTicket(tgtId, Ticket.class);
                if (ticket != null && !ticket.isExpired()) {
                    eventId = VALID;
                }
            } catch (final TicketException e) {
                logger.trace("Could not retrieve ticket id {} from registry.", e);
            }
            return new Event(this,  eventId);
        }
      当我们第一次访问集成了CAS单点登录的应用系统WEBAPP1时(http://127.0.0.1:8090/webapp1/main.do) 此时应系统会跳转到CAS的单点登录
      的服务器端(http://127.0.0.1:/8090/cas-server/login?service=http://127.0.0.1:8090/webapp1/main.do)
      由于此时request的cookie中不存在CASTGC(TGT),因此FlowScope作用域中的ticketGrantingTicketId为null
      所以我们将流程转到第二个state(gatewayRequestCheck)
       
      <decision-state id="gatewayRequestCheck">  
        <if test="requestParameters.gateway != '' and requestParameters.gateway != null and flowScope.service != null"   
            then="gatewayServicesManagementCheck" else="serviceAuthorizationCheck" />  
      </decision-state>   
      在这个阶段,我们把service保存在了FlowScope作用域中,但request中的参数gateway不存在,登录流程流转到第三个节点serviceAuthorizationCheck
    
    ----登录流转到serviceAuthorizationCheck阶段
        //这步的目的就是,在登陆前做一个服务授权检查
        <action-state id="serviceAuthorizationCheck">
            <evaluate expression="serviceAuthorizationCheck"/>
            <transition to="generateLoginTicket"/>
        </action-state>   
    
      --->ServiceAuthorizationCheck-->doExecte--
       /** 目的就是判断FlowScope作用域是否存在service,如果service存在,查找service的注册信息。登录流程转到第四个state(generateLoginTicket) */
        @Override
        protected Event doExecute(final RequestContext context) throws Exception {
            final Service service = WebUtils.getService(context);
            //No service == plain /login request. Return success indicating transition to the login form
            if (service == null) { return success(); }
            if (this.servicesManager.getAllServices().isEmpty()) {
                final String msg = String.format("No service definitions are found in the service manager. "
                        + "Service [%s] will not be automatically authorized to request authentication.", service.getId());
                logger.warn(msg);
                throw new UnauthorizedServiceException(UnauthorizedServiceException.CODE_EMPTY_SVC_MGMR);
            }
            final RegisteredService registeredService = this.servicesManager.findServiceBy(service);
            if (registeredService == null) {
                final String msg = String.format("ServiceManagement: Unauthorized Service Access. "
                        + "Service [%s] is not found in service registry.", service.getId());
                logger.warn(msg);
                throw new UnauthorizedServiceException(UnauthorizedServiceException.CODE_UNAUTHZ_SERVICE, msg);
            }
            if (!registeredService.getAccessStrategy().isServiceAccessAllowed()) {
                final String msg = String.format("ServiceManagement: Unauthorized Service Access. "
                        + "Service [%s] is not enabled in service registry.", service.getId());
                logger.warn(msg);
                throw new UnauthorizedServiceException(UnauthorizedServiceException.CODE_UNAUTHZ_SERVICE, msg);
            }
            return success();
        }
    
    
    ---登录流转到:generateLoginTicket
       <action-state id="generateLoginTicket">
            <evaluate expression="generateLoginTicketAction.generate(flowRequestContext)"/>
            <transition on="generated" to="viewLoginForm"/>
       </action-state>
     '-----generateLoginTicket---/WEB-INF/cas-servlet.xml部分代码------'
       <bean id="generateLoginTicketAction" 
            class="org.jasig.cas.web.flow.GenerateLoginTicketAction"
            p:ticketIdGenerator-ref="loginTicketUniqueIdGenerator"/>
     '-----loginTicketUniqueIdGenerator---/WEB-INF/spring-configuration/uniquieIdGenerator.xml---' 
       <bean id="loginTicketUniqueIdGenerator"
              class="org.jasig.cas.util.DefaultUniqueTicketIdGenerator"
              c:maxLength="30"
              c:suffix="${host.name}" />
      DefaultUniqueTicketIdGenerator的作用就是生成以LT作为前缀的loginTicket(例如:LT-2-pfDmbEHfX2OkS0swLtDd7iDwmzlhsn),LT只作为登录使用的票据。
      //--->GenerateLoginTicketAction的generate方法,该方法的主要目的就是loginTicket放到FlowScope作用域中
      public final String generate(final RequestContext context) {
           //通过DefaultUniqueTicketGenerator生成loginTicket,可以通过实现实现UniqueTicketIdGenerator.java,来自定义生成loginTicket的格式
            final String loginTicket = this.ticketIdGenerator.getNewTicketId(PREFIX); //这里ticketIdGerator就是DefaultUniqueTicketIdGenerator
            logger.debug("Generated login ticket {}", loginTicket);
            //ticket放入FlowScope
            WebUtils.putLoginTicket(context, loginTicket);
            return "generated";
        }
        
    -----登录流转到: viewLoginForm   
        <view-state id="viewLoginForm" view="casLoginView" model="credential">
            <binder>
                <binding property="username" required="true"/>
                <binding property="password" required="true"/>
            </binder>
            <on-entry>
                <set name="viewScope.commandName" value="'credential'"/>
            </on-entry>
            <transition on="submit" bind="true" validate="true" to="realSubmit"/>
        </view-state>
        
      到此,经过5个state 的流转,就完成了第一次访问集成单点登录的应用系统,此时流转到CAS单点登录服务器的登录页面/WEB-INF/jsp/ui/default/casLoginView.jsp
      由于casLoginView.jsp是CAS提供的默认登录页面,需要把此页面修改为我们系统需要的登录页面,格式需要参考casLoginView.jsp;
      注意,默认的登录页面中有lt,execution和_eventId三个隐藏参数,It参数值就是GenerateLoginTicketAction的generate方法中生成的loginTicket.
      
    -----casLoginView.jsp
      <input type="hidden" name="lt" value="${loginTicket}" />
      <input type="hidden" name="execution" value="${flowExecutionKey}" />
      <input type="hidden" name="_eventId" value="submit" />
    
            
    

    cas 服务端的登录认证

    当我们在登录页面输入用户名和密码,点击登录后会执行AuthenticationViaFormAction的doBind方法
    ---配置文件/WEB-INF/cas-servlet.xml----
      <bean id="authenticationViaFormAction" 
            class="org.jasig.cas.web.flow.AuthenticationViaFormAction"
            p:centralAuthenticationService-ref="centralAuthenticationService"
            p:warnCookieGenerator-ref="warnCookieGenerator"/>
    
    --------流程跳转到---------------
       <action-state id="realSubmit">
            <evaluate expression="authenticationViaFormAction.submit(flowRequestContext, flowScope.credential, messageContext)"/>
            <transition on="warn" to="warn"/>
            <transition on="success" to="sendTicketGrantingTicket"/>
            <transition on="successWithWarnings" to="showMessages"/>
            <transition on="authenticationFailure" to="handleAuthenticationFailure"/>
            <transition on="error" to="generateLoginTicket"/>
        </action-state>
        
      ---->AuthenticationViaFormAction的---submit方法---- 
        public final Event submit(final RequestContext context, final Credential credential,
                                  final MessageContext messageContext)  {
            //判断FlowScope和request域中的loginTicket是否相同
            if (!checkLoginTicketIfExists(context)) {
                return returnInvalidLoginTicketEvent(context, messageContext);
            }
            //根据用户凭证生成TGT(登录成功票据),并放到requestScope作用域中,同时把TGT缓存到服务器cache<ticketId,TGT>中
            if (isRequestAskingForServiceTicket(context)) {
                return grantServiceTicket(context, credential);
            }
            return createTicketGrantingTicket(context, credential, messageContext);
        }  
      ----登录流程跳转到第二个state-----           
         <action-state id="sendTicketGrantingTicket">
            <evaluate expression="sendTicketGrantingTicketAction"/>
            <transition to="serviceCheck"/>
         </action-state>   
         
       --->SendTicketGrantingTicketAction的doExecute的方法
        /*** SendTicketGrantingTicketAction的要做的获取TGT,并根据TGT生成cookie添加到response*/
        protected Event doExecute(final RequestContext context) {
            //获取requestScope和FlowScope中的TGT
            final String ticketGrantingTicketId = WebUtils.getTicketGrantingTicketId(context);
            final String ticketGrantingTicketValueFromCookie = (String) context.getFlowScope().get("ticketGrantingTicketId");
            if (ticketGrantingTicketId == null) {   return success();  }
            if (isAuthenticatingAtPublicWorkstation(context))  {
                LOGGER.info("Authentication is at a public workstation. "
                        + "SSO cookie will not be generated. Subsequent requests will be challenged for authentication.");
            } else if (!this.createSsoSessionCookieOnRenewAuthentications && isAuthenticationRenewed(context)) {
                LOGGER.info("Authentication session is renewed but CAS is not configured to create the SSO session. "
                        + "SSO cookie will not be generated. Subsequent requests will be challenged for authentication.");
            } else {
                //response中添加TGC
                this.ticketGrantingTicketCookieGenerator.addCookie(WebUtils.getHttpServletRequest(context), WebUtils
                    .getHttpServletResponse(context), ticketGrantingTicketId);
            }
            if (ticketGrantingTicketValueFromCookie != null && !ticketGrantingTicketId.equals(ticketGrantingTicketValueFromCookie)) {
                this.centralAuthenticationService.destroyTicketGrantingTicket(ticketGrantingTicketValueFromCookie);
            }
            return success();
        }
    ------登录流程跳转到第三个state            
        <decision-state id="serviceCheck">
            <if test="flowScope.service != null" then="generateServiceTicket" else="viewGenericLoginSuccess"/>
        </decision-state>
        此时flowScope中存在service(http://127.0.0.1:8081/cas-server/login?service=http://127.0.0.1:8090/webapp1/main.do)
    ------登录流程跳转到第四个state
         <action-state id="generateServiceTicket">
            <evaluate expression="generateServiceTicketAction"/>
            <transition on="success" to="warn"/>
            <transition on="authenticationFailure" to="handleAuthenticationFailure"/>
            <transition on="error" to="generateLoginTicket"/>
            <transition on="gateway" to="gatewayServicesManagementCheck"/>
        </action-state>
        
       /**
        * GenerateServiceTicketAction的doExecute要做的是获取service和TGT
        * 并根据service和TGT生成以ST为前缀的serviceTicket,并把serviceTicket放到requestScope中
        * */
        @Override
        protected Event doExecute(final RequestContext context) {
            //获取service
            final Service service = WebUtils.getService(context);
            //获取TGT
            final String ticketGrantingTicket = WebUtils.getTicketGrantingTicketId(context);
            try {
                final Credential credential = WebUtils.getCredential(context);
                //根据TGT和service ticket(ST-2-97kwhcdrBW97ynpBbZH5-cas01.example.org)
                final ServiceTicket serviceTicketId = this.centralAuthenticationService
                    .grantServiceTicket(ticketGrantingTicket, service, credential);
                //ST放到requestScope中
                WebUtils.putServiceTicketInRequestScope(context, serviceTicketId);
                return success();
            } catch (final AuthenticationException e) {
                logger.error("Could not verify credentials to grant service ticket", e);
            } catch (final TicketException e) {
                if (e instanceof InvalidTicketException) {
                    this.centralAuthenticationService.destroyTicketGrantingTicket(ticketGrantingTicket);
                }
                if (isGatewayPresent(context)) {
                    return result("gateway");
                }
            }
            return error();
        }
        
    ------登录流程跳转到第五个state            
          <decision-state id="warn">
            <if test="flowScope.warnCookieValue" then="showWarningView" else="redirect"/>
          </decision-state>      
          由于此时FlowScope中不存在warnCookieValue,所以跳转到redirect
    
    ------登录流程跳转到第六个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>
        在这一步从reuqestScope中获取serviceTicket,构造response对象,并把response,放到requestScope中
        
    -----登录流程流转到第七个state(postRedirectDecision)
         <decision-state id="postRedirectDecision">
            <if test="requestScope.response.responseType.name() == 'POST'" then="postView" else="redirectView"/>
         </decision-state>
         由于request请求(http://127.0.0.1:8081/cas-server/login?server=http://127.0.0.1:8090/webapp1/main.do)是get类型
         登录流程流转到第八个state(redirectView)
         <end-state id="redirectView" view="externalRedirect:#{requestScope.response.url}"/>
             
    ----------
         此时流程如下:
          > 跳转到应用系统(http://127.0.0.1:8090/webapp1/main.do?ticket=ST-1-4hH2s5tzsMGCcToDvGCb-cas01.example.org)
          > 进入cas客户端的AuthenticationFilter获取器,由于session中获取名为"_const_cas_assertion_"的assert对象不存在,但request有ticket,所以进入下一个过滤器
          > TicketValidationFilter过滤器的validate方法通过httpclient访问CAS服务器
             http://127.0.0.1:8081/cas-server/serviceValidate?ticket=ST-1-4hHxxxxcas01.example.org&service=http://127.0.0.1:8090/webapp1/main.do
             验证ticket是否正确,并返回assertion对象。
             
    ---------Assertion对象的格式类似
         <cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>  
             <cas:authenticationSuccess>  
                <cas:user>system</cas:user>  
             </cas:authenticationSuccess>      
         </cas:serviceResponse>  
    

    访问集成了CAS单点登录的应用系统webApp2

    当我们第一次访问集成了CAS单点登录的应用系统webapp2时(http://127.0.0.1:8091/webapp2/main.do),此时应用系统会跳转到CAS单点登录的
    服务器(http://127.0.0.1:8081/cas-server/login?service=http://127.0.0.1:8091/webapp2/main.do)
       InitialFlowStepAction的doExecutor初始化完成后,登录流程流转到第一个state
    ------登录流程跳转到第一个state:ticketGrantingTicketExistsCheck
      <decision-state id="ticketGrantingTicketExistsCheck">  
        <if test="flowScope.ticketGrantingTicketId != null" then="hasServiceCheck" else="gatewayRequestCheck" />  
      </decision-state> 
      因为应用系统webapp1已经成功登录,所以request的cookies中存在TGT,并保存到FlowScope中登录流转到第二个state
      
    ------登录流转到第二个state:hasServiceCheck
      <decision-state id="hasServiceCheck">  
        <if test="flowScope.service != null" then="renewRequestCheck" else="viewGenericLoginSuccess" />  
      </decision-state>  
      FlowScope中存在service,登录流转到第三个state(renewRequestCheck)
         
    ------登录流转到第二个state:hasServiceCheck
      <decision-state id="hasServiceCheck">  
        <if test="flowScope.service != null" then="renewRequestCheck" else="viewGenericLoginSuccess" />  
      </decision-state>  
    ------登录流转到第三个state:renewRequestCheck
       <decision-state id="renewRequestCheck">  
           <if test="requestParameters.renew != '' and requestParameters.renew != null"   
            then="serviceAuthorizationCheck" else="generateServiceTicket" />  
       </decision-state>  
    
    -------request中不存在renew,登录流程流转到第四个state:generateServiceTicket
       <action-state id="generateServiceTicket">  
        <evaluate expression="generateServiceTicketAction" />  
        <transition on="success" to ="warn" />  
        <transition on="error" to="generateLoginTicket" />  
        <transition on="gateway" to="gatewayServicesManagementCheck" />  
       </action-state>  
    ----后续的流转与应用系统webapp1相同---    
    
  • 相关阅读:
    jQuery教程
    AJAX请求 $.ajax方法的使用
    smarty block_function
    smarty
    位运算版本的交换两数
    提取字符串中的数字
    vue地址插件多级联动自适应 + github地址
    vue插件
    网页title旁边的小图片
    二十三种设计模式[14]
  • 原文地址:https://www.cnblogs.com/ssgao/p/8817155.html
Copyright © 2020-2023  润新知