CAS4.0 自定义客服端登陆界面

来源:互联网 发布:第六届软件杯 编辑:程序博客网 时间:2024/05/29 12:32

关于cas基本搭建,就不再描述了,随便一搜一大堆。我这里只关注后期的修正,当有这样一个需求:
当你登录系统软件时,不希望跳转到cas默认的页面去做登录。你希望在自己的子系统的登录页面进入。
我这里用服务端是cas4.0版本,客服端是3.1.12 其实服务器的版本之间差别还是有很多的。

实现原理:
一、 逻辑
1,客户端修改CAS Authentication Filter过滤器,该过滤器会判断用户是否登录,如果没有登录则跳转到自身配置的登录界面;
2,用户输入正确的信息,登录时,会被提交到服务端的登录流程中;
3,服务端通过新增一个remoteLogin处理类,专门处理客户端自定义登录业务;
4,该remoteLogin处理类与原始的login处理极为类似,只是修改了获取用户名与密码的方式;
5,如果用户名与密码不匹配,校验失败,会通过remoteCallbackView.jsp界面将错误提示与service一并响应给客户端的登录界面,接收之后将错误提示显示到界面上;
6,如果用户名与密码匹配,校验成功,会直接重定向到客户端的service页面,这时CAS Authentication Filter过滤器与CAS Validation Filter过滤器分别校验用户是否登录与ticket票据是否正确,完成最后的校验,否则要求用户重新登录。
二、 修改点
客户端
1. 修改web.xml,修改原先的CAS Authentication Filter过滤器
2. 新增RemoteAuthenticationFilter过滤器
3. 新增login.jsp
服务端
1. 修改web.xml,给cas过滤器添加remoteLogin servlet-mapping映射
2. 新增RemoteLoginAction登录处理类与AuthenticationViaFormAction表单处理类
3. 新增remoteLogin-webflow.xml自定义登录webflow流程文件
4. 修改cas-servlet.xml配置文件,新增一些bean配置
5. 新增remoteCallbackView.jsp响应界面,校验错误时用来通知客户端登录界面
客户端篇
1.替换原来过滤器org.jasig.cas.client.authentication.AuthenticationFilter,改成自己的过滤器RemoteAuthenticationFilter.java,参照原始的页面去修改,这个过滤器可以自己随便放到哪个包中,保证web.xml能够正确引用到就行:

