CAS 单点登录之自定义登录

开源的 CAS 单点登录本身已经提供了一个统一的登录页面,也就是我们配置好之后,所有的没有登录的请求都会拦截到自带的登录页面,我们可以根据自己的需求来改造这个页面,但是,需求是多种多样的并且有时候总是更改的,我就碰到过这种情况,有时就会让人很崩溃。

很多时候,我们都是接手别人的代码,而且大多都是比较成熟,并且有一定时间的项目,我们总是在前人的基础进行修修补补。就拿单点登录来说,我相信大多数项目都有一套自己比较成熟的登录过滤机制,如果我们集成单点登录功能,也是使用我们以前的登录页面,这时候我们就要对 CAS 进行改造。

在我第一次接手这个需求之前,我在网上查找了很多资料,大多数都是教我们将 CAS 集成进项目,我在集成的过程发现,因为我的项目本来就是一个老项目,使用的是 Spring Security 来进行登录过滤,要将原来的登录逻辑改成 CAS 的,这个比较难,往往会碰到很多 Bug,代码的耦合度有点高,牵一发而动全身,不胜其烦。所以,我感觉这个方法并不适合一个老项目。后来,我想了一个办法,另外单独写了一个登录的服务,所有的请求都拦截到这个新的服务,然后根据访问的链接,验证成功后再跳转回去,这样做的好处有,可扩展性比较强,以后如果有新的项目要加进来,只需添加一些配置即可,缺点就是需要单独启动一个服务,这样开销较大,要是这个服务崩,其他的也登录不进去了。

这时,我们就要发挥搜索引擎的巨大优势和身为程序员的主观能动性了,在我一通搜索之后,我终于找到了一个比较合适的解决方案了,那就是改造 CAS,自定义登录页面。

参考文章:http://lsz1023-126-com.iteye.com/blog/2098973

方法

将 CAS 原有的过滤器改为我们自己的过滤器,新增一个 remoteLogin 类,将 CAS 原有的处理登录的类换成我们自己的,其他的校验还是 CAS 自己的,新增一个校验登录失败的页面,将错误信息返回给客户端。我们需要修改客户端和 CAS 认证服务器端两处。

修改客户端

  1. 新增 RemoteAuthenticationFilter.java,可以自定义路径,web.xml 文件能够正确引用即可。
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;
  
  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.");
  }


  @Override
  public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
      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);
    }
    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 ,将原来的过滤器换成上面的过滤器,然后配置以下配置。如果要实现单点登出功能,只需将单点登出的配置放到最上面,使用 CAS 自带的单点登出功能即可。

