您的位置:首页 > 其它

单点登录(八)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信息的,因此我们首先需要重新实现一个自己的认证过滤器,以下是我们自己的认证过滤器的代码:

/**
* @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认证请求。

这样我们的自定义客户端远程登陆页面就完成了,现在赶快测试一下吧~。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: