[b]前言:[/b]在已有的CAS SSO架构代码上,经过4天的研究终于完成了客户端登录界面需求,其中多亏网上的资料【让CAS支持客户端自定义登陆页面——服务器新篇与客户端新篇】本文也是基于此完成的,不过我修改了一些代码,CAS SSO的内部流程我只了解了60%,如果你想看CAS SSO原理,请忽略此文章;本文致力提供完整,详细的搭建流程,争取让你一次成功!附件中有本文工程,分客户端与服务端,采用maven搭建,想要运行请配置好maven环境,数据库采用mysql。
原文链接地址:[url]http://lsz1023-126-com.iteye.com/blog/2098973[/url]
[b]实现原理:[/b]
一、 逻辑
客户端修改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响应界面,校验错误时用来通知客户端登录界面。
[b]客户端篇:[/b]
1.替换原来过滤器org.jasig.cas.client.authentication.AuthenticationFilter,改成自己的过滤器RemoteAuthenticationFilter.java,这个过滤器可以自己随便放到哪个包中,保证web.xml能够正确引用到就行:
2.web.xml中配置:
旧配置
修改成这样,注:其它路径mapping不用改。
3.加上你自己定义的登录界面,注:我修改了一些网上介绍的代码:
至此客户端完结!
[b]服务端篇:注我没有加上登出代码,因为登出代码可以使用原有的[/b]
1.添加客户端登录Action,org.jasig.cas.web.flow.RemoteLoginAction:
2.重写LoginForm代码,org.jasig.cas.web.flow.AuthenticationViaFormAction重写,此类基本上采用原有代码,只是添加了获取用户名与密码的代码:
3.web.xml配置,原有基础上新增这两句:
4.在cas-servlet.xml中最后面增加以下信息:
5.新建一个文件与login-webflow.xml同级,remoteLogin-webflow.xml:
6.加上一个回调视图配置,在default_views.properties中新增以下两句:
### 配置远程回调页面
remoteCallbackView.(class)=org.springframework.web.servlet.view.JstlView
remoteCallbackView.url=/WEB-INF/view/jsp/default/ui/remoteCallbackView.jsp
其它不变
7.加上回调页面jsp:
完结...
请关注下一篇,shiro + cas sso实现客户端自定义登录界面完整实现
原文链接地址:[url]http://lsz1023-126-com.iteye.com/blog/2098973[/url]
[b]实现原理:[/b]
一、 逻辑
客户端修改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响应界面,校验错误时用来通知客户端登录界面。
[b]客户端篇:[/b]
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>密 码:</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>
至此客户端完结!
[b]服务端篇:注我没有加上登出代码,因为登出代码可以使用原有的[/b]
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 '' && externalContext.requestParameterMap['gateway'] neq null && 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实现客户端自定义登录界面完整实现