JForum做为一个成熟的开源BBS论坛解决方案,提供了非常方便的SSO集成接口。它的主页上和网上都有许多介绍如何用SSO方式进行集成的办法。这里不罗列,google可以找到许多资料,主要描述一下如何解决用户名重名的一种方式。目前使用的JForum版本是2.1.8
简单地介绍一下采用的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模块)中的用户管理。这个页面提供了一个按用户名来查找用户的功能,所以也是非常合理的!
刚进场的时候戏就落幕
简单地介绍一下采用的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,主要是这个变量名来区分传过来的内容的不同。
String userId = null;
Cookie c = ControllerUtils.getCookie("ehrbbsuserid");
if(c!=null){
userId = c.getValue();
}
logger.info("单点登录BBS用户ID为:" + userId);
return userId;
}
按照正常的方式就是从管理系统把一个用户的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方法。
* 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);
}
}
解决的思路:修改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);
}
}
{
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模块)中的用户管理。这个页面提供了一个按用户名来查找用户的功能,所以也是非常合理的!
刚进场的时候戏就落幕