【IT168 技术文档】
1、背景
Sametime提供了非常强大的集成能力,可以广泛地扩展在线感知能力。但是要实现企业应用的在线感知能力,首先需要当前用户登陆到Sametime服务器,显然如果通过提供用户名和密码方式登陆Sametime服务器,对客户来说是非常不安全的,Token代替密码登陆的方式,就是客户最好的选择。
2、了解Sametime
IBM Lotus Sametime是第一个既能够提供完整集成、实时服务,又能够满足企业和电子商务所要求的可伸缩性、可管理性和安全性的实时协作平台级产品。
IBM Lotus Sametime包含客户端和服务器应用程序,允许内网或互联网上某一群体的用户进行实时在线的会议协作。IBM Lotus Sametime用户群成员使用协作功能在即时或是预定的会议中开会、交谈或是一同工作,这些协作功能包括:在线感知、聊天、屏幕共享、共享白板以及实时音视频。 可以分为三大类:
即时通信服务:包括在线感知,即时通信和安全会话功能。使用人员列表,Sametime的用户就可以感知到现在谁在线并可以进行交流(或谁在线,但不希望被打扰),发送或接收即时消息,参与一人或多人的会话;
集成服务:Sametime还提供了全面的基于Java、C++的API,客户可以很轻松地把实时协作功能集成到其它应用程序中,如电子交易站点,帮助台,以及销售自动化等的培训信息传递应用。此外,Sametime秉承IBM Lotus一致的技术结构体系,可以实现完全的集成功能,如与IBM其他的产品(如:Domino,iNotes,K-station,WebSphere等)集成,提供其他应用解决方案的实时协作功能;
会议服务:包括共享白板,共享程序和在线文档的功能。Sametime还提供了一个基于服务器的会议中心,用户可以事先安排在线会议,存储会议议程和管理其它会议资料。另外用户还可以根据需要选择记录会议,以便将来的回放。
Lotus Sametime的“在线感知”技术使得已登录到Sametime服务器的成员能够看到所有其他在线用户(已登录用户)。在线用户的名称显示于Lotus Sametime应用程序的“在线列表”中。从这些在线列表中,群体成员可以通过即时消息会话交谈,或是启动即时会议以包含聊天、屏幕共享、白板、提问/回答调查、发送Web页面和音视频协作的能力。
在线列表支持与其他在线用户的即时感知和即时协作,Lotus Sametime服务器中的Lotus Sametime Meeting Center则为群体用户提供了集中的会议场所。在Meeting Center中,用户可以安排特定时间的会议。用户在计划的时间使用Web浏览器访问Lotus Sametime Meeting Center就可以参加会议了。
Lotus Sametime有两种基本的客户端应用程序:IBM Lotus Sametime Connect客户端和IBM Lotus Sametime Meeting Room客户端。Lotus Sametime Connect客户端是一个基于Eclipse的应用程序,包含了在线列表以显示已选取的群体成员中哪些在线。Lotus Sametime Connect 7.5对于第三方开发人员的一个核心价值就是:可以使用基于Eclipse的插件来扩展产品功能。

