单点登录(八)cas支持客户端登录——客户端

来源:互联网 发布:mysql 存储过程 循环 编辑:程序博客网 时间:2024/05/21 17:06

客户端即指使用CAS中央认证服务器的应用程序,而不是指用户浏览器


客户端实现目标
客户端实现主要需要满足5个case:
·          1. 用户未在中央认证服务器登陆,访问客户端受保护资源时,客户端重定向到中央认证服务器请求TGT认证,认证失败,转回客户端登陆页面,保证受保护资源URL信息不丢失
·          2. 用户未在中央认证服务器登陆,访问客户端登陆页面时,客户端重定向到中央认证服务器请求TGT认证,认证失败,转回客户端登陆页面,此次登录页面不再受保护,允许访问
·          3. 用户已在中央认证服务器登陆,访问客户端受保护资源时,客户端重定向到中央认证服务器请求TGT认证,认证成功,直接转回受保护资源
·          4. 用户在客户端登陆页面提交用户名密码,客户端将用户名密码信息提交给服务器端,认证失败,转回客户端登陆页面,携带失败信息并保证转到登陆页面前受保护资源URL信息不丢失
·          5. 用户在客户端登陆页面提交用户名密码,客户端将用户名密码信息提交给服务器端,认证成功,转回转到登陆页面前受保护资源
        对于case  1和case  3,普通的CAS客户端即可满足需求,但对于case  4和case  5,则需要我们定制自己的登陆页面。对于case 2,主要是需要满足部分登陆页面希望在用户未登陆状态显示登陆框,在已登陆状态显示用户欢迎信息的需求,实现这个需求我们是通过让CAS客户端认证器满足一个排除约定,即当用户请求路径为登陆页面且带有validated=true的参数时,即不进行重定向TGT认证请求

客户端修改方案

        根据服务器流程修改方案,我们可以知道,所有的远程请求都必须携带有loginUrl参数信息以使得服务器端知道在认证失败后转向客户端登陆页面。而在CAS客户端上,上一节的case  4和case  5,我们主要通过提交表单的方式传递loginUrl,而case  1,  case  3则是依靠org.jasig.cas.client.authentication.AuthenticationFilter类进行的转向,但使用AuthenticationFilter转向时,是没有loginUrl信息的,因此我们首先需要重新实现一个自己的认证过滤器,以下是我们自己的认证过滤器的代码:

/** * @author xiaoxiao * 日期:2013-8-7 上午11:23:02 * The default character set is UTF-8.*/ package td.sso.filter;import java.io.IOException;import java.net.URL;import java.net.URLEncoder;import javax.servlet.FilterChain;import javax.servlet.FilterConfig;import javax.servlet.ServletException;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpSession;import org.jasig.cas.client.authentication.DefaultGatewayResolverImpl;import org.jasig.cas.client.authentication.GatewayResolver;import org.jasig.cas.client.util.AbstractCasFilter;import org.jasig.cas.client.util.CommonUtils;import org.jasig.cas.client.validation.Assertion;/** * 远程认证过滤器. * 由于AuthenticationFilter的doFilter方法被声明为final, * 只好重新实现一个认证过滤器,支持localLoginUrl设置. */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 {    System.out.println("调用RemoteAuthenticationFilter--->");        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 String serviceUrl = constructServiceUrl(request, response);        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"));                System.out.println(!isValidatedLocalLoginUrl && CommonUtils.isBlank(ticket) && assertion == null && !wasGatewayed);        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);            System.out.println(serviceUrl);            if (log.isDebugEnabled()) {                log.debug("Constructed service url: " + serviceUrl);            }            String urlToRedirectTo = CommonUtils.constructRedirectUrl(                    this.casServerLoginUrl, getServiceParameterName(),                    serviceUrl, this.renew, this.gateway);            System.out.println("RemoteAuthenticationFilter  urlToRedirectTo\t"+urlToRedirectTo+",loginUrl\t"+localLoginUrl);            // 加入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;    }    } 
以上黄色背景代码为修改部分,其余代码均拷贝自org.jasig.cas.client.authentication.AuthenticationFilter,可以看到我们为原有的认证过滤器增加了一个参数localLoginUrl。在

applicationContext-cas.xml,将authenticationFilter指向的类换成我们的RemoteAuthenticationFilter,并添加localLoginUrl 参数


applicationContext-cas.xml:

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:context="http://www.springframework.org/schema/context"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd       "><context:property-placeholder location="classpath:cas-client.properties" /><!-- 负责用户的认证 --><bean name="authenticationFilter"class="com.talkingdatawebsite.sso.filter.RemoteAuthenticationFilter"><property name="localLoginUrl" value="${local.loginUrl}"></property><property name="casServerLoginUrl" value="${cas.server.loginUrl}" /><property name="renew" value="${cas.server.renew}" /><property name="gateway" value="${cas.server.gateway}" /><property name="service" value="${cas.client.serverName}" /></bean><!-- 对认证ticket进行校验 --><bean name="ticketValidationFilter"class="org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter"><property name="service" value="${cas.client.serverName}" /><property name="ticketValidator"><bean class="org.jasig.cas.client.validation.Cas10TicketValidator"><constructor-arg index="0" value="${cas.server.url}" /></bean></property></bean></beans>

cas-client.properties:

cas.server.url=https://www.talkingdata.net:8446/cas.server.loginUrl=https://www.talkingdata.net:8446/remoteLoginlocal.loginUrl=http://localhost:8080/index.jspcas.client.serverName=http://localhost:8080/index.jspcas.server.renew=false cas.server.gateway=false 

最后来看看我们的登录页面login.jsp:

<%@ 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>远程CAS客户端登陆页面</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.substring(1, queryString.length).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);}// 注入servicevar service = getParam("service");if (service)document.getElementById("service").value = decodeURIComponent(service);// document.getElementById("service").value = service;elsedocument.getElementById("service").value = location.href;}</script></head><body ><h1>远程CAS客户端client1.1登陆页面</h1><%if (request.getRemoteUser() == null) {%><div id="errorMessage"></div><form id="myLoginForm" action="https://www.talkingdata.net:8446/remoteLogin"method="post"><input type="hidden" id="service" name="service" value=""><input type="hidden" name="loginUrl" value="http://localhost:8082/login.jsp"><input type="hidden" name="loginsubmit" value="true" /><table><tr><td>用户名:</td><td><input type="text" id="username" name="username"></td></tr><tr><td>密  码:</td><td><input type="password" id="password" name="password"></td></tr><tr><td colspan="2"><input type="submit" value="登陆" /></td></tr></table></form><script type="text/javascript">init()</script><%} else {%><div class="welcome">您好:<%=request.getRemoteUser()%></div><div id="logout"><a href="https://www.talkingdata.net:8446/logout?service=http://localhost:8082/">退出</a>    <a href="http://localhost:8081">我要去client1</a></div><%}%></body></html>

以上黄色背景字中,我们首先将表单action指向服务器端remoteLogin,然后在里面设置了两个重要的hidden域以传递  loginUrl和submit参数,前者用于告诉服务器失败后转向何处,后者告诉服务器端webflow现在要进行提交而不是TGT认证请求。


这样我们的自定义客户端远程登陆页面就完成了,现在赶快测试一下吧~。