环境:
tomcat7.05
cas-server-3.4.5
cas-client-3.2.0
呃,你没看错,是用iframe实现。这个不算是一个好的方案,甚至我自己都有点看不起自己的感觉。但却是一个简单易用并最能兼容以后CAS-server更新的一套方案(还用说么,你都用iframe了)。目前项目比较紧急没时间去研究Spring WebFlow 3的情况下只好出此下策了,大神请喷。
这个题目相信使用过CAS的朋友都不会陌生了。由于安全性的问题,CAS-Server 一直不建议把验证放到客户端之上。所以不提供验证表单的接口,或者说必须我们去开发扩展。而在实际应用中,很多单点登录的表单却是在客户端中完成,例如在某门户首页的登陆框。这些是不可能在Cas-Server上直接改造成门户的。所以造成了必须在门户中进行CAS服务器表单验证的局面。
当然这一切都是建立在我目前对CAS的熟悉程度来说的,欢迎指教。
其实网上也有比较多的方案,但基本上是版本对不上了,我也逼于无奈才采取这种比较傻瓜式的方法进行验证。
不管怎样,虽然觉得说名字大家都知道实现原理了,但也应该照顾一些不理解的。
1.自己的登录页面中用iframe加载cas-server的登录页面。
2.当用户输入账号密码登录的时候,同时把账号密码信息传入cas-server登录页面,并且提交。
3.若成功则刷新页面,登录窗口消失。若失败则提示用户。这个iframe相对用户来说是不可见的。
引用一下前辈的客户端实现目标描述:
引用
客户端实现目标
客户端实现主要需要满足5个case:
1. 用户未在中央认证服务器登陆,访问客户端受保护资源时,客户端重定向到中央认证服务器请求TGT认证,认证失败,转回客户端登陆页面,保证受保护资源URL信息不丢失
2. 用户未在中央认证服务器登陆,访问客户端登陆页面时,客户端重定向到中央认证服务器请求TGT认证,认证失败,转回客户端登陆页面,此次登录页面不再受保护,允许访问
3. 用户已在中央认证服务器登陆,访问客户端受保护资源时,客户端重定向到中央认证服务器请求TGT认证,认证成功,直接转回受保护资源
4. 用户在客户端登陆页面提交用户名密码,客户端将用户名密码信息提交给服务器端,认证失败,转回客户端登陆页面,携带失败信息并保证转到登陆页面前受保护资源URL信息不丢失
5. 用户在客户端登陆页面提交用户名密码,客户端将用户名密码信息提交给服务器端,认证成功,转回转到登陆页面前受保护资源
而技术方面没什么太大的要求,最主要是一些基本的javascript知识。
一、改造Cas-Server默登陆页面,让它对反馈信息支持。
打开 WEB-INF\view\jsp\default\ui\casLoginView.jsp
在<jsp:directive.include file="includes/bottom.jsp" />之上加上以下代码
<%-- add by Kenny Begin --%>
<script type="text/javascript">
function showErrorMsg(){
if(window.parent.window.document.getElementById('cas_serverFrame')){
var statusEl = document.getElementById('status');
if(statusEl && statusEl.className === 'errors'){
window.parent.window.alert('账号或密码有误!');
}
}
}
if(top.location !== self.location){
showErrorMsg();
}
</script>
<%-- add by Kenny End --%>
其中cas_serverFrame为我自定义登陆页面中iframe的id,稍后会看到。判断其实可以简化些,或者其他可以自由发挥了,供参考。
查找页面中的submit表单提交按钮,如果name为submit的话记得改掉(默认是name=submit),
因为如果在html中有任何一个元素的name属性为submit时,则用javascript操作form元素时是无法执行submit(),会提示'不存在的方法'。这个卡了我不少时间。
二、门户中自定义登陆表单页面
<%--
Document : login
Created on : 2011-1-11, 3:07:16
Author : Kenny
--%>
<%@ 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" src="js/clientLogin.js"> </script>
</head>
<body>
<h1>远程CAS客户端登陆页面</h1>
<% if (request.getRemoteUser() == null) { %>
<div id="errorMessage"></div>
<form id="myLoginForm" action="https://localhost:8443/cas-server/login" method="post">
<input type="hidden" id="service" name="service" value="">
<input type="hidden" name="loginUrl" value="https://localhost:8443/sso_demo/login.jsp">
<input type="hidden" name="submit" 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="button" value="登陆" id="loginButt" /></td>
</tr>
</table>
</form>
<iframe style="display:none;" src="" name="cas_serverFrame" id="cas_serverFrame" ></iframe>
<script type="text/javascript">
loadedIFrame(window.event);
</script>
<% } else { %>
<div class="welcome">您好:<%= request.getRemoteUser() %></div>
<div id="logout">
<a href="https://localhost:8443/cas-server/logout?service=https://localhost:8443/sso_demo/login.jsp">单点登出</a>
</div>
<% } %>
</body>
</html>
三、登陆页面中用到的clientLogin.js
var constants = {
loginButt : 'loginButt',
clientLoginForm : 'myLoginForm',
casServerFrame : 'cas_serverFrame',
loginFormUser : 'username',
loginFormPassword : 'password',
serverLoginFormId : 'fm1',
redirectURLName : 'service',
loginURL : 'https://localhost:8443/sso_demo/login.jsp',
casServerURL : 'https://localhost:8443/cas-server/login'
}
function initEvents(e){
var loginButtEl = document.getElementById(constants.loginButt);
if(loginButtEl){
registerListener(loginButtEl, 'click', loginCAS);
}
var loginFormEl = document.getElementById(constants.clientLoginForm);
if(loginFormEl){
registerListener(loginFormEl, 'keydown', function(e){
if(isKeyDownEnter(e)){
loginCAS();
}
});
}
}
loginCAS = function(){
var username = document.getElementById(constants.loginFormUser).value;
var password = document.getElementById(constants.loginFormPassword).value;
var casServerFrameDoc = document.getElementById(constants.casServerFrame).contentWindow.document;
var cas_username = casServerFrameDoc.getElementById(constants.loginFormUser);
var cas_password = casServerFrameDoc.getElementById(constants.loginFormPassword);
cas_username.value = username;
cas_password.value = password;
var cas_loginForm = casServerFrameDoc.forms[constants.serverLoginFormId];
if(cas_loginForm){
cas_loginForm.submit();
}else{
alert('cas_loginForm is undefined');
}
}
function resetWindow(){
if (top.location !== self.location) {
top.location = self.location;
}
}
function loadedIFrame(e){
var frameURL = constants.casServerURL;
// frameURL += '?service=';
// var redirectURL = getParam(constants.redirectURLName);
// redirectURL = redirectURL ? redirectURL : constants.loginURL;
// frameURL += redirectURL;
var cas_serverFrame = document.getElementById(constants.casServerFrame);
if(!cas_serverFrame)return;
cas_serverFrame.src = frameURL;
if(cas_serverFrame.attachEvent){
cas_serverFrame.attachEvent('onload', function(){
initEvents(e);
});
}else{
cas_serverFrame.onload = function(){
initEvents(e);
}
}
}
function registerListener(el, eventName, handle) {
if (window.attachEvent) {
el.attachEvent('on' + eventName, handle);
} else if (window.addEventListener) {
el.addEventListener(eventName, handle, false);
}
}
function isKeyDownEnter(e) {
e = e ? e : window.event;
var _isie = (window.attachEvent) ? true : false;
var _key = '';
if (_isie) {
_key = e.keyCode;
} else {
_key = e.which;
}
if (_key == 13)
return true;
else
return false;
}
function getParam(name) {
var queryString = window.location.search;
var param = queryString.substr(1, queryString.length - 1).split("&");
for (var i = 0; i < param.length; i++) {
var keyValue = param[i].split("=");
if (keyValue[0] == name) return keyValue[1];
}
return null;
}
四、为了实现验证成功后跳转到验证之前的URL资源,cas-server的验证成功后的redirect修改是必不可少了。
①修改WEB-INF/login-webflow.xml文件
搜索<end-state id="viewGenericLoginSuccess" view="casLoginGenericSuccessView" />
修改为
<end-state id="viewGenericLoginSuccess" view="redirectView" />
②添加redirectView
打开WEB-INF\classes\default_views.properties
增加如下字段:
### 跳转回调页面
redirectView.(class)=org.springframework.web.servlet.view.JstlView
redirectView.url=/WEB-INF/view/jsp/default/ui/redirect.jsp
③新建/WEB-INF/view/jsp/default/ui/redirect.jsp
代码参考如下:
<%--
Document : redirect
Created on : 2011-1-11, 17:42:09
Author : Kenny
--%>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<script type="text/javascript">
var defLoginURL = 'https://localhost:8443/sso_demo/login.jsp';
function redirect(){
var _window = top.location != self.location? window.parent.window : window;
var serviceURL = getParam('service', _window);
if(!serviceURL){
serviceURL = defLoginURL;
}
_window.location.href = 'login?service='+serviceURL;
}
function getParam(name, _window) {
_window ? _window : window;
var queryString = _window.location.search;
var param = queryString.substr(1, queryString.length - 1).split("&");
for (var i = 0; i < param.length; i++) {
var keyValue = param[i].split("=");
if (keyValue[0] == name) return keyValue[1];
}
return null;
}
</script>
</head>
<body onload="redirect()">
</body>
</html>
附上本帖子提及过的文件和我测试的客户端项目,详细见附件。
题外:
本次开发是在netbeans上开发的,所以用eclipse的朋友导入demo项目可能要麻烦一些。不知道为何我的MyEclipse上的m4eclipse怎么没了,所以用不了maven插件,懒得弄就直接用netbeans了,因为NetBeans 6.9版本对maven支持很好。
参考:
http://fallenlord.blogbus.com/logs/36905130.html
http://fallenlord.blogbus.com/logs/36907990.html
http://fallenlord.blogbus.com/logs/36907044.html
分享到:
相关推荐
让CAS支持客户端自定义登陆页面----服务器篇--.doc
让CAS支持客户端自定义登陆页面----服务器篇--.doc
让CAS支持客户端自定义登陆页面----服务器篇-.pdf
让CAS支持客户端自定义登陆页面----服务器篇.doc
NULL 博文链接:https://lsz1023-126-com.iteye.com/blog/2098973
----------------------------使用--------------------------------- ...14.CAS-实现自定义返回用户登录信息 15.CAS-页面缓存记住我 ------------------------------建表脚本在文件里登录用户/密码:ll/123456
包含cas源码、cas使用说明文档(包含配置信息)、连接数据库所需jar包、cas服务端自定义返回值等
django-cas-ng, Django ( 中央身份验证服务) 客户端 Django CAS django-cas-ng 是中央认证服务( CAS ) 客户端实现。 这里项目继承自 django-cas ( 自从 2013年04月 之后还没有更新过) 。 NG代表"下一代"。 我们的...
cas-client-3.2.1-release.zip
单点登录 sso cas带超详细文档,包含(cas-server-webapp-4.0.0.war、c3p0-0.9.1.2.jar、cas-client-core-3.3.3.jar、cas-server-support-jdbc-4.0.0.jar、cas-server-webapp-support-4.0.0.jar、commons-logging-...
cas的jar包,包括: 服务器端:cas-server-3.4.10-release.zip 客户端:cas-client-3.2.1-release.zip
cas的jar包,包括: 服务器端:cas-server-4.2.1-release.zip 客户端:cas-client-3.3.3-release.zip
单点登录服务端项目cas-server 单点登录服务端项目cas-server 单点登录服务端项目cas-server 单点登录服务端项目cas-server 单点登录服务端项目cas-server 单点登录服务端项目cas-server 单点登录服务端项目cas-...
cas的jar包,包括: 服务器端:cas-server-3.4.10-release.zip 客户端:cas-client-3.2.1-release.zip
cas6.x需要使用jdk11版本,目前cas5.3.15.1是8的最高版本,但是稳定性cas-5.3.14.war最佳,本次基于jdk8-201编译
Liferay6.0.5 与cascas-server-3.4.3.1-release.zip 整合
详细描述了cas 自定义登陆页面的配置与demo