图 通过插件,让客户端更加丰富
Lotus Sametime Connect 7.5包括记录扩展开发所需API文档的SDK,您可以使用Eclipse的富客户机应用插件开发模型。使用Lotus Sametime Connect客户端,用户可以向群体中其他任何在线用户发送即时消息、启动即时会议以进行协作。
Lotus Sametime支持广播技术,可以允许会议中大量的只浏览用户(听众)来观看少部分用户(演讲人)的动作。广播技术尤其适用于单个人或一小组人向众多听众作报告。听众成员使用单独的只用于接受的Java客户机(即Lotus Sametime Broadcast客户机)来观看广播会议。
每个Lotus Sametime服务器都包含一个IBM Lotus Domino Directory,用于维护组成Lotus Sametime群的所有用户和服务器的信息。Lotus Sametime服务器也能配置为LDAP服务器(包含LDAP目录)的客户端。
Lotus Sametime通过客户应用与Sametime服务器之间的交互来工作。Lotus Sametime服务包括群服务、 会议服务、广播服务、Lotus Domino/Web 应用服务以及音视频服务(由Lotus Sametime多媒体服务提供)。对于Lotus Sametime服务器最主要的管理任务包括:管理目录,保证Lotus Sametime客户机与Sametime服务器的连接,配置Sametime服务,监控服务器。
Lotus Sametime管理员使用基于Web的Sametime管理客户工具,它运行在Web浏览器中,可以通过Lotus Sametime服务器主页上的“管理服务器”链接访问。
Lotus Sametime 7.5包含服务器群集的概念。Lotus Sametime服务器群集:
-增强了服务器的可扩展性和可靠性,以使得Lotus Sametime服务器能够适用于大用户量的需求;
-为Lotus Sametime群服务和会议服务提供了负载均衡和失效接管能力。
对Sametime有一点认识之后,当中我们最感兴趣的还是Sametime的在线感知功能,但要实现Sametime感知的话,要实现的最重要的一步,就是怎么实现WEB客户端的登录。
3.Sametime实现Web登录方式
越来越多的企业应用逐步实现了WEB化,那么这些应用集成Sametime的在线感知功能,需要使用Sametime的STLink的集成方式,这种方式采用如下方式实现登陆:
在WEB页面上执行javascript:
//用户登录
writeSTLinksApplet(username,pasword,false);
显然,我们的用户名和密码都暴露在客户端的HTML源代码里面,安全——客户能答应吗?No.
我们还有其他方法吗?答案是有的,那就是token:
//用户登录
writeSTLinksApplet(loginname,token,true);
那么,客户问你怎么得到Token? know or don't know.
有以下方式,我们可以获取Token。
4、通过用户登录后,得到该用户的Token。
使用Sametime API的StComm.jar包当中的相关方法我们得到了Token,这种方式仍然需要提供用户名和密码,只不过不会在客户的HTML源代码当中出现,而在服务器端执行,在客户端HTML代码当中的看到的仅仅是Token.
完整代码如下:
(需要StComm.jar包支持)
import com.lotus.sametime.community.*; import com.lotus.sametime.core.comparch.STSession; import com.lotus.sametime.token.*; /** *实现Token的监听期,服务器通过这种方式反馈Token */ public class Tokens implements LoginListener,TokenServiceListener{ private String _loginname; //记录用户登录名 例如:zhangshan private String _password;//记录用户的密码 private String _host;//Sametime的服务器地址 //登录用户的全称,例如 uid=zhangshan,cn=users,dc=your.com,dc=com private String _fullUsername; private String _token;//生成的Token private STSession m_session; //和Sametime服务器连接创建的session private CommunityService m_comm; //和Sametime CommunityService构建的连接 private TokenService m_token; //Sametime TokenService服务 private String errors; private boolean success; private boolean stop; public Tokens(String host) { _host=host; stop=false; success=false; } //用户登录 public void login(String loginname,String password) throws Exception { login(loginname,password,10); } //用户登录 public void login(String loginname,String password,int waitSeconds) throws Exception { _loginname=loginname; _password=password; m_session = new STSession(loginname); //创建一个与Sametime通讯的session m_session.start(); //通讯启用 m_comm=new STBase(m_session); m_token=new TokenComp(m_session); //增加TokensService监听器,监听Token反馈的信息 m_token.addTokenServiceListener(this); int i=0; //侦听用户的登录情况 m_comm.addLoginListener(this);//增加登录的监听器,监听是否登录成功 //用户登录 m_comm.loginByPassword(_host,_loginname,_password); //用户登录动作 try { while(!stop) { Thread.sleep(50); //waitSecond秒钟不能完成登录,自动退出. if(i++*50>=waitSeconds*1000){ stop=true; success=false; } } } catch(Exception e) { e.printStackTrace(); } m_comm.logout(); m_session.stop(); m_session.unloadSession();//释放资源 if(!success) { throw new Exception("生成Token异常!"); } } public void loggedIn(LoginEvent arg0) { try { //生成Token字符串 m_token.generateToken(); } catch(Exception e) { errors="创建token发生错误!"; e.printStackTrace(); } } /* * 用户登录注销 */ public void loggedOut(LoginEvent event) { if(event.getReason()==0) { success=true; } else { System.out.println("登录异常!"); } stop=true; } /** * 当Token生成的时候,调用该方法,得到Token **/ public void tokenGenerated(TokenEvent tokenEvent) { //获取Token的字符串及相关的用户名全称 _fullUsername=tokenEvent.getToken().getLoginName(); _token=tokenEvent.getToken().getTokenString(); success=true; //告诉程序我们获取了Token了,可以关闭循环了 stop=true; } /** * 当Token失败的时候,调用该方法,通知失败 **/ public void generateTokenFailed(TokenEvent tokenEvent) { errors="生成Token发生错误!"; stop=true; } public void serviceAvailable(TokenEvent tokenEvent) { } /** * Token生成之后,得到用户登录名全称 **/ public String getLoginName() { return _fullUsername; } /** * Token生成之后,得到用户的Token **/ public String getToken() { return _token; } }
通过调用getLoginName()得到登录的完全用户名,以及getToken()得到Token值。
5、通过服务器登录后,查询用户的Token。
这是我们最希望看到的方案。通过构建一个Servlet或者Web Service,只要提供用户名,就可以通过查询获取Token,这种方式也让你担心其安全性,但让我们放心的是并不是所有运行该代码的客户端,都允许得到Token,该客户端需要得到服务器端的的信任。
配置如下:
打开Sametime 服务器的stconfig.nsf

配置CommunityConnectivity的Community Trusted IPS,增加Sametime信任的服务器IP

完整代码如下:
(需要StComm.jar包支持)
import javax.servlet.*; import javax.servlet.http.*; import java.io.*; import com.lotus.sametime.core.comparch.*; import com.lotus.sametime.core.types.*; import com.lotus.sametime.community.*; import com.lotus.sametime.core.util.connection.*; import com.lotus.sametime.token.SATokenService; import com.lotus.sametime.token.Token; import com.lotus.sametime.token.TokenEvent; import com.lotus.sametime.token.TokenServiceListener; /* * * 实现一个Servlet,通过Servelt来查询用户的Token,也可以实现Web Service来获取登录用户的Token。 */ public class SametimeServlet extends HttpServlet implements LoginListener, TokenServiceListener { /* * * * */ public void serviceAvailable(TokenEvent arg0) { } // Sametime 的Session private STSession m_session; // 作为应用来登录服务器,而不是使用用户名和密码来登录服务器 private ServerAppService m_saService; //设置sametime服务器 private String host="testsametime.csvw.com"; // 使用这个服务生成Token private SATokenService m_tokenService; //查询的用户名 private STUser m_user; //查询生成的Token private Token m_token; private String m_userName; private boolean success;//表明是否成功登录服务器 public void init(ServletConfig config) throws ServletException { super.init(config); // Init Sametime log("initialize sametime"); initSametime(); // Wait until initialization complete try { synchronized (this) { wait(); } } catch (InterruptedException e) { } log(">> Sametime servlet was initialized successfully"); } /** * 初始化Sametime服务器信息 */ void initSametime() { success=false; // Create the session, load components and start it try { m_session = new STSession("" + this); loadComponents(); m_session.start(); } catch (DuplicateObjectException e) { e.printStackTrace(); } m_saService = (ServerAppService) m_session .getCompApi(ServerAppService.COMP_NAME); m_tokenService = (SATokenService) m_session .getCompApi(SATokenService.COMP_NAME); m_tokenService.addTokenServiceListener(this); // 作为应用登录服务器r loginToServer(host); } private void loadComponents() { String[] compNames = { "com.lotus.sametime.community.STBase", "com.lotus.sametime.token.SATokenComp" }; m_session.loadComponents(compNames); } void loginToServer(String serverName) { m_saService.addLoginListener(this); //设置登录Sametime类似是应用的方式 short loginType = STUserInstance.LT_SERVER_APP; //创建一个与Sametime服务器的连接 Connection[] connections = { new SocketConnection(8082, 17000)}; m_saService.setConnectivity(connections); log(">> login to sametime server name is " + serverName); //以应用的方式登录Sametime服务器,而且不注销 m_saService.loginAsServerApp(serverName, loginType, "SametimeServlet", null); } public void loggedIn(LoginEvent event) { log(">> loggedIn to Sametime"); success=true; synchronized (this) { log(">> wake up call"); notify(); } } public void loggedOut(LoginEvent event) { log("***** loggedOut from Sametime reason = " + event.getReason()); synchronized (this) { log(">> wake up call"); notify(); } } public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { doPost(req, res); } public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { m_userName = req.getParameter("username"); log(">> do post user name is " + m_userName); //判断服务器是否登录成功,登陆成功才可以执行查询操作 if(success) { //对UID需要根据DN组装 m_user = new STUser(new STId(m_userName, m_userName), "", ""); //请求生成Token m_tokenService.generateToken(m_user); synchronized (this) { try { log(">> waiting..."); //等待服务器返回Token ,10秒超时 wait(10000); log(">> waked up..."); } catch (InterruptedException e) { } } } log(">> creating response..."); //返回得到的登录名和Token createResponse(res); } public void tokenGenerated(TokenEvent event) { log(">> token generated"); //成功获取Token m_token = event.getToken(); synchronized (this) { log(">> wake up call"); //通知完成Token生成 notify(); } } public void generateTokenFailed(TokenEvent event) { log("***** Storage request failed = " + event.getReason()); synchronized (this) { log(">> wake up call"); notify(); } } private void createResponse(HttpServletResponse res) throws IOException { PrintWriter pw = res.getWriter(); if(success) { //在 pw.print(m_token.getLoginName()+"|"); pw.print(m_token.getTokenString()); } } /* * 释放资源 */ public void destroy() { m_session.stop(); m_session.unloadSession(); super.destroy(); } }
这个Servlet会通过我们访问如下URL:
http://localhost:8083/servlet/ SametimeServlet?username=uid=zhangshan,cn=users,dc=your.com,dc=com
返回:
uid=zhangshan,cn=users,dc=your.com,dc=com|(DDFDFdFDDGGDGDGDG)
注:登录名和Token,并且使用”|”分开
总之,两种方式都可以获取Token,但是从性能方面考虑,采用第二种方式,对认证服务的压力小些,如果把这个Servlet配置在Sametime本身的Domino执行的话,更是优秀的方案。有了这个Token,我们可以实现无论,net、PHP、ASP等WEB页面的Sametime的集成,真正实现企业所有WEB应用的在线感知。