JForum做為一個成熟的開源BBS論壇解決方案,提供了非常方便的SSO集成接口。它的主頁上和網上都有許多介紹如何用SSO方式進行集成的辦法。這里不羅列,google可以找到許多資料,主要描述一下如何解決用戶名重名的一種方式。目前使用的JForum版本是2.1.8
簡單地介紹一下采用的SSO方式。由于應用上需要一個BBS,找了JForum做為一個子系統,集成到現成的一個管理系統當中,管理系統本身有一套完全的身份權限認證方案,由于系統的安全要求不是特別嚴格,所以采了最直接和最省事的方式:Cookie寫入。即在管理系統登錄時,把用戶信息寫入Cookie,JForum從Cookie中讀取用戶信息進行登錄。
為JForum項目添加一個SSO接口的擴展類CookieSSO,主要實現authenticateUser(RequestContext request)方法。方法大體如下:
public String authenticateUser(RequestContext request) {
String userId = null;
Cookie c = ControllerUtils.getCookie("ehrbbsuserid");
if(c!=null){
userId = c.getValue();
}
logger.info("單點登錄BBS用戶ID為:" + userId);
return userId;
}
就是從Cookie中讀取在管理系統中放入的username。但是這里寫的是:ehrbbsuserid,主要是這個變量名來區分傳過來的內容的不同。
按照正常的方式就是從管理系統把一個用戶的username傳過來。然后在這里取出,return給調用方法進行驗證。在實際項目中,問題就出在這里了!
舉例說有二個用戶名字都叫:李四,那么當二個李四都同時登錄時,JForum的驗證方式就會出問題!它認不清到底是哪個李四,數據庫查詢時只按第一個來!
仔細看一下jforum的數據庫表設計,jforum_user這個表沒有display_user_name類似的字段,它就是把username做為顯示在頁面上的用戶名,如果不做集成,把它單獨做為一個BBS時,username既是登錄的用戶名,也是顯示在頁面上的用戶名。主要原因我想大概就是老外的思維方式跟中國人的不太一樣。中國人登錄的時候用英文,顯示的時候還有一個昵稱或是中文名等。
所以把它集成的時候,用Cookie傳送一個username給JForum時就得用中文(而且重名的概率很大)。我們再跟進一個它的代碼,看是誰調用了SSO接口的這個方法:
結果發現是類:net.jforum.ControllerUtils的checkSSO(UserSession userSession)方法
/**
* Checks for user authentication using some SSO implementation
* @param userSession UserSession
*/
protected void checkSSO(UserSession userSession)
{
try {
SSO sso = (SSO) Class.forName(SystemGlobals.getValue(ConfigKeys.SSO_IMPLEMENTATION)).newInstance();
String username = sso.authenticateUser(JForumExecutionContext.getRequest());

if (username == null || username.trim().equals("")) {
userSession.makeAnonymous();
}
else {
SSOUtils utils = new SSOUtils();

if (!utils.userExists(username)) {
SessionContext session = JForumExecutionContext.getRequest().getSessionContext();

String email = (String) session.getAttribute(SystemGlobals.getValue(ConfigKeys.SSO_EMAIL_ATTRIBUTE));
String password = (String) session.getAttribute(SystemGlobals.getValue(ConfigKeys.SSO_PASSWORD_ATTRIBUTE));

if (email == null) {
email = SystemGlobals.getValue(ConfigKeys.SSO_DEFAULT_EMAIL);
}

if (password == null) {
password = SystemGlobals.getValue(ConfigKeys.SSO_DEFAULT_PASSWORD);
}

utils.register(password, email);
}

this.configureUserSession(userSession, utils.getUser());
}
}
catch (Exception e) {
e.printStackTrace();
throw new ForumException("Error while executing SSO actions: " + e);
}
}
在這里明顯的,它是通過username去確定這個人是否存在?再繼續跟進代碼就會發現最后調用了Dao的selectUserByName方法。
解決的思路:修改JForum的數據庫表,并更改代碼和頁面文件是不科學的,工作量大,而且風險比較高!那么就繼續把username用來保存中文名稱,它的user_id是一個自增的數字序列。在管理系統的用戶表中,擴展一個字段bbs_user_id,用來保存在JForum中的用戶id,這個字段就肯定是唯一的,在管理系統登錄時,把這個bbs_user_id查詢出來,放到Cookie中。在JForum驗證時,不再使用它推薦的返回username方式,而是返回它的user_id值。
那么回到最上面的CookieSSO類的代碼,這里返回的String其實是jforum_user表中user_id字段。為了匹配,那么net.jforum.ControllerUtils的checkSSO(UserSession userSession)方法也要改!改為下面的方式:
protected void checkSSO(UserSession userSession)
{
try {
SSO sso = (SSO) Class.forName(SystemGlobals.getValue(ConfigKeys.SSO_IMPLEMENTATION)).newInstance();
String username = sso.authenticateUser(JForumExecutionContext.getRequest());

if (username == null || username.trim().equals("")) {
userSession.makeAnonymous();
}
else {
// SSOUtils utils = new SSOUtils();
/* 重構為按userId驗證身份 */
// if (!utils.userExists(username)) {
// SessionContext session = JForumExecutionContext.getRequest().getSessionContext();
//
// String email = (String) session.getAttribute(SystemGlobals.getValue(ConfigKeys.SSO_EMAIL_ATTRIBUTE));
// String password = (String) session.getAttribute(SystemGlobals.getValue(ConfigKeys.SSO_PASSWORD_ATTRIBUTE));
//
// if (email == null) {
// email = SystemGlobals.getValue(ConfigKeys.SSO_DEFAULT_EMAIL);
// }
//
// if (password == null) {
// password = SystemGlobals.getValue(ConfigKeys.SSO_DEFAULT_PASSWORD);
// }
//
// utils.register(password, email);
// }
/* 新添加的代碼 */
UserDAO dao = DataAccessDriver.getInstance().newUserDAO();
User user = dao.selectById(Integer.parseInt(username));
// this.configureUserSession(userSession, utils.getUser());
this.configureUserSession(userSession, user);
}
}
catch (Exception e) {
e.printStackTrace();
throw new ForumException("Error while executing SSO actions: " + e);
}
}
這樣就可以解決認證的問題!同時又保證username可以是中文的,而且重名也無所謂。
附加:查看它的SQL配置文件,發現有selectUserByName這樣的方法,通過用戶名來查找用戶,起初怕是它在某些模塊中使用了。后來詳細查看,發現它只使用在后臺管理(即admin模塊)中的用戶管理。這個頁面提供了一個按用戶名來查找用戶的功能,所以也是非常合理的!
剛進場的時候戲就落幕
簡單地介紹一下采用的SSO方式。由于應用上需要一個BBS,找了JForum做為一個子系統,集成到現成的一個管理系統當中,管理系統本身有一套完全的身份權限認證方案,由于系統的安全要求不是特別嚴格,所以采了最直接和最省事的方式:Cookie寫入。即在管理系統登錄時,把用戶信息寫入Cookie,JForum從Cookie中讀取用戶信息進行登錄。
為JForum項目添加一個SSO接口的擴展類CookieSSO,主要實現authenticateUser(RequestContext request)方法。方法大體如下:









按照正常的方式就是從管理系統把一個用戶的username傳過來。然后在這里取出,return給調用方法進行驗證。在實際項目中,問題就出在這里了!
舉例說有二個用戶名字都叫:李四,那么當二個李四都同時登錄時,JForum的驗證方式就會出問題!它認不清到底是哪個李四,數據庫查詢時只按第一個來!
仔細看一下jforum的數據庫表設計,jforum_user這個表沒有display_user_name類似的字段,它就是把username做為顯示在頁面上的用戶名,如果不做集成,把它單獨做為一個BBS時,username既是登錄的用戶名,也是顯示在頁面上的用戶名。主要原因我想大概就是老外的思維方式跟中國人的不太一樣。中國人登錄的時候用英文,顯示的時候還有一個昵稱或是中文名等。
所以把它集成的時候,用Cookie傳送一個username給JForum時就得用中文(而且重名的概率很大)。我們再跟進一個它的代碼,看是誰調用了SSO接口的這個方法:
結果發現是類:net.jforum.ControllerUtils的checkSSO(UserSession userSession)方法









































解決的思路:修改JForum的數據庫表,并更改代碼和頁面文件是不科學的,工作量大,而且風險比較高!那么就繼續把username用來保存中文名稱,它的user_id是一個自增的數字序列。在管理系統的用戶表中,擴展一個字段bbs_user_id,用來保存在JForum中的用戶id,這個字段就肯定是唯一的,在管理系統登錄時,把這個bbs_user_id查詢出來,放到Cookie中。在JForum驗證時,不再使用它推薦的返回username方式,而是返回它的user_id值。
那么回到最上面的CookieSSO類的代碼,這里返回的String其實是jforum_user表中user_id字段。為了匹配,那么net.jforum.ControllerUtils的checkSSO(UserSession userSession)方法也要改!改為下面的方式:









































這樣就可以解決認證的問題!同時又保證username可以是中文的,而且重名也無所謂。
附加:查看它的SQL配置文件,發現有selectUserByName這樣的方法,通過用戶名來查找用戶,起初怕是它在某些模塊中使用了。后來詳細查看,發現它只使用在后臺管理(即admin模塊)中的用戶管理。這個頁面提供了一個按用戶名來查找用戶的功能,所以也是非常合理的!
剛進場的時候戲就落幕