package com.xb.login;import java.io.IOException;import java.net.URL;import java.net.URLEncoder;import javax.servlet.FilterChain;import javax.servlet.FilterConfig;import javax.servlet.ServletException;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpSession;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.jasig.cas.client.util.AbstractCasFilter;import org.jasig.cas.client.util.CommonUtils;import org.jasig.cas.client.validation.Assertion;/**  * qzg 17.11.01 */ public class RemoteAuthenticationFilter extends AbstractCasFilter  {      public static final String CONST_CAS_GATEWAY = "_const_cas_gateway_";      /**      * 本地登陆页面URL.      */      private String localLoginUrl;      /**      * The URL to the CAS Server login.      */      private String casServerLoginUrl;      /**      * Whether to send the renew request or not.      */      private boolean renew = false;      /**      * Whether to send the gateway request or not.      */      private boolean gateway = false;      /** Instance of commons logging for logging purposes. */    protected final Log log = LogFactory.getLog(getClass());    protected void initInternal(final FilterConfig filterConfig)              throws ServletException      {          super.initInternal(filterConfig);          setCasServerLoginUrl(getPropertyFromInitParams(filterConfig,                  "casServerLoginUrl", null));          log.trace("Loaded CasServerLoginUrl parameter: "                  + this.casServerLoginUrl);          setLocalLoginUrl(getPropertyFromInitParams(filterConfig,                  "localLoginUrl", null));          log.trace("Loaded LocalLoginUrl parameter: " + this.localLoginUrl);          setRenew(Boolean.parseBoolean(getPropertyFromInitParams(filterConfig,                  "renew", "false")));          log.trace("Loaded renew parameter: " + this.renew);          setGateway(Boolean.parseBoolean(getPropertyFromInitParams(filterConfig,                  "gateway", "false")));          log.trace("Loaded gateway parameter: " + this.gateway);      }      public void init()      {          super.init();          CommonUtils.assertNotNull(this.localLoginUrl,                  "localLoginUrl cannot be null.");          CommonUtils.assertNotNull(this.casServerLoginUrl,                  "casServerLoginUrl cannot be null.");      }      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 String ticket = request.getParameter(getArtifactParameterName());          final Assertion assertion = session != null ? (Assertion) session                  .getAttribute(CONST_CAS_ASSERTION) : null;          final boolean wasGatewayed = session != null                  && session.getAttribute(CONST_CAS_GATEWAY) != null;          // 如果访问路径为localLoginUrl且带有validated参数则跳过          URL url = new URL(localLoginUrl);          final boolean isValidatedLocalLoginUrl = request.getRequestURI()                  .endsWith(url.getPath())                  && CommonUtils.isNotBlank(request.getParameter("validated"));        if (!isValidatedLocalLoginUrl && CommonUtils.isBlank(ticket)                  && assertion == null && !wasGatewayed)          {            log.debug("no ticket and no assertion found");              if (this.gateway)  {                  log.debug("setting gateway attribute in session");                  request.getSession(true).setAttribute(CONST_CAS_GATEWAY, "yes");              }              final String serviceUrl = constructServiceUrl(request, response);              if (log.isDebugEnabled())  {                  log.debug("Constructed service url: " + serviceUrl);              }              String urlToRedirectTo = CommonUtils.constructRedirectUrl(                      this.casServerLoginUrl, getServiceParameterName(),                      serviceUrl, this.renew, this.gateway);              // 加入localLoginUrl              urlToRedirectTo += (urlToRedirectTo.contains("?") ? "&" : "?")                      + "loginUrl=" + URLEncoder.encode(localLoginUrl, "utf-8");              if (log.isDebugEnabled())              {                  log.debug("redirecting to \"" + urlToRedirectTo + "\"");              }              response.sendRedirect(urlToRedirectTo);              return;          }          if (session != null)          {              log.debug("removing gateway attribute from session");              session.setAttribute(CONST_CAS_GATEWAY, null);          }          System.out.println("---------end---2---#");          filterChain.doFilter(request, response);      }      public final void setRenew(final boolean renew)      {          this.renew = renew;      }      public final void setGateway(final boolean gateway)      {          this.gateway = gateway;      }      public final void setCasServerLoginUrl(final String casServerLoginUrl)      {          this.casServerLoginUrl = casServerLoginUrl;      }      public final void setLocalLoginUrl(String localLoginUrl)      {          this.localLoginUrl = localLoginUrl;      }  }  

2 web.xml中配置:
旧的:<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>
<param-value>https://localhost:8443/cas/login</param-value>
</init-param>
<init-param>
<param-name>serverName</param-name>
<param-value>http://localhost:8080</param-value>
</init-param>
<init-param>
<param-name>renew</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>gateway</param-name>
<param-value>false</param-value>
</init-param>
</filter>

修改后:

     <filter>          <filter-name>CASFilter</filter-name>          <filter-class>com.xb.login.RemoteAuthenticationFilter</filter-class>          <init-param>              <param-name>localLoginUrl</param-name>              <param-value>http://127.0.0.1:8080/casClient/login.jsp</param-value>          </init-param>          <init-param>              <param-name>casServerLoginUrl</param-name>              <param-value>https://sso.castest.com:8443/cas/remoteLogin</param-value>          </init-param>          <init-param>              <param-name>serverName</param-name>              <param-value>http://127.0.0.1:8080</param-value>          </init-param>      </filter>          <filter-mapping>                <filter-name>CASFilter</filter-name>                <url-pattern>/pages/*</url-pattern>        </filter-mapping>        <filter-mapping>                <filter-name>CASFilter</filter-name>                <url-pattern>*.action</url-pattern>        </filter-mapping>

3.加上你自己定义的登录界面,就是子系统的login.jsp

    <%@ page language="java" contentType="text/html; charset=utf-8"          pageEncoding="utf-8"%>      <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">   <html>      <head>   <meta http-equiv="Content-Type" content="text/html; charset=utf-8">      <title>系统1客户端登录</title>      <script type="text/javascript">          function getParam(name)          {              var queryString = window.location.search;              var param = queryString.substr(1, queryString.length - 1).split("&");              for (var i = 0; i < param.length; i++)              {                  var keyValue = param[i].split("=");                  if (keyValue[0] == name)                      return keyValue[1];              }              return null;          }          function init()          {              // 显示异常信息              var error = getParam("errorMessage");              if (error)              {                  document.getElementById("errorMessage").innerHTML = decodeURIComponent(error);              }              // 注入service              var service = getParam("service");              console.log("service:"+service);            if (service)                  document.getElementById("service").value = decodeURIComponent(service);              else                  document.getElementById("service").value = location.href;              var loginTicket = getParam("loginTicket");              console.log("loginTicket:"+loginTicket);            if (loginTicket)                  document.getElementById("loginTicket").value = decodeURIComponent(loginTicket);              else                  document.getElementById("loginTicket").value = '';          // 这个参数在4.0之前的版本没有,搞了我好久。。。。            var flowExecutionKey = getParam("flowExecutionKey");              console.log("flowExecutionKey:"+flowExecutionKey);            if (flowExecutionKey)                  document.getElementById("execution").value = decodeURIComponent(flowExecutionKey);              else                  document.getElementById("execution").value = '';        }      </script>      </head>      <body>          <h1>系统1客户端登录</h1>          <div id="errorMessage" style="color: red;"></div>          <form id="myLoginForm" action="https://sso.castest.com:8443/cas/remoteLogin"              method="post">               <input type="hidden" name="service" value="http://127.0.0.1:8080/casClient/index.jsp">              <input type="hidden" name="loginUrl" value="http://127.0.0.1:8080/casClient/login.jsp">              <input type="hidden" name="submit" value="LOGIN" />             <input type="hidden" name="msubmit" value="true" />               <input type="hidden" name="lt" id="loginTicket" value="" />              <input type="hidden" name="execution" id="execution" value="" />              <input type="hidden" name="service" id="service" value="" />             <input type="hidden" name="_eventId" value="submit" />            <table>                  <tr>                      <td>用户名:</td>                      <td><input type="text" name="username"></td>                  </tr>                  <tr>                      <td>密&nbsp;&nbsp;码:</td>                      <td><input type="password" name="password"></td>                  </tr>                  <tr>                      <td colspan="2"><input type="submit" value="登陆" /></td>                  </tr>              </table>          </form>  <a href="https://sso.castest.com:8443/cas/logout">注销登录</a>        <script type="text/javascript">              init()          </script>      </body>      </html>  

客户端完成改造。
服务端篇
1.添加客户端登录Action,org.jasig.cas.web.flow.RemoteLoginAction:一样的是参照源码修改的。可以先看看源码再去修改。

package com.xb.casLogin;import java.text.SimpleDateFormat;import java.util.List;import javax.servlet.http.HttpServletRequest;import javax.validation.constraints.NotNull;import javax.validation.constraints.Size;import org.jasig.cas.authentication.principal.Service;import org.jasig.cas.web.support.ArgumentExtractor;import org.jasig.cas.web.support.CookieRetrievingCookieGenerator;import org.jasig.cas.web.support.WebUtils;import org.springframework.util.StringUtils;import org.springframework.webflow.action.AbstractAction;import org.springframework.webflow.execution.Event;import org.springframework.webflow.execution.RequestContext;/**  *  * org.jasig.cas.web.flow.RemoteLoginAction: * 远程登陆票据提供Action. 根据InitialFlowSetupAction修改.  * 由于InitialFlowSetupAction为final类,因此只能将代码复制过来再进行修改.  */  public class RemoteLoginAction extends AbstractAction  {      /** CookieGenerator for the Warnings. */      @NotNull      private CookieRetrievingCookieGenerator warnCookieGenerator;      /** CookieGenerator for the TicketGrantingTickets. */      @NotNull      private CookieRetrievingCookieGenerator ticketGrantingTicketCookieGenerator;      /** Extractors for finding the service. */      @NotNull      @Size(min = 1)      private List<ArgumentExtractor> argumentExtractors;      /** Boolean to note whether we've set the values on the generators or not. */      private boolean pathPopulated = false;      protected Event doExecute(final RequestContext context) throws Exception      {          SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");        String timetodaypi= sdf.format(new java.util.Date());        System.out.println(timetodaypi+"----------------------------Relogin------go--------------------------------------------------");        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;        }        context.getFlowScope().put(            "ticketGrantingTicketId", this.ticketGrantingTicketCookieGenerator.retrieveCookieValue(request));        context.getFlowScope().put(            "warnCookieValue",            Boolean.valueOf(this.warnCookieGenerator.retrieveCookieValue(request)));        // 存放service url          //context.getFlowScope().put("serviceUrl", request.getParameter("service"));          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);          // 客户端必须传递loginUrl参数过来,否则无法确定登陆目标页面    loginUrl        System.out.println("------loginUrl--"+request.getParameter("loginUrl"));        if (StringUtils.hasText(request.getParameter("loginUrl")))          {              context.getFlowScope().put("remoteLoginUrl",                      request.getParameter("loginUrl"));          } else          {              request.setAttribute("remoteLoginMessage",                      "loginUrl parameter must be supported.");              return result("error");        }          // 若参数包含submit则进行提交,否则进行验证           if (StringUtils.hasText(request.getParameter("msubmit")))          {              return result("submit");          } else          {              return result("checkTicketGrantingTicket");          }     }      public void setTicketGrantingTicketCookieGenerator(              final CookieRetrievingCookieGenerator ticketGrantingTicketCookieGenerator)      {          this.ticketGrantingTicketCookieGenerator = ticketGrantingTicketCookieGenerator;      }      public void setWarnCookieGenerator(              final CookieRetrievingCookieGenerator warnCookieGenerator)      {          this.warnCookieGenerator = warnCookieGenerator;      }      public void setArgumentExtractors(              final List<ArgumentExtractor> argumentExtractors)      {          this.argumentExtractors = argumentExtractors;      }  }  

2 web.xml配置,

<servlet-mapping>       <servlet-name>cas</servlet-name>       <url-pattern>/remoteLogin</url-pattern>   </servlet-mapping> 

3 cas-servlet.xml中最后面增加以下信息

<?xml version="1.0" encoding="UTF-8"?><!--    Licensed to Jasig under one or more contributor license    agreements. See the NOTICE file distributed with this work    for additional information regarding copyright ownership.    Jasig licenses this file to you under the Apache License,    Version 2.0 (the "License"); you may not use this file    except in compliance with the License.  You may obtain a    copy of the License at the following location:      http://www.apache.org/licenses/LICENSE-2.0    Unless required by applicable law or agreed to in writing,    software distributed under the License is distributed on an    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY    KIND, either express or implied.  See the License for the    specific language governing permissions and limitations    under the License.--><beans xmlns="http://www.springframework.org/schema/beans"       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"       xmlns:webflow="http://www.springframework.org/schema/webflow-config"       xmlns:p="http://www.springframework.org/schema/p"       xmlns:c="http://www.springframework.org/schema/c"       xmlns:util="http://www.springframework.org/schema/util"       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       http://www.springframework.org/schema/webflow-config http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.3.xsd">  <import resource="spring-configuration/propertyFileConfigurer.xml"/>  <!-- Theme Resolver -->  <bean id="themeResolver" class="org.jasig.cas.services.web.ServiceThemeResolver"        p:defaultThemeName="${cas.themeResolver.defaultThemeName}"        p:argumentExtractors-ref="argumentExtractors"        p:servicesManager-ref="servicesManager">    <property name="mobileBrowsers">      <util:map>        <entry key=".*iPhone.*" value="iphone"/>        <entry key=".*Android.*" value="iphone"/>        <entry key=".*Safari.*Pre.*" value="iphone"/>        <entry key=".*Nokia.*AppleWebKit.*" value="iphone"/>      </util:map>    </property>  </bean>  <!-- View Resolver -->  <bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver"        p:order="0">    <property name="basenames">      <util:list>        <value>${cas.viewResolver.basename}</value>        <value>protocol_views</value>      </util:list>    </property>  </bean>  <!-- Locale Resolver -->  <bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver" p:defaultLocale="en" />  <bean id="localeChangeInterceptor" class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"/>  <bean id="urlBasedViewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver"        p:viewClass="org.springframework.web.servlet.view.InternalResourceView"        p:prefix="/WEB-INF/view/jsp/"        p:suffix=".jsp"        p:order="1"/>  <bean id="errorHandlerResolver" class="org.jasig.cas.web.FlowExecutionExceptionResolver"/>  <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>  <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>        <prop key="/status">healthCheckController</prop>        <prop key="/statistics">statisticsController</prop>      </util:properties>    </property>    <!--     uncomment this to enable sending PageRequest events.     <property       name="interceptors">       <list>         <ref bean="pageRequestHandlerInterceptorAdapter" />       </list>     </property>      -->  </bean>  <bean id="passThroughController" class="org.springframework.web.servlet.mvc.UrlFilenameViewController"/>  <!-- login webflow configuration -->  <bean id="loginFlowHandlerMapping" class="org.springframework.webflow.mvc.servlet.FlowHandlerMapping"        p:flowRegistry-ref="loginFlowRegistry" p:order="2">    <property name="interceptors">      <ref local="localeChangeInterceptor" />    </property>  </bean>  <bean id="loginHandlerAdapter" class="org.jasig.cas.web.flow.SelectiveFlowHandlerAdapter"        p:supportedFlowId="login" p:flowExecutor-ref="loginFlowExecutor" p:flowUrlHandler-ref="loginFlowUrlHandler" />  <bean id="loginFlowUrlHandler" class="org.jasig.cas.web.flow.CasDefaultFlowUrlHandler" />  <webflow:flow-executor id="loginFlowExecutor" flow-registry="loginFlowRegistry">    <webflow:flow-execution-attributes>      <webflow:always-redirect-on-pause value="false" />      <webflow:redirect-in-same-state value="false" />    </webflow:flow-execution-attributes>    <webflow:flow-execution-listeners>      <webflow:listener ref="terminateWebSessionListener" />    </webflow:flow-execution-listeners>  </webflow:flow-executor>  <webflow:flow-registry id="loginFlowRegistry" flow-builder-services="builder">    <webflow:flow-location path="/WEB-INF/login-webflow.xml" id="login" />  </webflow:flow-registry>  <!-- logout webflow configuration -->  <bean id="logoutFlowHandlerMapping" class="org.springframework.webflow.mvc.servlet.FlowHandlerMapping"        p:flowRegistry-ref="logoutFlowRegistry" p:order="3">    <property name="interceptors">      <ref local="localeChangeInterceptor" />    </property>  </bean>  <bean id="logoutHandlerAdapter" class="org.jasig.cas.web.flow.SelectiveFlowHandlerAdapter"        p:supportedFlowId="logout" p:flowExecutor-ref="logoutFlowExecutor" p:flowUrlHandler-ref="logoutFlowUrlHandler" />  <bean id="logoutFlowUrlHandler" class="org.jasig.cas.web.flow.CasDefaultFlowUrlHandler"        p:flowExecutionKeyParameter="RelayState" />  <webflow:flow-executor id="logoutFlowExecutor" flow-registry="logoutFlowRegistry">    <webflow:flow-execution-attributes>      <webflow:always-redirect-on-pause value="false" />      <webflow:redirect-in-same-state value="false" />    </webflow:flow-execution-attributes>    <webflow:flow-execution-listeners>      <webflow:listener ref="terminateWebSessionListener" />    </webflow:flow-execution-listeners>  </webflow:flow-executor>  <webflow:flow-registry id="logoutFlowRegistry" flow-builder-services="builder">    <webflow:flow-location path="/WEB-INF/logout-webflow.xml" id="logout" />  </webflow:flow-registry>  <webflow:flow-builder-services id="builder" view-factory-creator="viewFactoryCreator" expression-parser="expressionParser" />  <bean id="logoutConversionService" class="org.jasig.cas.web.flow.LogoutConversionService" />  <bean id="terminateWebSessionListener" class="org.jasig.cas.web.flow.TerminateWebSessionListener"   p:timeToDieInSeconds="10"/>  <bean id="expressionParser" class="org.springframework.webflow.expression.spel.WebFlowSpringELExpressionParser"        c:conversionService-ref="logoutConversionService">    <constructor-arg>        <bean class="org.springframework.expression.spel.standard.SpelExpressionParser" />    </constructor-arg>  </bean>  <bean id="viewFactoryCreator" class="org.springframework.webflow.mvc.builder.MvcViewFactoryCreator">    <property name="viewResolvers">      <util:list>        <ref local="viewResolver"/>      </util:list>    </property>  </bean>  <!--  CAS 2 Protocol service/proxy validation -->       <bean id="abstractValidateController" class="org.jasig.cas.web.ServiceValidateController" abstract="true"        p:centralAuthenticationService-ref="centralAuthenticationService"        p:proxyHandler-ref="proxy20Handler"        p:argumentExtractor-ref="casArgumentExtractor"/>  <bean id="proxyValidateController" parent="abstractValidateController"/>  <bean id="serviceValidateController" parent="abstractValidateController"        p:validationSpecificationClass="org.jasig.cas.validation.Cas20WithoutProxyingValidationSpecification"/>  <!--  CAS 3 Protocol service/proxy validation with attributes -->  <bean id="v3AbstractValidateController" parent="abstractValidateController" abstract="true"        p:successView="cas3ServiceSuccessView"        p:failureView="cas3ServiceFailureView" />  <bean id="v3ProxyValidateController" parent="v3AbstractValidateController" />  <bean id="v3ServiceValidateController" parent="v3AbstractValidateController"        p:validationSpecificationClass="org.jasig.cas.validation.Cas20WithoutProxyingValidationSpecification"/>  <!--  CAS 1 legacy validation -->         <bean id="legacyValidateController" parent="abstractValidateController"        p:proxyHandler-ref="proxy10Handler"        p:successView="cas1ServiceSuccessView"        p:failureView="cas1ServiceFailureView"        p:validationSpecificationClass="org.jasig.cas.validation.Cas10ProtocolValidationSpecification"/>  <bean id="proxyController" class="org.jasig.cas.web.ProxyController"        p:centralAuthenticationService-ref="centralAuthenticationService"/>  <bean id="statisticsController" class="org.jasig.cas.web.StatisticsController"        p:casTicketSuffix="${host.name}" c:ticketRegistry-ref="ticketRegistry" />  <bean id="logoutAction" class="org.jasig.cas.web.flow.LogoutAction"        p:servicesManager-ref="servicesManager"        p:followServiceRedirects="${cas.logout.followServiceRedirects:false}"/>  <bean id="frontChannelLogoutAction" class="org.jasig.cas.web.flow.FrontChannelLogoutAction"        c:logoutManager-ref="logoutManager"/>  <bean id="healthCheckController" class="org.jasig.cas.web.HealthCheckController"        p:healthCheckMonitor-ref="healthCheckMonitor"/><!-- add org.jasig.cas.web.flow.InitialFlowSetupActioncom.xb.casLogin -->  <bean id="initialFlowSetupAction" class="org.jasig.cas.web.flow.InitialFlowSetupAction"        p:argumentExtractors-ref="argumentExtractors"        p:warnCookieGenerator-ref="warnCookieGenerator"        p:ticketGrantingTicketCookieGenerator-ref="ticketGrantingTicketCookieGenerator"/><!-- add1107 org.jasig.cas.web.flow.AuthenticationViaFormActioncom.xb.casLogin.AuthenticationViaFormAction-->  <bean id="authenticationViaFormAction" class="org.jasig.cas.web.flow.AuthenticationViaFormAction"        p:centralAuthenticationService-ref="centralAuthenticationService"        p:warnCookieGenerator-ref="warnCookieGenerator"        p:ticketRegistry-ref="ticketRegistry"/>  <bean id="authenticationExceptionHandler" class="org.jasig.cas.web.flow.AuthenticationExceptionHandler" />  <bean id="generateServiceTicketAction" class="org.jasig.cas.web.flow.GenerateServiceTicketAction"        p:centralAuthenticationService-ref="centralAuthenticationService"/>  <bean id="sendTicketGrantingTicketAction" class="org.jasig.cas.web.flow.SendTicketGrantingTicketAction"        p:centralAuthenticationService-ref="centralAuthenticationService"        p:ticketGrantingTicketCookieGenerator-ref="ticketGrantingTicketCookieGenerator"/>  <bean id="gatewayServicesManagementCheck" class="org.jasig.cas.web.flow.GatewayServicesManagementCheck"    c:servicesManager-ref="servicesManager" />  <bean id="serviceAuthorizationCheck" class="org.jasig.cas.web.flow.ServiceAuthorizationCheck"    c:servicesManager-ref="servicesManager" /><!-- org.jasig.cas.web.flow.GenerateLoginTicketActioncom.xb.casLogin.GenerateLoginTicketAction -->  <bean id="generateLoginTicketAction" class="org.jasig.cas.web.flow.GenerateLoginTicketAction"        p:ticketIdGenerator-ref="loginTicketUniqueIdGenerator"/>  <bean id="messageInterpolator" class="org.jasig.cas.util.SpringAwareMessageMessageInterpolator"/>  <bean id="credentialsValidator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"        p:messageInterpolator-ref="messageInterpolator"/><!-- org.jasig.cas.web.flow.TicketGrantingTicketCheckActioncom.xb.casLogin.GenerateLoginTicketAction -->  <bean id="ticketGrantingTicketCheckAction" class="org.jasig.cas.web.flow.TicketGrantingTicketCheckAction"        c:registry-ref="ticketRegistry" />  <bean id="terminateSessionAction" class="org.jasig.cas.web.flow.TerminateSessionAction"        c:cas-ref="centralAuthenticationService"        c:tgtCookieGenerator-ref="ticketGrantingTicketCookieGenerator"        c:warnCookieGenerator-ref="warnCookieGenerator"/> <!-- 增加远程控制者,允许以/remote请求启动remote控制流程 -->    <bean id="handlerMappingB"        class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">        <property name="mappings">            <props>                <prop key="/remoteLogin">remoteLoginController</prop>            </props>        </property>        <property name="interceptors">            <list>                <ref bean="localeChangeInterceptor" />            </list>        </property>    </bean><bean id="flowUrlHandler" class="org.jasig.cas.web.flow.CasDefaultFlowUrlHandler" />    <bean id="remoteLoginController" class="org.springframework.webflow.mvc.servlet.FlowController">        <property name="flowExecutor" ref="remoteLoginFlowExecutor" />        <property name="flowUrlHandler" ref="loginFlowUrlHandler" />    </bean>    <webflow:flow-executor id="remoteLoginFlowExecutor"        flow-registry="remoteLoginFlowRegistry">        <webflow:flow-execution-attributes>            <webflow:always-redirect-on-pause                value="false" />        </webflow:flow-execution-attributes>    </webflow:flow-executor>    <webflow:flow-registry id="remoteLoginFlowRegistry"        flow-builder-services="builder">        <webflow:flow-location path="/WEB-INF/remoteLogin-webflow.xml"            id="remoteLogin" />    </webflow:flow-registry>    <webflow:flow-builder-services id="flowBuilderServices"        view-factory-creator="viewFactoryCreator" />    <bean id="remoteLoginAction" class="com.xb.casLogin.RemoteLoginAction"        p:argumentExtractors-ref="argumentExtractors"        p:warnCookieGenerator-ref="warnCookieGenerator"        p:ticketGrantingTicketCookieGenerator-ref="ticketGrantingTicketCookieGenerator" /></beans>

4,新建一个文件与login-webflow.xml同级,remoteLogin-webflow.xml:,这个与3.0的版本有很多差别,如果你用的还是3.0版本的。就需要注意了。

<?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"    start-state="remoteLogin">    <!-- <on-start> <evaluate expression="remoteLoginAction.doBind(flowRequestContext,         flowScope.credentials)" /> </on-start> -->    <var name="credentials"        class="org.jasig.cas.authentication.UsernamePasswordCredential" />    <!-- 远程登陆主要Action -->    <action-state id="remoteLogin">        <evaluate expression="remoteLoginAction" />        <transition on="error" to="viewServiceErrorView" />        <transition on="submit" to="bindAndValidate" />        <transition on="checkTicketGrantingTicket" to="ticketGrantingTicketExistsCheck" />    </action-state><!--    <decision-state id="ticketGrantingTicketExistsCheck">        <if test="flowScope.ticketGrantingTicketId != null"         then="hasServiceCheck"        else="gatewayRequestCheck" />    </decision-state>-->    <action-state id="ticketGrantingTicketExistsCheck">        <evaluate expression="ticketGrantingTicketCheckAction.checkValidity(flowRequestContext)"/>        <transition on="notExists" to="gatewayRequestCheck"/>        <transition on="invalid" to="terminateSession"/>        <transition on="valid" to="hasServiceCheck"/>    </action-state>    <action-state id="terminateSession">        <evaluate expression="terminateSessionAction.terminate(flowRequestContext)"/>        <transition to="generateLoginTicket"/>    </action-state>    <decision-state id="gatewayRequestCheck">        <if test="requestParameters.gateway != '' and requestParameters.gateway != null and flowScope.service != null"         then="gatewayServicesManagementCheck" else="serviceAuthorizationCheck" />    </decision-state>    <decision-state id="hasServiceCheck">        <if test="flowScope.service != null"        then="renewRequestCheck"            else="viewGenericLoginSuccess" />    </decision-state>    <decision-state id="renewRequestCheck">        <if test="requestParameters.renew != '' and requestParameters.renew != null"         then="serviceAuthorizationCheck" else="generateServiceTicket" />    </decision-state> <!-- Do a service authorization check early without the need to login first -->    <action-state id="serviceAuthorizationCheck">        <evaluate expression="serviceAuthorizationCheck"/>        <transition to="generateLoginTicket"/>    </action-state>     <decision-state id="warn">        <if test="flowScope.warnCookieValue"         then="showWarningView" else="redirect" />    </decision-state><!--get lt 将lt  返回给客服端 remoteCallbackView-->        <action-state id="generateLoginTicket">        <evaluate expression="generateLoginTicketAction.generate(flowRequestContext)" />        <transition on="generated" to="remoteCallbackView" />    </action-state>     <!-- 远程回调登录页面,主要以JavaScript的方式回传一些参数用 -->    <!--<end-state id="remoteCallbackView" view="remoteCallbackView" />         -->    <view-state id="remoteCallbackView" view="remoteCallbackView" model="credentials">            <binder>            <binding property="username" />            <binding property="password" />        </binder>        <on-entry>            <set name="viewScope.commandName" value="'credentials'" />        </on-entry>        <transition on="submit" bind="true" validate="true" to="realSubmit">            <evaluate expression="authenticationViaFormAction.doBind(flowRequestContext, flowScope.credentials)" />        </transition>    </view-state>    <!-- 默认登录的流程,登录页面 -->    <view-state id="viewLoginForm" view="casLoginView" model="credentials">        <binder>            <binding property="username" />            <binding property="password" />        </binder>        <on-entry>            <set name="viewScope.commandName" value="'credentials'" />        </on-entry>        <transition on="submit" bind="true" validate="true" to="realSubmit">            <evaluate expression="authenticationViaFormAction.doBind(flowRequestContext, flowScope.credentials)" />        </transition>    </view-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="successWithWarnings" to="showMessages" />    <transition on="authenticationFailure" to="handleAuthenticationFailure" />    <transition on="error" to="generateLoginTicket" />  </action-state>   <view-state id="showMessages" view="casLoginMessageView">    <on-entry>      <evaluate expression="sendTicketGrantingTicketAction" />      <set name="requestScope.messages" value="messageContext.allMessages" />    </on-entry>    <transition on="proceed" to="serviceCheck" />  </view-state>    <action-state id="handleAuthenticationFailure">    <evaluate expression="authenticationExceptionHandler.handle(currentEvent.attributes.error, messageContext)" />    <transition on="AccountDisabledException" to="casAccountDisabledView"/>    <transition on="AccountLockedException" to="casAccountLockedView"/>    <transition on="CredentialExpiredException" to="casExpiredPassView"/>    <transition on="InvalidLoginLocationException" to="casBadWorkstationView"/>    <transition on="InvalidLoginTimeException" to="casBadHoursView"/>    <transition on="FailedLoginException" to="generateLoginTicket"/>    <transition on="AccountNotFoundException" to="generateLoginTicket"/>    <transition on="UNKNOWN" to="generateLoginTicket"/>  </action-state>    <action-state id="sendTicketGrantingTicket">        <evaluate expression="sendTicketGrantingTicketAction" />        <transition to="serviceCheck" />    </action-state><!--add  登录成功后跳转 remoteCallbackView-->    <decision-state id="serviceCheck">        <if test="flowScope.service != 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" />        <transition on="authenticationFailure" to="handleAuthenticationFailure" />    </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() == '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" />    <end-state id="showWarningView" view="casLoginConfirmView" />     <!-- Password policy failure states -->  <end-state id="abstactPasswordChangeView">    <on-entry>      <set name="flowScope.passwordPolicyUrl" value="passwordPolicy.passwordPolicyUrl" />    </on-entry>  </end-state>  <end-state id="casExpiredPassView" view="casExpiredPassView" parent="#abstactPasswordChangeView" />  <end-state id="casMustChangePassView" view="casMustChangePassView" parent="#abstactPasswordChangeView" />  <end-state id="casAccountDisabledView" view="casAccountDisabledView" />  <end-state id="casAccountLockedView" view="casAccountLockedView" />  <end-state id="casBadHoursView" view="casBadHoursView" />  <end-state id="casBadWorkstationView" view="casBadWorkstationView" />    <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>    <!--<end-state id="redirectView" view="externalRedirect:${requestScope.response.url}" />-->    <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>

5,回调页面jsp:

<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>  <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>  <script type="text/javascript">      var remoteUrl = "${remoteLoginUrl}?validated=true";      //登陆回调页面。        var re1 = '${loginTicket}';    var re2 = '${flowExecutionKey}';    console.log("-loginTicket--"+re1);    console.log("-execution--"+re2);   // 构造错误消息,从webflow scope中取出      var errorMessage = '${remoteLoginMessage}';      /* <spring:hasBindErrors name="credentials">      errorMessage = "&errorMessage=" + encodeURIComponent('<c:forEach var="error" items="${errors.allErrors}"><spring:message code="${error.code}" text="${error.defaultMessage}" /></c:forEach>');     </spring:hasBindErrors> */      // 如果存在错误消息则追加到 url中      if(null != errorMessage && errorMessage.length > 0)      {          errorMessage = "&errorMessage=" + encodeURIComponent(errorMessage);      }      console.log("客户端remoteUrl:---"+remoteUrl + errorMessage );    // 构造service      var service = "";    var loginTicket = "";    <c:if test="${service != null && service != ''}">       service = "&service=" + encodeURIComponent("${service}");      </c:if>      <c:if test="${loginTicket != null && loginTicket != ''}">       loginTicket = "&loginTicket=" + "${loginTicket}";      </c:if>     console.log("客户端service:"+ service);    var flowExecutionKey ="&flowExecutionKey=" + re2;     // 跳转回去(客户端)    window.location.href = remoteUrl + errorMessage + service+loginTicket+flowExecutionKey;    </script>

6.在default_views.properties中新增以下两句:
remoteCallbackView.(class)=org.springframework.web.servlet.view.JstlView
remoteCallbackView.url=/WEB-INF/view/jsp/default/ui/remoteCallbackView.jsp

完成,希望你也能一次成功。
当访问子系统的资源时:
127.0.0.1:8080/casClient/pages/index.jsp
会被拦截然后调往服务端的remoteLogin流程。服务器处理完后,地址链接就会跳转到:
127.0.0.1:8080/casClient/login.jsp?validated=true&service=http%3A%2F%2F127.0.0.1%3A8080%2FcasClient%2Fpages%2Findex.jsp&loginTicket=LT-2-gqkSPrIxkevb5TyqU2QprMQauC92OE-cas01.example.org&flowExecutionKey=e1s1
此时我们看到2 个关键的参数已经有值 loginTicket和 flowExecutionKey。
登录界面如下:
这是子系统的登录页面

下面是我自己项目的连接地址,有需要可以去下载。
http://download.csdn.net/download/qinzuoguo/10133874