JForum论坛单点登录的几种实现方式 (CAS和Cookie)
王保政
Email:[email protected]
<chsdate isrocdate="False" islunardate="False" day="4" month="8" year="2007" w:st="on"><span lang="EN-US"><font face="Times New Roman" size="3">2007-8-4</font></span></chsdate>
一、用CAS实现Jforum的单点登录
(一)CAS客户端应用的web.xml配置
CAS和jforum的安装过程本文就不介绍了,下面是jforum配置CAS服务器连接需要在web.xml中添加的配置:
<filter>
<filter-name>CASFilter</filter-name>
<filter-class>edu.yale.its.tp.cas.client.filter.CASFilter</filter-class>
<init-param>
<param-name>edu.yale.its.tp.cas.client.filter.loginUrl</param-name>
<param-value>https://localhost:8443/cas/login</param-value>
</init-param>
<init-param>
<param-name>edu.yale.its.tp.cas.client.filter.validateUrl</param-name>
<param-value>https://localhost:8443/cas/proxyValidate</param-value>
</init-param>
<init-param>
<param-name>edu.yale.its.tp.cas.client.filter.serverName</param-name>
<param-value>localhost:8000</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CASFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
一开始我调试jforum的单点登录的时候,首先在地址栏输入http://localhost:8000/cas/login?service=http://localhost:8000/jforum/user.jsp
(其中user.jsp是我自己添加JSP页面做测试用 ),在CAS页面输入用户名和口令确认后,页面自动跳转到http://localhost:8000/jforum/user.jsp?ticket= ticket=ST-5-Ih4fJNYyWlhFfywfeOwuVAFZn1vKOOVAgpD-20
Ticket是生成的票据,然后用这个ticket做参数访问:
https://localhost:8443/cas/serviceValidate?service= http://localhost:8000/jforum/user.jsp&ticket= ST-5-Ih4fJNYyWlhFfywfeOwuVAFZn1vKOOVAgpD-20
如果成功,返回的页面中出现登录成功的用户名,打开html源文件,内容为:
<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
<cas:authenticationSuccess>
<cas:user>admin</cas:user>
</cas:authenticationSuccess>
</cas:serviceResponse>
如果失败,页面显示ticket 'ST-2-4ffpnvHKv1NH5So7uWvFdVNrbHsaPAfROXx-20' not recognized,html源文件内容:
<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
<cas:authenticationFailure code='INVALID_TICKET'>
ticket 'ST-2-4ffpnvHKv1NH5So7uWvFdVNrbHsaPAfROXx-20' not recognized
</cas:authenticationFailure>
</cas:serviceResponse>
(二)Jforum配置单点登录
Jforum的WEB-INF\config目录下有一个SystemGlobals.properties文件,配置SSO需要更改此文件的几个配置参数:
authentication.type = sso
#CasUserSSO类用于CAS单点登录,下面将讲述此类的代码
sso.implementation = com.iss.common.sso.CasUserSSO
#CasCookieSSO是基于Cookie的一个简单的单点登录,代码见下文
#sso.implementation = com.iss.common.sso.CasCookieSSO
#下面的redirect我也不太清楚具体有什么用
sso.redirect = https://localhost:8443/cas/
(三)当把jforum的web.xml的CAS filter注释掉以后,使用下面的JSP通过CAS单点登录票据验证的一个示例,其中URL应带service参数,如:
http://localhost:8000/cas/login?service=http://localhost:8000/jforum/testsso.jsp
登录CAS成功后,返回的页面url带有一个ticket参数,见下面的返回URL:
http://localhost:8000/jforum/testsso.jsp?ticket=ST-5-VbM7tdMPeLD1WlH2ZGnocVGTbAY73ff4y17-20
Tomcat控制台显示下面的输出说明票据认证通过:
<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
<cas:authenticationSuccess>
<cas:user>admin</cas:user>
</cas:authenticationSuccess>
</cas:serviceResponse>
下面是testsso.jsp:
<%@ page contentType="text/html;charset=GBK"%>
<%@ page import="java.util.*"%>
<%@ page import="net.jforum.context.RequestContext"%>
<%@ page import="net.jforum.entities.UserSession"%>
<%@ page import="net.jforum.util.preferences.ConfigKeys"%>
<%@ page import="net.jforum.util.preferences.SystemGlobals"%>
<%@ page import="org.apache.log4j.Logger"%>
<%@ page import="net.jforum.sso.*"%>
<%@ page import="java.io.*"%>
<%@ page import="edu.yale.its.tp.cas.client.*"%>
<%
String username = null;
String errorCode = null;
String errorMessage = null;
String xmlResponse = null;
String ticket = request.getParameter("ticket");
System.out.println("获取的ticket为:"+ticket);
ServiceTicketValidator sv = new ServiceTicketValidator();
if(ticket != null)
{
try
{
sv.setCasValidateUrl("https://localhost:8443/cas/serviceValidate");
sv.setServiceTicket(ticket);
sv.setService("http://localhost:8000/jforum/testsso.jsp");
sv.validate();
xmlResponse = sv.getResponse();
if (sv.isAuthenticationSuccesful())
{
username = sv.getUser();
System.out.println("认证成功,获得的用户名为:");
System.out.println(username);
}
else
{
errorCode = sv.getErrorCode();
errorMessage = sv.getErrorMessage();
System.out.println("认证失败!!!!!!!!!!!");
}
}
catch (Exception exc)
{
System.out.println(exc.getMessage());
}
}
%>
edu.yale.its.tp.cas.client.ServiceTicketValidator是casclient.jar中的类。
(四)当Jforum的web.xml中不配置CAS Filter时,如何实现单点登录类?
这种情况类似于(三),但问题是如何在Java类中实现单点登录,而不是在jsp中实现。
我从网上找到一个名为CasUserSSO.java的程序,并按步骤(二)的说明将此类作为单点登录类配置,但编译运行后,在浏览器地址栏输入http://localhost:8000/cas/login?service=http://localhost:8000/jforum/user.jsp
(user.jsp是我自己写的一个显示简单输出的jsp文件, http://localhost:8000/jforum/user.jsp是我在CasUserSSO.java里面作为service参数的),CAS登录成功后Tomcat控制台没有显示单点登录的认证信息,好象CasUserSSO没有调用到,于是我修改了CasUserSSO.java,将
sv.setService("http://localhost:8000/jforum/user.jsp")改为sv.setService(java.net.URLEncoder.encode(http://localhost:8000/jforum/forums/jforum.page?module=forums&action=list"));
重新编译运行后,地址栏中输入http://localhost:8000/cas/login?service= http%<chmetcnv w:st="on" unitname="a" sourcevalue="3" hasspace="False" negative="False" numbertype="1" tcsc="0">3A</chmetcnv>%<chmetcnv w:st="on" unitname="F" sourcevalue="2" hasspace="False" negative="False" numbertype="1" tcsc="0">2F</chmetcnv>%2Flocalhost%<chmetcnv w:st="on" unitname="a" sourcevalue="3" hasspace="False" negative="False" numbertype="1" tcsc="0">3A</chmetcnv>8000%2Fjforum%2Fforums%2Fjforum.page%3Fmodule%3Dforums%26action%3Dlist
注意service等号右面的必须是java.net.URLEncoder.encode转换后的字符串(http://localhost:8000/jforum/forums/jforum.page?module=forums&action=list转换后的字符串为http%<chmetcnv w:st="on" unitname="a" sourcevalue="3" hasspace="False" negative="False" numbertype="1" tcsc="0">3A</chmetcnv>%<chmetcnv w:st="on" unitname="F" sourcevalue="2" hasspace="False" negative="False" numbertype="1" tcsc="0">2F</chmetcnv>%2Flocalhost%<chmetcnv w:st="on" unitname="a" sourcevalue="3" hasspace="False" negative="False" numbertype="1" tcsc="0">3A</chmetcnv>8000%2Fjforum%2Fforums%2Fjforum.page%3Fmodule%3Dforums%26action%3Dlist)
注意地址栏中service后不要带空格,在地址栏输入http://localhost:8000/cas/login?service=http%3A%2F%2Flocalhost%3A8000%2Fjforum%2Fforums%2Fjforum.page%3Fmodule%3Dforums%26action%3Dlist,CAS登录后(我的jforum配的超级管理员帐号是admin/123),出现jforum页面,这说明CAS单点登录成功!!!
Tomcat控制台显示:
<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
<cas:authenticationSuccess>
<cas:user>admin</cas:user>
</cas:authenticationSuccess>
</cas:serviceResponse>
请大家一定要注意,在地址栏中service=后面的URL必须是encode转换后的,因为开始没转换,我还以为是CasUserSSO写的有问题,网上下载的CasUserSSO中的
sv.setCasValidateUrl("https://localhost:8443/cas/login");我认为是有问题的,会出现一个很奇怪的异常,显示信息为org.xml.sax.SAXParseException: The reference to entity "ticket" must end with the ';' delimiter.我改为:
sv.setCasValidateUrl("https://localhost:8443/cas/serviceValidate")则不会出现这个问题。见下面的CasUserSSO.java:
package com.iss.common.sso;
import net.jforum.context.RequestContext;
import net.jforum.entities.UserSession;
import net.jforum.util.preferences.ConfigKeys;
import net.jforum.util.preferences.SystemGlobals;
import org.apache.log4j.Logger;
import net.jforum.sso.*;
import java.util.*;
import java.net.*;
import edu.yale.its.tp.cas.client.*;
public class CasUserSSO implements SSO
{
static final Logger logger = Logger.getLogger(CasUserSSO.class.getName());
public String authenticateUser(RequestContext request)
{
String username = null;
String errorCode = null;
String errorMessage = null;
String xmlResponse = null;
// 开始setServiceTicket单点登录代码
String ticket = request.getParameter("ticket");
logger.info("获取的ticket为:"+ticket);
ServiceTicketValidator sv = new ServiceTicketValidator();
if(ticket != null)
{
try
{
logger.info("ticket为非空!!!!!!!!!!!!!!!!!");
sv.setCasValidateUrl("https://localhost:8443/cas/serviceValidate");
//sv.setCasValidateUrl("https://localhost:8443/cas/login");
System.out.println(java.net.URLEncoder.encode("http://localhost:8000/jforum/forums/jforum.page?module=forums&action=list"));
sv.setService(java.net.URLEncoder.encode("http://localhost:8000/jforum/forums/jforum.page?module=forums&action=list"));
sv.setServiceTicket(ticket);
logger.info("开始验证............");
sv.validate();
xmlResponse = sv.getResponse();
//System.out.println(xmlResponse);
if (sv.isAuthenticationSuccesful())
{
username = sv.getUser();
logger.info("认证成功,获得的用户名为:");
logger.info(username);
}
else
{
errorCode = sv.getErrorCode();
errorMessage = sv.getErrorMessage();
logger.info("认证失败!!!!!!!!!!!");
}
}
catch (Exception exc)
{
System.out.println(exc.getMessage());
}
}
// 结束setServiceTicket单点登录代码
/* System.out.println("开始获取用户名。。。");
username = (String)request.getSessionContext().getAttribute("edu.yale.its.tp.cas.client.filter.user");
System.out.println(username);
System.out.println("结束获取用户名。。。");
logger.info("登录用户为:"+username);*/
return username;
}
public boolean isSessionValid(UserSession userSession,RequestContext request)
{
ServiceTicketValidator sv = new ServiceTicketValidator();
String remoteUser =sv.getUser();
logger.info("RemoteUser为");
logger.info(remoteUser);
//user has since logged out
if (remoteUser == null && userSession.getUserId() != SystemGlobals.getIntValue(ConfigKeys.ANONYMOUS_USER_ID))
{
return false;
}
// user has since logged in
else if (remoteUser != null && userSession.getUserId() == SystemGlobals.getIntValue(ConfigKeys.ANONYMOUS_USER_ID))
{
return false;
}
// user has changed user
else if (remoteUser != null && !remoteUser.equals(userSession.getUsername()))
{
return false;
}
return true;
}
}
通过http://localhost:8000/cas/login?service=http%3A%2F%2Flocalhost%3A8000%2Fjforum%2Fforums%2Fjforum.page%3Fmodule%3Dforums%26action%3Dlist登录成功后(admin/123),jforum显示的页面为admin的已登录页面,如果这时在当前浏览器输入配置了CAS的其他web应用,其他web应用不会弹出cas登录页面,因为cas已经登录过了。我做了一个测试,首先
打开一个新的浏览器窗口,输入一个配置了CAS的web应用地址,如http://localhost:8000/cms
,弹出cas登录窗口,输入admin/123,登录成功,这时候再输入http://localhost:8000/cas/login?service=http%3A%2F%2Flocalhost%3A8000%2Fjforum%2Fforums%2Fjforum.page%3Fmodule%3Dforums%26action%3Dlist,仍然出来一个CAS登录窗口,这好象不是我们所希望的,那么只好再把jforum的web.xml中的CAS过滤器配置好。
(五)当Jforum的web.xml中配置了CAS Filter时,如何实现单点登录类?
现在我们按(一)的说明配置好CAS Filter,重启tomcat,再次输入http://localhost:8000/cas/login?service=http%3A%2F%2Flocalhost%3A8000%2Fjforum%2Fforums%2Fjforum.page%3Fmodule%3Dforums%26action%3Dlist,反而后台报出认证失败的错误,Tomcat的控制台输出:
<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
<cas:authenticationFailure code='INVALID_TICKET'>
ticket 'ST-8-DWSSawgxWPwjjJwkScbIOqDcqR6eTUdZhwi-20' not recogni
zed
</cas:authenticationFailure>
</cas:serviceResponse>
难道上面的CasUserSSO.java的ServiceTicketValidator的那段代码只适用于未配置CAS Filter的情况?还是因为配置了CAS Filter后,CAS自动做了ticket的验证后丢掉了ticket?所以CasUserSSO中取到的ticket已经过期?这个问题我没想明白,大家如果有清楚的话可以交流一下。
我现在想到一个问题,既然配了Cas Filter后,那么CAS登录后在session里有用户信息,为什么不直接从session中取呢?见上面的CasUserSSO.java,注释掉// 开始setServiceTicket单点登录代码和// 结束setServiceTicket单点登录代码之间的代码,使用下面的代码获得用户名:
username = (String)request.getSessionContext().getAttribute("edu.yale.its.tp.cas.client.filter.user ");
这样岂不更直接!其中CAS登录成功后,配置了CAS Filter的Web应用的session都可以通过edu.yale.its.tp.cas.client.filter.user取得用户名,然后我们重新编译CasUserSSO.java,并启动tomcat,
这时再按下面的过程测试一下单点登录,首先新打开一个浏览器,输入http://localhost:8000/cms(此Web应用已配置了CAS),CAS登录窗口输入admin/123(cms应用的admin的口令和jforum的可以不同),登录成功后,再运行:
http://localhost:8000/jforum/forums/jforum.page?module=forums&action=list
不会再出来登录窗口了,此页面是系统管理员admin已登录的页面。
注意:如果CAS登录通过了一个jforum目前没有的帐户,则访问jforum时jforum会自动在数据库表(jforum_users)中插入一条新的记录,换句话说就是能自动注册,如果其他Web应用需要和jforum集成的话,只要配好了单点登录,就不需要担心其他应用的用户在jforum中注册的问题(不过自动注册只填写帐号名,其他字段还需要用户自己维护,或者编程实现从另外的应用数据库中将用户详细信息与jforum中的用户相关信息进行同步)。
(六)使用Cookie实现单点等录
由于CAS的配置比较复杂。在实际的企业应用中部署CAS比较麻烦,有的企业的应用服务器是WebSphere的,因此还要解决CAS如何在Websphere部署的问题,同时CAS的用户认证接口还要进行开发,CAS的性能能否满足注册用户信息量很大的电子商务网站?已有系统如何与CAS集成?等等很多问题需要我们考虑,如果我们只需要将Jforum论坛和电子商务平台集成起来,可以考虑采用Cookie进行单点登录的认证(我们项目只要求登录电子商务页面后进入论坛不用再登录而不是相反),用<span