<!-- 单点登出 -->
    <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>
               <!-- 只需改成 CAS 认证服务器的地址,例如 -->
               <param-value>http://127.0.0.1:8080/cas</param-value>
          </init-param>
    </filter>
    <filter-mapping>
            <filter-name>CAS Single Sign Out Filter</filter-name>
            <url-pattern>/*</url-pattern>
    </filter-mapping>
    
    <!-- 单点登录  Start -->
    <filter>
        <filter-name>CAS Filter</filter-name>
        <!-- 这里换成自己的包 -->
        <filter-class>com.demo.filter.RemoteAuthenticationFilter</filter-class>
        <init-param>
            <param-name>localLoginUrl</param-name>
            <!-- 这里放我们自己项目的登陆页地址,例如 -->
            <param-value>http://localhost:8082/myProject/login.do</param-value>
        </init-param>
        <init-param>
            <param-name>casServerLoginUrl</param-name>
            <!-- 只需改 CAS 认证服务器的地址 -->
            <param-value>http://127.0.0.1:8080/cas/remoteLogin</param-value>
        </init-param>
        <init-param>
            <param-name>serverName</param-name>
            <!-- 只需改项目的 IP 和端口 -->
            <param-value>http://localhost:8082/</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CAS Filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
    <!-- CAS Client向CAS Server进行ticket验证 -->  
    <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> 
                    <!-- 只需改 CAS 认证服务器的 IP 和端口 --> 
                    <param-value>http://127.0.0.1:8080/cas/</param-value>
        </init-param>  
            <init-param>  
                <param-name>serverName</param-name>  
                <!-- 只需改项目的 IP 和端口 -->
                <param-value>http://localhost:8082/</param-value>
        </init-param>  
    </filter>  
    <filter-mapping>  
            <filter-name>CAS Validation Filter</filter-name>  
         <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!-- 封装request, 支持getUserPrincipal等方法-->  
    <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>  

    <!-- 存放Assertion到ThreadLocal中   -->  
    <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>
    <!-- 单点登录  End -->

3. login.jsp,在登录页面加上以下代码

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 getParamVal(name, queryString) {     
    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) {   
        var errorStr = decodeURIComponent(error);
        $("#errorMessage").html(errorStr);
    }   
    // 注入service   
    var service = getParam("service");
    if (service) {
        var serviceStr = decodeURIComponent(service);
        $("#service").val(serviceStr);
        var errorMsg = getParamVal("errorMessage", service);
        if (errorMsg != null) {
            $("#errorMessage").html(decodeURIComponent(errorMsg));
        }
    } else {
        $("#service").val(location.href);
    }
}
<form id="form1" name="form1" method="post" onsubmit="init()" action="http:/localhost:8080/cas/remoteLogin">
    <input type="hidden" name="loginUrl" value="http://localhost:8082/demo/login.do">  
    <input type="hidden" name="submit" value="true" />
    <input type="hidden" name="service" id= "service"/> />  

修改 CAS 服务端

  1. 修改 web.xml 文件,添加 remoteLogin 的映射
<servlet-mapping>
       <servlet-name>cas</servlet-name>
       <url-pattern>/remoteLogin</url-pattern>
</servlet-mapping>
  1. 增加 RemoteLoginAction 登录处理类
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  
    {  
        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;  
        }  
        String ticketGrantingTicketId = this.ticketGrantingTicketCookieGenerator.retrieveCookieValue(request);
        boolean warnCookieValue = Boolean.valueOf(this.warnCookieGenerator.retrieveCookieValue(request));
        context.getFlowScope().put("ticketGrantingTicketId", ticketGrantingTicketId);  
        context.getFlowScope().put("warnCookieValue", warnCookieValue);  
          
        // 存放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参数过来,否则无法确定登陆目标页面  
        if (StringUtils.hasText(request.getParameter("loginUrl")))  
        {  
            String loginUrl = request.getParameter("loginUrl");
            System.out.println(loginUrl);
            context.getFlowScope().put("remoteLoginUrl",loginUrl);  
        } else  
        {  
            request.setAttribute("remoteLoginMessage",  
                    "loginUrl parameter must be supported.");  
            return error();  
        }  
          
        // 若参数包含submit则进行提交,否则进行验证  
        if (StringUtils.hasText(request.getParameter("submit")))  
        {  
            return result("submit");  
        } else  
        {   
            Event event = result("checkTicketGrantingTicket");
            return event;  
        }  
    }  
      
    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;  
    }  
}

  1. 在 WEB-INF 下增加 remoteLogin-weblow.xml 自定义登录 webflow 流程文件
<?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="remoteLogin" />
    </on-start> -->
       
    <!-- <on-start> <evaluate expression="remoteLoginAction.doBind(flowRequestContext,    
        flowScope.credentials)" /> </on-start> -->  
    <var name="credentials"  
        class="org.jasig.cas.authentication.principal.UsernamePasswordCredentials" />
        
    <!-- 远程登陆主要Action -->  
    <action-state id="remoteLogin">  
        <evaluate expression="remoteLoginAction" />  
        <transition on="error" to="remoteCallbackView" />  
        <transition on="submit" to="bindAndValidate" />  
        <transition on="checkTicketGrantingTicket" to="ticketGrantingTicketExistsCheck" />  
    </action-state>  
  
    <!-- 远程回调页面,主要以JavaScript的方式回传一些参数用 -->  
    <end-state id="remoteCallbackView" view="viewStatisticsView" />  
  
    <decision-state id="ticketGrantingTicketExistsCheck">  
        <if test="flowScope.ticketGrantingTicketId != null" then="hasServiceCheck"  
            else="gatewayRequestCheck" />  
    </decision-state>  
  
    <decision-state id="hasServiceCheck">  
        <if test="flowScope.service != null" then="generateServiceTicket"  
            else="remoteCallbackView" />  
    </decision-state>  
    <decision-state id="gatewayRequestCheck">  
        <if  
            test="externalContext.requestParameterMap['gateway'] neq '' &amp;&amp; externalContext.requestParameterMap['gateway'] neq null &amp;&amp; flowScope.service neq null"  
            then="redirect" else="remoteCallbackView" />  
    </decision-state>  
  
    <action-state id="bindAndValidate">  
        <evaluate  
            expression="authenticationViaFormAction.doBind(flowRequestContext, flowScope.credentials)" />  
        <transition on="success" to="submit" />  
        <transition on="warn" to="warn" />
        <transition on="error" to="remoteCallbackView" />  
    </action-state> 
    
    <action-state id="generateServiceTicket">  
        <evaluate expression="generateServiceTicketAction" />  
        <transition on="success" to="warn" />  
        <transition on="error" to="remoteCallbackView" />  
        <transition on="gateway" to="redirect" />  
    </action-state>  
  
    <decision-state id="warn">  
        <if test="flowScope.warnCookieValue" then="showWarningView" else="redirect" />  
    </decision-state>  
  
    <action-state id="submit">  
        <evaluate  
            expression="authenticationViaFormAction.submit(flowRequestContext, messageContext)" />  
        <transition on="warn" to="warn" />  
        <transition on="success" to="sendTicketGrantingTicket" />  
        <transition on="error" to="remoteCallbackView" />  
    </action-state>  
  
    <action-state id="sendTicketGrantingTicket">  
        <evaluate expression="sendTicketGrantingTicketAction" />  
        <transition to="serviceCheck" />  
    </action-state>  
  
    <decision-state id="serviceCheck">  
        <if test="flowScope.service neq null" then="generateServiceTicket"  
            else="remoteCallbackView" />  
    </decision-state>  
  
    <end-state id="showWarningView" view="casLoginConfirmView" />  
  
    <!-- <end-state id="redirect" view="bean:dynamicRedirectViewSelector" /> -->  
  
    <action-state id="redirect">  
        <evaluate  
            expression="flowScope.service.getResponse(requestScope.serviceTicketId)"  
            result-type="org.jasig.cas.authentication.principal.Response" result="requestScope.response" />  
        <transition to="postRedirectDecision" />  
    </action-state>  
  
    <decision-state id="postRedirectDecision">  
        <if test="requestScope.response.responseType.name() eq 'POST'"  
            then="postView" else="redirectView" />  
    </decision-state>  
       
    <!-- <decision-state id="hashServiceUrl">  
        <if test="flowScope.serviceUrl neq null" then="redirectServiceView"  else="redirectView"/>  
    </decision-state>  
    <end-state id="redirectServiceView" view="externalRedirect:${flowScope.serviceUrl}" /> -->  
       
    <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="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>  

4. 修改 cas-servlet.xml,在后面加上一下代码

<bean id="handlerMappingB"  
        class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">  
        <property name="mappings">  
            <props>  
                <prop key="/remoteLogin">remoteLoginController</prop>
                <prop key="/remoteLogout">remoteLogoutController</prop>  
            </props>  
        </property>  
        <property name="interceptors">  
            <list>  
                <ref bean="localeChangeInterceptor" />  
            </list>  
        </property>  
      </bean>  
  
    <bean id="remoteLoginController" class="org.springframework.webflow.mvc.servlet.FlowController">  
        <property name="flowExecutor" ref="remoteLoginFlowExecutor" />  
        <property name="flowUrlHandler" ref="flowUrlHandler" /> 
    </bean>
    
    <bean id="remoteLogoutController" class="org.springframework.webflow.mvc.servlet.FlowController">  
        <property name="flowExecutor" ref="remoteLogoutFlowExecutor" />  
        <property name="flowUrlHandler" ref="flowUrlHandler" /> 
    </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-executor id="remoteLogoutFlowExecutor"  
        flow-registry="remoteLogoutFlowRegistry">  
        <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-registry id="remoteLogoutFlowRegistry"  
        flow-builder-services="builder">
        <webflow:flow-location path="/WEB-INF/remoteLogout-webflow.xml"  
            id="remoteLogout"/>  
    </webflow:flow-registry>  
  
    <webflow:flow-builder-services id="flowBuilderServices"  
        view-factory-creator="viewFactoryCreator" />  
  
    <bean id="remoteLoginAction" class="org.jasig.cas.web.flow.RemoteLoginAction"  
        p:argumentExtractors-ref="argumentExtractors"  
        p:warnCookieGenerator-ref="warnCookieGenerator"  
        p:ticketGrantingTicketCookieGenerator-ref="ticketGrantingTicketCookieGenerator" />  
  
    <bean id="remoteLogoutAction" class="org.jasig.cas.web.flow.RemoteLogoutAction"  
        p:argumentExtractors-ref="argumentExtractors"  
        p:warnCookieGenerator-ref="warnCookieGenerator"  
        p:ticketGrantingTicketCookieGenerator-ref="ticketGrantingTicketCookieGenerator" 
        p:centralAuthenticationService-ref="centralAuthenticationService"/>

5. 增加 remoteCallView.jsp 返回错误提示信息

<%@ page pageEncoding="UTF-8" %>
<%@ page contentType="text/html; charset=UTF-8" %>
<script type="text/javascript">   
    var remoteUrl = "${remoteLoginUrl}?validated=true";   
    // 构造错误消息,从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);   
    }   
    // 构造service  
    var service = "${service}";   
    if (service != null && service != "") {
        service = "&service=" + encodeURIComponent(service);  
    }
    // 跳转回去(客户端)  
    window.location.href = remoteUrl + errorMessage + service;   
</script>  

6. 在 default_views.properties 中新增以下配置

viewStatisticsView.(class)=org.springframework.web.servlet.view.JstlView
viewStatisticsView.url=/WEB-INF/view/jsp/default/ui/remoteCallbackView.jsp

下一篇文章我会写一下在实际开发中遇到的一些问题,供广大网友参考,在此也感谢一下很多博客主分享的文章,参考了很多的文章。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容