JForum論壇單點(diǎn)登錄的幾種實(shí)現(xiàn)方式 (CAS和Cookie) 0評(píng)/451閱 發(fā)表于: 2011-04-23 13:15 作者: abao
(一)CAS客戶端應(yīng)用的web.xml配置 CAS和jforum的安裝過程本文就不介紹了,下面是jforum配置CAS服務(wù)器連接需要在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>
一開始我調(diào)試jforum的單點(diǎn)登錄的時(shí)候,首先在地址欄輸入http://localhost:8000/cas/login?service=http://localhost:8000/jforum/user.jsp (其中user.jsp是我自己添加JSP頁面做測(cè)試用 ),在CAS頁面輸入用戶名和口令確認(rèn)后,頁面自動(dòng)跳轉(zhuǎn)到http://localhost:8000/jforum/user.jsp?ticket= ticket=ST-5-Ih4fJNYyWlhFfywfeOwuVAFZn1vKOOVAgpD-20 Ticket是生成的票據(jù),然后用這個(gè)ticket做參數(shù)訪問: https://localhost:8443/cas/serviceValidate?service= http://localhost:8000/jforum/user.jsp&ticket= ST-5-Ih4fJNYyWlhFfywfeOwuVAFZn1vKOOVAgpD-20 如果成功,返回的頁面中出現(xiàn)登錄成功的用戶名,打開html源文件,內(nèi)容為: <cas:serviceResponse xmlns:cas='http://www./tp/cas'> <cas:authenticationSuccess> <cas:user>admin</cas:user> </cas:authenticationSuccess> </cas:serviceResponse> 如果失敗,頁面顯示ticket 'ST-2-4ffpnvHKv1NH5So7uWvFdVNrbHsaPAfROXx-20' not recognized,html源文件內(nèi)容: <cas:serviceResponse xmlns:cas='http://www./tp/cas'> <cas:authenticationFailure code='INVALID_TICKET'> ticket 'ST-2-4ffpnvHKv1NH5So7uWvFdVNrbHsaPAfROXx-20' not recognized </cas:authenticationFailure> </cas:serviceResponse> (二)Jforum配置單點(diǎn)登錄 Jforum的WEB-INF\config目錄下有一個(gè)SystemGlobals.properties文件,配置SSO需要更改此文件的幾個(gè)配置參數(shù): authentication.type = sso #CasUserSSO類用于CAS單點(diǎn)登錄,下面將講述此類的代碼 sso.implementation = com.iss.common.sso.CasUserSSO #CasCookieSSO是基于Cookie的一個(gè)簡(jiǎn)單的單點(diǎn)登錄,代碼見下文 #sso.implementation = com.iss.common.sso.CasCookieSSO #下面的redirect我也不太清楚具體有什么用 sso.redirect = https://localhost:8443/cas/ (三)當(dāng)把jforum的web.xml的CAS filter注釋掉以后,使用下面的JSP通過CAS單點(diǎn)登錄票據(jù)驗(yàn)證的一個(gè)示例,其中URL應(yīng)帶service參數(shù),如: http://localhost:8000/cas/login?service=http://localhost:8000/jforum/testsso.jsp 登錄CAS成功后,返回的頁面url帶有一個(gè)ticket參數(shù),見下面的返回URL: http://localhost:8000/jforum/testsso.jsp?ticket=ST-5-VbM7tdMPeLD1WlH2ZGnocVGTbAY73ff4y17-20 Tomcat控制臺(tái)顯示下面的輸出說明票據(jù)認(rèn)證通過: <cas:serviceResponse xmlns:cas='http://www./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("認(rèn)證成功,獲得的用戶名為:"); System.out.println(username); } else { errorCode = sv.getErrorCode(); errorMessage = sv.getErrorMessage(); System.out.println("認(rèn)證失敗?。。。。。。。。。。?); } } catch (Exception exc) { System.out.println(exc.getMessage()); } } %>
edu.yale.its.tp.cas.client.ServiceTicketValidator是casclient.jar中的類。
(四)當(dāng)Jforum的web.xml中不配置CAS Filter時(shí),如何實(shí)現(xiàn)單點(diǎn)登錄類? 這種情況類似于(三),但問題是如何在Java類中實(shí)現(xiàn)單點(diǎn)登錄,而不是在jsp中實(shí)現(xiàn)。 我從網(wǎng)上找到一個(gè)名為CasUserSSO.java的程序,并按步驟(二)的說明將此類作為單點(diǎn)登錄類配置,但編譯運(yùn)行后,在瀏覽器地址欄輸入http://localhost:8000/cas/login?service=http://localhost:8000/jforum/user.jsp (user.jsp是我自己寫的一個(gè)顯示簡(jiǎn)單輸出的jsp文件, http://localhost:8000/jforum/user.jsp是我在CasUserSSO.java里面作為service參數(shù)的),CAS登錄成功后Tomcat控制臺(tái)沒有顯示單點(diǎn)登錄的認(rèn)證信息,好象CasUserSSO沒有調(diào)用到,于是我修改了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")); 重新編譯運(yùn)行后,地址欄中輸入http://localhost:8000/cas/login?service= http%3A%2F%2Flocalhost%3A8000%2Fjforum%2Fforums%2Fjforum.page%3Fmodule%3Dforums%26action%3Dlist 注意service等號(hào)右面的必須是java.net.URLEncoder.encode轉(zhuǎn)換后的字符串(http://localhost:8000/jforum/forums/jforum.page?module=forums&action=list轉(zhuǎn)換后的字符串為http%3A%2F%2Flocalhost%3A8000%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配的超級(jí)管理員賬號(hào)是admin/123),出現(xiàn)jforum頁面,這說明CAS單點(diǎn)登錄成功?。?! Tomcat控制臺(tái)顯示:
<cas:serviceResponse xmlns:cas='http://www./tp/cas'> <cas:authenticationSuccess> <cas:user>admin</cas:user> </cas:authenticationSuccess> </cas:serviceResponse>
請(qǐng)大家一定要注意,在地址欄中service=后面的URL必須是encode轉(zhuǎn)換后的,因?yàn)殚_始沒轉(zhuǎn)換,我還以為是CasUserSSO寫的有問題,網(wǎng)上下載的CasUserSSO中的 sv.setCasValidateUrl("https://localhost:8443/cas/login");我認(rèn)為是有問題的,會(huì)出現(xiàn)一個(gè)很奇怪的異常,顯示信息為org.xml.sax.SAXParseException: The reference to entity "ticket" must end with the ';' delimiter.我改為: sv.setCasValidateUrl("https://localhost:8443/cas/serviceValidate")則不會(huì)出現(xiàn)這個(gè)問題。見下面的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單點(diǎn)登錄代碼
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("開始驗(yàn)證............"); sv.validate(); xmlResponse = sv.getResponse(); //System.out.println(xmlResponse); if (sv.isAuthenticationSuccesful()) { username = sv.getUser(); logger.info("認(rèn)證成功,獲得的用戶名為:"); logger.info(username); } else { errorCode = sv.getErrorCode(); errorMessage = sv.getErrorMessage(); logger.info("認(rèn)證失?。。。。。。。。。。?!"); } } catch (Exception exc) { System.out.println(exc.getMessage()); } } // 結(jié)束setServiceTicket單點(diǎn)登錄代碼
/* System.out.println("開始獲取用戶名。。。"); username = (String)request.getSessionContext().getAttribute("edu.yale.its.tp.cas.client.filter.user"); System.out.println(username); System.out.println("結(jié)束獲取用戶名。。。");
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的已登錄頁面,如果這時(shí)在當(dāng)前瀏覽器輸入配置了CAS的其他web應(yīng)用,其他web應(yīng)用不會(huì)彈出cas登錄頁面,因?yàn)閏as已經(jīng)登錄過了。我做了一個(gè)測(cè)試,首先 打開一個(gè)新的瀏覽器窗口,輸入一個(gè)配置了CAS的web應(yīng)用地址,如http://localhost:8000/cms ,彈出cas登錄窗口,輸入admin/123,登錄成功,這時(shí)候再輸入http://localhost:8000/cas/login?service=http%3A%2F%2Flocalhost%3A8000%2Fjforum%2Fforums%2Fjforum.page%3Fmodule%3Dforums%26action%3Dlist,仍然出來一個(gè)CAS登錄窗口,這好象不是我們所希望的,那么只好再把jforum的web.xml中的CAS過濾器配置好。 (五)當(dāng)Jforum的web.xml中配置了CAS Filter時(shí),如何實(shí)現(xiàn)單點(diǎn)登錄類? 現(xiàn)在我們按(一)的說明配置好CAS Filter,重啟tomcat,再次輸入http://localhost:8000/cas/login?service=http%3A%2F%2Flocalhost%3A8000%2Fjforum%2Fforums%2Fjforum.page%3Fmodule%3Dforums%26action%3Dlist,反而后臺(tái)報(bào)出認(rèn)證失敗的錯(cuò)誤,Tomcat的控制臺(tái)輸出:
<cas:serviceResponse xmlns:cas='http://www./tp/cas'> <cas:authenticationFailure code='INVALID_TICKET'> ticket 'ST-8-DWSSawgxWPwjjJwkScbIOqDcqR6eTUdZhwi-20' not recogni zed </cas:authenticationFailure> </cas:serviceResponse>
難道上面的CasUserSSO.java的ServiceTicketValidator的那段代碼只適用于未配置CAS Filter的情況?還是因?yàn)榕渲昧薈AS Filter后,CAS自動(dòng)做了ticket的驗(yàn)證后丟掉了ticket?所以CasUserSSO中取到的ticket已經(jīng)過期?這個(gè)問題我沒想明白,大家如果有清楚的話可以交流一下。 我現(xiàn)在想到一個(gè)問題,既然配了Cas Filter后,那么CAS登錄后在session里有用戶信息,為什么不直接從session中取呢?見上面的CasUserSSO.java,注釋掉// 開始setServiceTicket單點(diǎn)登錄代碼和// 結(jié)束setServiceTicket單點(diǎn)登錄代碼之間的代碼,使用下面的代碼獲得用戶名:
username = (String)request.getSessionContext().getAttribute("edu.yale.its.tp.cas.client.filter.user ");
這樣豈不更直接!其中CAS登錄成功后,配置了CAS Filter的Web應(yīng)用的session都可以通過edu.yale.its.tp.cas.client.filter.user取得用戶名,然后我們重新編譯CasUserSSO.java,并啟動(dòng)tomcat, 這時(shí)再按下面的過程測(cè)試一下單點(diǎn)登錄,首先新打開一個(gè)瀏覽器,輸入http://localhost:8000/cms(此Web應(yīng)用已配置了CAS),CAS登錄窗口輸入admin/123(cms應(yīng)用的admin的口令和jforum的可以不同),登錄成功后,再運(yùn)行: http://localhost:8000/jforum/forums/jforum.page?module=forums&action=list 不會(huì)再出來登錄窗口了,此頁面是系統(tǒng)管理員admin已登錄的頁面。
注意:如果CAS登錄通過了一個(gè)jforum目前沒有的帳戶,則訪問jforum時(shí)jforum會(huì)自動(dòng)在數(shù)據(jù)庫表(jforum_users)中插入一條新的記錄,換句話說就是能自動(dòng)注冊(cè),如果其他Web應(yīng)用需要和jforum集成的話,只要配好了單點(diǎn)登錄,就不需要擔(dān)心其他應(yīng)用的用戶在jforum中注冊(cè)的問題(不過自動(dòng)注冊(cè)只填寫賬號(hào)名,其他字段還需要用戶自己維護(hù),或者編程實(shí)現(xiàn)從另外的應(yīng)用數(shù)據(jù)庫中將用戶詳細(xì)信息與jforum中的用戶相關(guān)信息進(jìn)行同步)。 (六)使用Cookie實(shí)現(xiàn)單點(diǎn)等錄
由于CAS的配置比較復(fù)雜。在實(shí)際的企業(yè)應(yīng)用中部署CAS比較麻煩,有的企業(yè)的應(yīng)用服務(wù)器是WebSphere的,因此還要解決CAS如何在Websphere部署的問題,同時(shí)CAS的用戶認(rèn)證接口還要進(jìn)行開發(fā),CAS的性能能否滿足注冊(cè)用戶信息量很大的電子商務(wù)網(wǎng)站?已有系統(tǒng)如何與CAS集成?等等很多問題需要我們考慮,如果我們只需要將Jforum論壇和電子商務(wù)平臺(tái)集成起來,可以考慮采用Cookie進(jìn)行單點(diǎn)登錄的認(rèn)證(我們項(xiàng)目只要求登錄電子商務(wù)頁面后進(jìn)入論壇不用再登錄而不是相反),用Cookie做單點(diǎn)登錄的實(shí)現(xiàn)方式: (1)電子商務(wù)平臺(tái)登錄成功后(不是基于CAS的,把用戶名寫到名為bbsUser的Cookie中) (2)為保證安全性,Cookie只在電子商務(wù)應(yīng)用服務(wù)器中有效(和Jforum在同一應(yīng)用服務(wù)器運(yùn)行,cookie.setPAth(“/”)),并且Cookie的MaxAge設(shè)置為-1,就是瀏覽器關(guān)閉后Cookie自動(dòng)失效,另外session失效時(shí)也刪除名為bbsUser的Cookie)
所以控制層和JSP中可參考下面代碼創(chuàng)建Cookie: Cookie[] cookie1 = request.getCookies(); if(cookie1==null) { Cookie cookie = new Cookie("bbsUser", acegiUserName); cookie.setMaxAge(-1); //負(fù)值表示瀏覽器關(guān)閉時(shí)cookie被刪除,零值則是要?jiǎng)h除該Cookie。 cookie.setPath("/"); response.addCookie(cookie); } else { for(int i=0; i< cookie1.length; i++) { String name = cookie1[i].getName(); String value = cookie1[i].getValue(); if(!name.equals("bbsUser")) //已存在cookie則不需要增加cookie,否則增加cookie { Cookie cookie = new Cookie("bbsUser", acegiUserName); cookie.setMaxAge(-1); //負(fù)值表示瀏覽器關(guān)閉時(shí)cookie被刪除,零值則是要?jiǎng)h除該Cookie。 cookie.setPath("/"); //cookie只在同一應(yīng)用服務(wù)器有效 response.addCookie(cookie); } } }
瀏覽器關(guān)閉,上面的cookie失效,如果session失效,則失效跳轉(zhuǎn)或login.jsp要清除cookie,代碼: //清除bbsUser的cookie Cookie cookie = new Cookie("bbsUser", ""); cookie.setMaxAge(0); //負(fù)值表示瀏覽器關(guān)閉時(shí)cookie被刪除,零值則是要?jiǎng)h除該Cookie。 cookie.setPath("/"); response.addCookie(cookie);
下面是在JForum中使用的CasCookieSSO.java,此代碼從Cookie中讀取名為bbsUser的Cookie作為用戶名: 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 javax.servlet.http.Cookie;
public class CasCookieSSO 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;
Cookie[] cookie = request.getCookies(); if(cookie == null) { //username = "guest"; } else { for(int i=0; i< cookie.length; i++) { String name = cookie[i].getName(); String value = cookie[i].getValue(); if(name.equals("bbsUser")) { username = value; break; //退出循環(huán) } } }
logger.info("登錄用戶為:"+username); return username; }
public boolean isSessionValid(UserSession userSession,RequestContext request) { return false; //說明:為什么在這里返回false,因?yàn)槿绻祷豻rue的話,當(dāng)cookie中的用戶id變化后,再次訪問jforum時(shí), //jforum仍記憶上次使用的用戶ID,當(dāng)其他應(yīng)用改變登錄用戶后,不能根據(jù)cookie中的bbsUser變量切換為新的用戶,將返回值設(shè)置為false后問題解決。所以這里推薦直接返回false。 } } 使用CasCookieSSO時(shí),注意更改Jforum的配置文件,sso.implementation = com.iss.common.sso. CasCookieSSO。 |
|