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中配置:

旧配置

		CAS Authentication Filter
		org.jasig.cas.client.authentication.AuthenticationFilter
		
			casServerLoginUrl
			https://localhost:8443/cas/login
		
		
			serverName
			http://localhost:8080 
		
		
            renew
            false
        
        
            gateway
            false
        
	

修改成这样,注:其它路径mapping不用改。

	    CAS Authentication Filter
	    org.demo.user.common.RemoteAuthenticationFilter
	    
	        localLoginUrl
	        http://localhost:8080/app/mylogin.jsp
	    
	    
	        casServerLoginUrl
	        https://localhost:8443/cas/remoteLogin
	    
	    
	        serverName
	        http://localhost:8080
	    
	


3.加上你自己定义的登录界面,注:我修改了一些网上介绍的代码:
<%@ page language="java" contentType="text/html; charset=utf-8"
	pageEncoding="utf-8"%>



APP1客户端登录




	

APP1客户端登录

用户名:
密  码:

至此客户端完结!

服务端篇:注我没有加上登出代码,因为登出代码可以使用原有的
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 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 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配置,原有基础上新增这两句:

     cas
     /remoteLogin
 


4.在cas-servlet.xml中最后面增加以下信息:

		
			
				remoteLoginController
			
		
		
			
				
			
		
	

	
		
		
	

	
		
			
		
	

	
		
	

	

	

	
		
		
	


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:
<%@ 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" %> --%>



完结...

请关注下一篇,shiro + cas sso实现客户端自定义登录界面完整实现
  • trunk.rar (531.8 KB)
  • 下载次数: 128

你可能感兴趣的:(CAS-3.2.1自定义客户端登录界面----完整篇)