CAS-3.2.1自定义客户端登录界面----完整篇
发布日期:2022-02-09 20:39:05 浏览次数:4 分类:技术文章

本文共 20081 字,大约阅读时间需要 66 分钟。

[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能够正确引用到就行:
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客户端登录

用户名:
密  码:
至此客户端完结!
[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
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实现客户端自定义登录界面完整实现

转载地址:https://blog.csdn.net/iteye_12884/article/details/82601683 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:手工初始化spring beans
下一篇:SVN环境搭建

发表评论

最新留言

关注你微信了!
[***.104.42.241]2024年03月19日 03时53分18秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章