单点登录(八)cas支持客户端登录——客户端
2013-09-27 10:13
155 查看
客户端即指使用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信息的,因此我们首先需要重新实现一个自己的认证过滤器,以下是我们自己的认证过滤器的代码:
以上黄色背景代码为修改部分,其余代码均拷贝自org.jasig.cas.client.authentication.AuthenticationFilter,可以看到我们为原有的认证过滤器增加了一个参数localLoginUrl。在
applicationContext-cas.xml,将authenticationFilter指向的类换成我们的RemoteAuthenticationFilter,并添加localLoginUrl 参数
applicationContext-cas.xml:
cas-client.properties:
最后来看看我们的登录页面login.jsp:
以上黄色背景字中,我们首先将表单action指向服务器端remoteLogin,然后在里面设置了两个重要的hidden域以传递 loginUrl和submit参数,前者用于告诉服务器失败后转向何处,后者告诉服务器端webflow现在要进行提交而不是TGT认证请求。
这样我们的自定义客户端远程登陆页面就完成了,现在赶快测试一下吧~。
客户端实现目标
客户端实现主要需要满足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/remoteLogin local.loginUrl=http://localhost:8080/index.jsp cas.client.serverName=http://localhost:8080/index.jsp cas.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); } // 注入service var service = getParam("service"); if (service) document.getElementById("service").value = decodeURIComponent(service); // document.getElementById("service").value = service; else document.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认证请求。
这样我们的自定义客户端远程登陆页面就完成了,现在赶快测试一下吧~。
相关文章推荐
- 单点登录(七)cas支持客户端登录——服务器端
- 单点登录(十七)----cas4.2.x登录mongodb验证方式成功后返回更多信息更多属性到客户端
- CAS单点登录python客户端分析
- cas单点登录客户端(asp.net mvc4)配置
- CAS客户端使用Ajax登陆(即保留原有客户端登录页面)
- ASP.NET MVC配置客户端单点登录CAS
- CAS服务器集群和客户端集群环境下的单点登录和单点注销
- 单点登录(十八)----cas4.2.x客户端增加权限控制shiro
- CAS修改源码支持多种方式登录认证
- macOS微信客户端插件,支持免认证登录、多账号登录以及防撤回
- 单点登录(十八)----cas4.2.x客户端增加权限控制shiro
- 单点登录(四)cas客户端配置
- 单点登录学习(3)CAS客户端配置
- 单点登录 - 添加CAS客户端
- cas 单点登录服务端客户端配置
- 单点登录cas综述之cas4.2.7服务端+cas客户端+示例程序+环境搭建说明-陈杰
- CAS python 客户端登录分析
- cas 单点登录服务端客户端配置
- javaWeb不允许两个客户端同时使用一个账号登录系统(支持多个tomcat)
- Cas客户端业务系统自动登录处理