`

CAS-3.2.1自定义客户端登录界面----完整篇

 
阅读更多
前言:在已有的CAS SSO架构代码上,经过4天的研究终于完成了客户端登录界面需求,其中多亏网上的资料【让CAS支持客户端自定义登陆页面——服务器新篇与客户端新篇】本文也是基于此完成的,不过我修改了一些代码,CAS SSO的内部流程我只了解了60%,如果你想看CAS SSO原理,请忽略此文章;本文致力提供完整,详细的搭建流程,争取让你一次成功!附件中有本文工程,分客户端与服务端,采用maven搭建,想要运行请配置好maven环境,数据库采用mysql。

原文链接地址:http://lsz1023-126-com.iteye.com/blog/2098973

实现原理:
一、 逻辑
 客户端修改CAS Authentication Filter过滤器,该过滤器会判断用户是否登录,如果没有登录则跳转到自身配置的登录界面;
 用户输入正确的信息,登录时,会被提交到服务端的登录流程中;
 服务端通过新增一个remoteLogin处理类,专门处理客户端自定义登录业务;
 该remoteLogin处理类与原始的login处理极为类似,只是修改了获取用户名与密码的方式;
 如果用户名与密码不匹配,校验失败,会通过remoteCallbackView.jsp界面将错误提示与service一并响应给客户端的登录界面,接收之后将错误提示显示到界面上;
 如果用户名与密码匹配,校验成功,会直接重定向到客户端的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能够正确引用到就行:
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.");
	}
	
	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);
		}
		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>

修改成这样,注:其它路径mapping不用改。
<filter>
	    <filter-name>CAS Authentication Filter</filter-name>
	    <filter-class>org.demo.user.common.RemoteAuthenticationFilter</filter-class>
	    <init-param>
	        <param-name>localLoginUrl</param-name>
	        <param-value>http://localhost:8080/app/mylogin.jsp</param-value>
	    </init-param>
	    <init-param>
	        <param-name>casServerLoginUrl</param-name>
	        <param-value>https://localhost:8443/cas/remoteLogin</param-value>
	    </init-param>
	    <init-param>
	        <param-name>serverName</param-name>
	        <param-value>http://localhost:8080</param-value>
	    </init-param>
	</filter>


3.加上你自己定义的登录界面,注:我修改了一些网上介绍的代码:
<%@ 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">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>APP1客户端登录</title>
<link rel="stylesheet" type="text/css"
	href="<%=request.getContextPath()%>/styles/main.css" />
<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");
		if (service)
			document.getElementById("service").value = decodeURIComponent(service);
		else
			document.getElementById("service").value = location.href;
	}
</script>
</head>
<body>
	<h1>APP1客户端登录</h1>

	<div id="errorMessage" style="color: red;"></div>
	<form id="myLoginForm" action="https://localhost:8443/cas/remoteLogin?service=http://localhost:8080/app/pages/home.jsp"
		method="post">
		
		<input type="hidden" name="loginUrl" value="http://localhost:8080/app/mylogin.jsp">
		<input type="hidden" name="submit" value="true" />
		<input type="hidden" name="lt" id="loginTicket" value="" />
		
		<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>
	<script type="text/javascript">
		init()
	</script>
</body>
</html>

至此客户端完结!

服务端篇:注我没有加上登出代码,因为登出代码可以使用原有的
1.添加客户端登录Action,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
	{
		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参数过来,否则无法确定登陆目标页面
		if (StringUtils.hasText(request.getParameter("loginUrl")))
		{
			context.getFlowScope().put("remoteLoginUrl",
					request.getParameter("loginUrl"));
		} else
		{
			request.setAttribute("remoteLoginMessage",
					"loginUrl parameter must be supported.");
			return error();
		}
		
		// 若参数包含submit则进行提交,否则进行验证
		if (StringUtils.hasText(request.getParameter("submit")))
		{
			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.重写LoginForm代码,org.jasig.cas.web.flow.AuthenticationViaFormAction重写,此类基本上采用原有代码,只是添加了获取用户名与密码的代码:
public class AuthenticationViaFormAction
{
	/**
	 * Binder that allows additional binding of form object beyond Spring
	 * defaults.
	 */
	private CredentialsBinder credentialsBinder;
	
	/** Core we delegate to for handling all ticket related tasks. */
	@NotNull
	private CentralAuthenticationService centralAuthenticationService;
	
	@NotNull
	private CookieGenerator warnCookieGenerator;
	
	protected Logger logger = LoggerFactory.getLogger(getClass());
	
	public final void doBind(final RequestContext context,
			final Credentials credentials) throws Exception
	{
		final HttpServletRequest request = WebUtils
				.getHttpServletRequest(context);
		
		if (this.credentialsBinder != null
				&& this.credentialsBinder.supports(credentials.getClass()))
		{
			this.credentialsBinder.bind(request, credentials);
		}
	}
	
	public final String submit(final RequestContext context,
			final MessageContext messageContext) throws Exception
	{
		// Validate login ticket
		final String authoritativeLoginTicket = WebUtils
				.getLoginTicketFromFlowScope(context);
		final String providedLoginTicket = WebUtils
				.getLoginTicketFromRequest(context);
		if (!authoritativeLoginTicket.equals(providedLoginTicket))
		{
			this.logger.warn("Invalid login ticket " + providedLoginTicket);
			final String code = "INVALID_TICKET";
			messageContext.addMessage(new MessageBuilder().error().code(code)
					.arg(providedLoginTicket).defaultText(code).build());
			return "error";
		}
		final String ticketGrantingTicketId = WebUtils
				.getTicketGrantingTicketId(context);
		final Service service = WebUtils.getService(context);
		final HttpServletRequest request = WebUtils
				.getHttpServletRequest(context);
		org.jasig.cas.authentication.principal.UsernamePasswordCredentials credentials = new org.jasig.cas.authentication.principal.UsernamePasswordCredentials();
		credentials.setPassword(request.getParameter("password"));
		credentials.setUsername(request.getParameter("username"));
		if (StringUtils.hasText(context.getRequestParameters().get("renew"))
				&& ticketGrantingTicketId != null && service != null)
		{
			try
			{
				final String serviceTicketId = this.centralAuthenticationService
						.grantServiceTicket(ticketGrantingTicketId, service,
								credentials);
				WebUtils.putServiceTicketInRequestScope(context,
						serviceTicketId);
				putWarnCookieIfRequestParameterPresent(context);
				return "warn";
			} catch (final TicketException e)
			{
				if (e.getCause() != null
						&& AuthenticationException.class.isAssignableFrom(e
								.getCause().getClass()))
				{
					populateErrorsInstance(context, e, messageContext);
					return "error";
				}
				this.centralAuthenticationService
						.destroyTicketGrantingTicket(ticketGrantingTicketId);
				if (logger.isDebugEnabled())
				{
					logger.debug(
							"Attempted to generate a ServiceTicket using renew=true with different credentials",
							e);
				}
			}
		}
		
		try
		{
			WebUtils.putTicketGrantingTicketInRequestScope(context,
					this.centralAuthenticationService
							.createTicketGrantingTicket(credentials));
			putWarnCookieIfRequestParameterPresent(context);
			return "success";
		} catch (final TicketException e)
		{
			populateErrorsInstance(context, e, messageContext);
			return "error";
		}
	}
	
	public final String submit(final RequestContext context,
			final Credentials credentials, final MessageContext messageContext)
			throws Exception
	{
		// Validate login ticket
		final String authoritativeLoginTicket = WebUtils
				.getLoginTicketFromFlowScope(context);
		final String providedLoginTicket = WebUtils
				.getLoginTicketFromRequest(context);
		if (!authoritativeLoginTicket.equals(providedLoginTicket))
		{
			this.logger.warn("Invalid login ticket " + providedLoginTicket);
			final String code = "INVALID_TICKET";
			messageContext.addMessage(new MessageBuilder().error().code(code)
					.arg(providedLoginTicket).defaultText(code).build());
			return "error";
		}
		
		final String ticketGrantingTicketId = WebUtils
				.getTicketGrantingTicketId(context);
		final Service service = WebUtils.getService(context);
		if (StringUtils.hasText(context.getRequestParameters().get("renew"))
				&& ticketGrantingTicketId != null && service != null)
		{
			
			try
			{
				final String serviceTicketId = this.centralAuthenticationService
						.grantServiceTicket(ticketGrantingTicketId, service,
								credentials);
				WebUtils.putServiceTicketInRequestScope(context,
						serviceTicketId);
				putWarnCookieIfRequestParameterPresent(context);
				return "warn";
			} catch (final TicketException e)
			{
				if (isCauseAuthenticationException(e))
				{
					populateErrorsInstance(e, messageContext);
					return getAuthenticationExceptionEventId(e);
				}
				
				this.centralAuthenticationService
						.destroyTicketGrantingTicket(ticketGrantingTicketId);
				if (logger.isDebugEnabled())
				{
					logger.debug(
							"Attempted to generate a ServiceTicket using renew=true with different credentials",
							e);
				}
			}
		}
		
		try
		{
			WebUtils.putTicketGrantingTicketInRequestScope(context,
					this.centralAuthenticationService
							.createTicketGrantingTicket(credentials));
			putWarnCookieIfRequestParameterPresent(context);
			return "success";
		} catch (final TicketException e)
		{
			populateErrorsInstance(e, messageContext);
			if (isCauseAuthenticationException(e))
				return getAuthenticationExceptionEventId(e);
			return "error";
		}
	}
	
	private void populateErrorsInstance(final TicketException e,
			final MessageContext messageContext)
	{
		
		try
		{
			messageContext.addMessage(new MessageBuilder().error()
					.code(e.getCode()).defaultText(e.getCode()).build());
		} catch (final Exception fe)
		{
			logger.error(fe.getMessage(), fe);
		}
	}
	
	private void populateErrorsInstance(final RequestContext context,
			final TicketException e, final MessageContext messageContext)
	{
		
		try
		{
			messageContext.addMessage(new MessageBuilder().error()
					.code(e.getCode()).defaultText(e.getCode()).build());
			
			Message[] messages = messageContext.getAllMessages();
			
			context.getFlowScope().put("remoteLoginMessage",
					messages[messages.length - 1].getText());
			
		} catch (final Exception fe)
		{
			logger.error(fe.getMessage(), fe);
		}
	}
	
	private void putWarnCookieIfRequestParameterPresent(
			final RequestContext context)
	{
		final HttpServletResponse response = WebUtils
				.getHttpServletResponse(context);
		
		if (StringUtils.hasText(context.getExternalContext()
				.getRequestParameterMap().get("warn")))
		{
			this.warnCookieGenerator.addCookie(response, "true");
		} else
		{
			this.warnCookieGenerator.removeCookie(response);
		}
	}
	
	private AuthenticationException getAuthenticationExceptionAsCause(
			final TicketException e)
	{
		return (AuthenticationException) e.getCause();
	}
	
	private String getAuthenticationExceptionEventId(final TicketException e)
	{
		final AuthenticationException authEx = getAuthenticationExceptionAsCause(e);
		
		if (this.logger.isDebugEnabled())
			this.logger
					.debug("An authentication error has occurred. Returning the event id "
							+ authEx.getType());
		
		return authEx.getType();
	}
	
	private boolean isCauseAuthenticationException(final TicketException e)
	{
		return e.getCause() != null
				&& AuthenticationException.class.isAssignableFrom(e.getCause()
						.getClass());
	}
	
	public final void setCentralAuthenticationService(
			final CentralAuthenticationService centralAuthenticationService)
	{
		this.centralAuthenticationService = centralAuthenticationService;
	}
	
	/**
	 * Set a CredentialsBinder for additional binding of the HttpServletRequest
	 * to the Credentials instance, beyond our default binding of the
	 * Credentials as a Form Object in Spring WebMVC parlance. By the time we
	 * invoke this CredentialsBinder, we have already engaged in default binding
	 * such that for each HttpServletRequest parameter, if there was a JavaBean
	 * property of the Credentials implementation of the same name, we have set
	 * that property to be the value of the corresponding request parameter.
	 * This CredentialsBinder plugin point exists to allow consideration of
	 * things other than HttpServletRequest parameters in populating the
	 * Credentials (or more sophisticated consideration of the
	 * HttpServletRequest parameters).
	 * 
	 * @param credentialsBinder
	 *            the credentials binder to set.
	 */
	public final void setCredentialsBinder(
			final CredentialsBinder credentialsBinder)
	{
		this.credentialsBinder = credentialsBinder;
	}
	
	public final void setWarnCookieGenerator(
			final CookieGenerator warnCookieGenerator)
	{
		this.warnCookieGenerator = warnCookieGenerator;
	}
}


3.web.xml配置,原有基础上新增这两句:
<servlet-mapping>
     <servlet-name>cas</servlet-name>
     <url-pattern>/remoteLogin</url-pattern>
 </servlet-mapping>


4.在cas-servlet.xml中最后面增加以下信息:
<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="remoteLoginController" class="org.springframework.webflow.mvc.servlet.FlowController">
		<property name="flowExecutor" ref="remoteLoginFlowExecutor" />
		<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-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="org.jasig.cas.web.flow.RemoteLoginAction"
		p:argumentExtractors-ref="argumentExtractors"
		p:warnCookieGenerator-ref="warnCookieGenerator"
		p:ticketGrantingTicketCookieGenerator-ref="ticketGrantingTicketCookieGenerator" />

	<bean id="remoteLogoutController" class="org.springframework.webflow.mvc.servlet.FlowController">
		<property name="flowExecutor" ref="remoteLogoutFlowExecutor" />
		<property name="flowUrlHandler" ref="flowUrlHandler" />
	</bean>


5.新建一个文件与login-webflow.xml同级,remoteLogin-webflow.xml:
<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/webflow
                          http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd"
	start-state="remoteLogin">
	
	<!-- <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="remoteCallbackView" />

	<action-state id="bindAndValidate">
		<evaluate
			expression="authenticationViaFormAction.doBind(flowRequestContext, flowScope.credentials)" />
		<transition on="success" to="submit" />
		<transition on="error" to="remoteCallbackView" />
	</action-state>

	<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="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>


6.加上一个回调视图配置,在default_views.properties中新增以下两句:
### 配置远程回调页面
remoteCallbackView.(class)=org.springframework.web.servlet.view.JstlView
remoteCallbackView.url=/WEB-INF/view/jsp/default/ui/remoteCallbackView.jsp
其它不变

7.加上回调页面jsp:
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%-- <%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> --%>
<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 = "";
    <c:if test="${service != null && service != ''}">
     service = "&service=" + encodeURIComponent("${service}");
    </c:if>
    
    // 跳转回去(客户端)
    window.location.href = remoteUrl + errorMessage + service;
</script>


完结...

请关注下一篇,shiro + cas sso实现客户端自定义登录界面完整实现
分享到:
评论
5 楼 qzg196 2017-11-08  
楼主啊 我用的4.0的服务器版。为什么配置好后  地址已经跳转了 但就是没进去 报500错误。服务器端处理我的remotelogin 时卡住了,检查了好几遍控制流程没有配错啊。
4 楼 月色无夜 2017-06-23  
楼主你附带在项目中的证书导入密码是什么?
3 楼 zws_miss 2017-05-16  
AuthenticationViaFormAction   这个类好多东西报错, 不知道怎么来的, 云里雾里!
2 楼 javabean96 2015-04-23  
下一篇cas+shiro自定义界面有博文没
1 楼 游其是你 2014-12-18  
楼主的这个例子挺不错的,请问能不能给一个MyEclipse的代码呢?上面的代码Server端在MyEclipse里导入不进来,谢谢!

相关推荐

    cas-client-core-3.2.1-API文档-中英对照版 (1).zip

    赠送原API文档:cas-client-core-3.2.1-javadoc.jar; 赠送源代码:cas-client-core-3.2.1-sources.jar; 赠送Maven依赖信息文件:cas-client-core-3.2.1.pom; 包含翻译后的API文档:cas-client-core-3.2.1-javadoc...

    cas-client-core-3.2.1.jar

    cas-client-core-3.2.1

    cas-server-3.5.1和cas-client-3.2.1

    在这个压缩包中,包含的是CAS服务器端3.5.1版本和客户端3.2.1版本的代码和相关依赖的jar文件。 首先,我们来看`cas-server-3.5.1`部分。这是CAS服务器端的核心组件,负责处理用户的认证请求和响应。3.5.1版本可能...

    cas-client-3.2.1

    "cas-client-3.2.1"是CAS客户端的一个特定版本,它允许用户通过一个统一的身份验证入口点登录到多个服务,而无需为每个服务单独输入凭证。 在CAS客户端3.2.1版本中,与2.1.1版本相比,配置过程确实可能更加复杂。这...

    cas-client-3.2.1和cas-server-3.5.2

    在这个压缩包文件中,"cas-client-3.2.1" 和 "cas-server-3.5.2" 分别代表CAS客户端和服务器的特定版本。 **CAS Server 3.5.2** CAS Server是整个SSO系统的核心,它负责验证用户的凭证并提供登录服务。版本3.5.2是...

    cas-server-3.4.10-release和cas-client-3.2.1-release两个

    标题提到的"cas-server-3.4.10-release"和"cas-client-3.2.1-release"是CAS协议的两个关键组件:服务器端和客户端。 **CAS服务器端(cas-server-3.4.10-release)** CAS服务器是整个系统的核心,它负责处理用户的...

    cas-client-3.2.1 cas-server-3.4.11

    在"cas-client-3.2.1-release.zip"中,可能包含了以下内容: - 客户端库文件:如JAR文件,供开发者集成到Java Web应用中。 - 示例配置:展示如何配置CAS客户端,包括web.xml的设置和CAS服务器地址的指定。 - 文档...

    cas-server-3.4.10-release和cas-client-3.2.1-release

    接下来是`cas-client-3.2.1-release`,它是CAS客户端的发布包,用于集成到各个应用系统中,实现对CAS服务器的调用。这个版本包含客户端库、配置示例和其他相关资源。客户端的主要功能有: 1. **票证验证**:客户端...

    gradle-3.2.1-all.zip,完整版-解压即可使用

    Gradle-3.2.1是Gradle的一个版本,该版本包含了所有必要的组件,可以在解压后直接使用,无需额外安装或配置,这对于那些网络环境不佳或者需要离线开发的用户来说非常方便。 Gradle 的核心特性之一是它的基于Groovy...

    cas-client-3.2.1+cas-server-3.4.10

    1. **CAS客户端**(cas-client-3.2.1-release.zip): CAS客户端是安装在各个应用服务器上的组件,它负责与CAS服务器通信,实现用户的身份验证。在这个版本中,CAS客户端3.2.1可能包含了以下特性: - 支持多种协议...

    cas-server-3.4.11和cas-client-3.2.1

    在"cas-server-3.4.11-release.zip"和"cas-client-3.2.1-release.zip"这两个压缩包中,你可以找到源码、文档、示例以及必要的配置文件,用于部署和定制CAS服务器和客户端。通过深入研究这些资源,你可以了解到如何...

    commons-collections-3.2.1-API文档-中文版.zip

    赠送原API文档:commons-collections-3.2.1-javadoc.jar; 赠送源代码:commons-collections-3.2.1-sources.jar; 包含翻译后的API文档:commons-collections-3.2.1-javadoc-API文档-中文(简体)版.zip 对应Maven...

    一维码二维码所需资源包javase-3.2.1 zxing-core-3.2.1.jar

    这两个资源包,`javase-3.2.1.jar` 和 `zxing-core-3.2.1.jar`,是开发Java应用程序时用于生成和解析一维码与二维码的关键组件。 `javase-3.2.1.jar` 是一个针对Java Standard Edition(Java SE)平台的特定版本库...

    jquery-3.2.1.min.js

    jquery-3.2.1

    apache-maven-3.2.1

    apache-maven-3.2.1apache-maven-3.2.1apache-maven-3.2.1apache-maven-3.2.1apache-maven-3.2.1apache-maven-3.2.1apache-maven-3.2.1

    hessian-lite-3.2.1-fixed-2.jar

    com.alibaba:hessian-lite:jar:3.2.1-fixed-2 hessian-lite hessian-lite-3.2.1-fixed-2.jar

    commons-collections-3.2.1-API文档-中英对照版.zip

    赠送原API文档:commons-collections-3.2.1-javadoc.jar 赠送源代码:commons-collections-3.2.1-sources.jar 包含翻译后的API文档:commons-collections-3.2.1-javadoc-API文档-中文(简体)-英语-对照版.zip 对应...

Global site tag (gtag.js) - Google Analytics