1、修改/WEB-INF/web.xml,为cas增加一个/remoteLogin和//remoteLogout的映射,否则总是会转到login那个请求去了:
<servlet-mapping>
<servlet-name>cas</servlet-name>
<url-pattern>/remoteLogin</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>cas</servlet-name>
<url-pattern>/remoteLogout</url-pattern>
</servlet-mapping>
2、然后修改cas-servlet.xml文件,增加对/remoteLogin和/remoteLogout映射的处理,需要增加两个新流程:
<!-- 增加远程控制者,允许以/remote请求启动remote控制流程 -->
<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>
<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.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>
<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="remoteLogoutFlowRegistry" flow-builder-services="builder">
<webflow:flow-location path="/WEB-INF/remoteLogout-webflow.xml" id="remoteLogout"/>
</webflow:flow-registry>
<bean id="remoteLogoutAction" class="com.cas.web.flow.RemoteLogoutAction"
p:argumentExtractors-ref="argumentExtractors"
p:warnCookieGenerator-ref="warnCookieGenerator"
p:ticketGrantingTicketCookieGenerator-ref="ticketGrantingTicketCookieGenerator"
p:centralAuthenticationService-ref="centralAuthenticationService" />
3、流程定义的xml文件:可以看到上面将请求指向了webflow配置文件/WEB-INF/remoteLogin-webflow.xml和/WEB-INF/remoteLogout-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>
<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>
以下是remoteLogout-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="remoteLogout">
<!-- 远程登陆主要Action -->
<action-state id="remoteLogout">
<evaluate expression="remoteLogoutAction" />
<transition on="success" to="remoteCallbackView" />
</action-state>
<end-state id="remoteCallbackView" view="remoteCallbackView" />
<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、流程首先由Action类进行处理,成功或失败都将转回 remoteCallbackView,这样就返回了远程的登录页面。
4.1、登录处理Action类:
package com.cas.web.flow;
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;
/**
* 远程登陆票据提供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)));
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;
}
}
4.2、退出处理Action类:
package com.cas.web.flow;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.jasig.cas.CentralAuthenticationService;
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;
public class RemoteLogoutAction extends AbstractAction {
@NotNull
private CookieRetrievingCookieGenerator ticketGrantingTicketCookieGenerator;
@NotNull
private CookieRetrievingCookieGenerator warnCookieGenerator;
@NotNull
private CentralAuthenticationService centralAuthenticationService;
@NotNull
@Size(min=1)
private List<ArgumentExtractor> argumentExtractors;
private boolean pathPopulated = false;
@Override
protected Event doExecute(final RequestContext context) throws Exception {
final HttpServletRequest request = WebUtils.getHttpServletRequest(context);
final HttpServletResponse response = WebUtils.getHttpServletResponse(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)));
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);
context.getFlowScope().put("remoteLoginUrl", request.getParameter("service"));
final String ticketGrantingTicketId = this.ticketGrantingTicketCookieGenerator.retrieveCookieValue(request);
if (ticketGrantingTicketId != null) {
this.centralAuthenticationService.destroyTicketGrantingTicket(ticketGrantingTicketId);
this.ticketGrantingTicketCookieGenerator.removeCookie(response);
this.warnCookieGenerator.removeCookie(response);
}
return result("success");
}
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;
}
public void setCentralAuthenticationService(final CentralAuthenticationService centralAuthenticationService){
this.centralAuthenticationService = centralAuthenticationService;
}
}
4.3、cas的登录页面,username和password是传递给
<var name="credentials" class="org.jasig.cas.authentication.principal.UsernamePasswordCredentials" />
这个变量的,判断用户登录信息也以这个变量传递给org.jasig.cas.web.flow.AuthenticationViaFormAction的submit方法来进行,很遗憾,这个变量没法中间赋值(反正我不会),所以我重构了一个方法:
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(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(e, messageContext);
return "error";
}
}
这个方法直接从request里获得username和password,然后验证。
5、配置remoteCallbackView显示节点,修改src/default_views.properties文件,增加remoteCallbackView配置:
### 配置远程回调页面
remoteCallbackView.(class)=org.springframework.web.servlet.view.JstlView
remoteCallbackView.url=/WEB-INF/view/jsp/default/ui/remoteCallbackView.jsp
6、创建/WEB-INF/view/jsp/default/ui/remoteCallbackView.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"%>
<html>
<head>
<script type="text/javascript">
var remoteUrl = "${remoteLoginUrl}?validated=true";
// 构造错误消息
var errorMessage = "";
<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>
// 构造service
var service = "";
<c:if test="${service != null && service != ''}">
service = "&service=" + encodeURIComponent("${service}");
</c:if>
// 跳转回去
window.location.href = remoteUrl + errorMessage + service;
</script>
</head>
<body>
${remoteLoginMessage}
</body>
</html>
CAS 之自定义登录页实践 http://denger.iteye.com/blog/809170
https://wiki.jasig.org/display/CAS/Using+CAS+without+the+Login+Screen