XMPP(Extensible Messaging and Presence Protocol,前称Jabber)是一种以XML为基础的开放式实时通信协议,是经由互联网工程工作小组(IETF)通过的互联网标准。XMPP因为被GoogleTalk应用而被广大网民所接触。开放式协议的好处就是,不区分客户端。就是说无论您使用gtalk、Facebook Chat还是网易泡泡,都可以实现相互间通话。也就是说,我们自己开发的客户端,也可以实现跟gtalk之间的聊天。
Openfire 采用Java开发,开源的实时协作(RTC)服务器基于XMPP(Jabber)协议。Openfire安装和使用都非常简单,并利用Web进行管理。单台服务器可支持上万并发用户。
下面进入正题。
一、准备环境
Jdk1.6+
Openfire3.8.2:http://www.igniterealtime.org/这里要注意下,如果使用不同版本的openfire可能会和现有的源码出现兼容性问题。
Spark2.6.3:http://www.igniterealtime.org/
Nginx1.4.0
Mysql(这里使用Oracle也行)
二、部署openfire到Eclipse
参考:http://blog.csdn.net/ares1201/article/details/7737872
这篇文章写的很好很详细,这里就不重复说明了。
在部署完成后,登陆控制台,进入 服务器->服务器设置->HTTP绑定,把http绑定、ScriptSyntax都启用起来,这样才能以http的方式通过7070端口访问到服务器。
其中需要注意:
1、配置启动参数时,选中Arguments选项卡,在VM arguments中填入
-DopenfireHome="${workspace_loc:openfire_src}/target/openfire",前面标红的-不要落了。
2、在控制台上输出openfire日志:
修改:/openfire_src/build/lib/dist/log4j.xml如下:
<!-- 添加一个打印控制台的日志 -->
<appender name="STDOUT"class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<paramname="ConversionPattern" value="%d [%24F:%-4L:%-5p][%x]-%m%n"/>
</layout>
</appender>
修改root
<root>
<level value="debug"/>
<appender-ref ref="STDOUT" />
<appender-ref ref="debug-out" />
<appender-ref ref="info-out" />
<appender-ref ref="warn-out" />
<appender-ref ref="error-out" />
</root>
工程中添加个目录,如下图
这样,在Eclipse控制台上就有openfire的日志输出了。
3、我在配置openfire的时候,全部使用的是域名的形式,我尝试了下使用ip,发现经常会有莫名其妙的问题,所以建议全部统一(不包括nginx部分)。
三、使用nginx部署strophe.js客户端
1、下载后的strophe.js解压,随访放个目录;
2、解压nginx,随访放个目录,修改conf/nginx.conf文件:
修改默认location的地址为strophe的目录,如下:
location/ {
root D:/workspaces/php;
index index.html index.htm;
}
增加一个location为代理项;
location /http-bind {
proxy_pass http://192.168.136.176:7070/http-bind/;
proxy_redirect off;
proxy_read_timeout 120;
proxy_connect_timeout 120;
}
proxy_pass的意思是代理“http://192.168.136.176:7070/http-bind/”这个地址。效果就是访问nginx(80端口)下的http-bind应用时会直接跳转到7070端口下。这样可以保证strophe和openfire在一个域下,避免跨域访问。下面画个图解释下。
如图,目的就是让用户无论访问unieap(strophe)服务器还是访问openfire的时候都在同一个域下。
四、使用spark和浏览器实现对话
1、spark下载安装后,就可以直接连接到openfire服务器。
2、strophe解压后,里面有个example文件夹,里面有对应的例子,basic.html啊之类的。我这里下了个别人开发的聊天小程序,是用echobot.html那个例子改的。如果使用的是自带的例子,那么修改如下:
1)修改html中引用的jquery的地址为本地地址;
2)修改对应js里面的BOSH_SERVICE为'/http-bind/'。这里可以不加域,在写联系人的时候直接写上,如zhangsan@xxx,也可以在这里把域写死。
效果如下:
五、集成unieap用户
1、我使用的openfire外置数据库是mysql的,unieap工程主要修改修改的就是ofproperty这张表。这张表是具体的参数,可以通过console修改,我这里直接提供这个表的插入脚本了。
insert into `ofproperty` (`name`,`propValue`) values('admin.authorizedJIDs','admin@xueyi');
insert into `ofproperty` (`name`,`propValue`) values('httpbind.CORS.domains','*');
insert into `ofproperty` (`name`,`propValue`) values('httpbind.CORS.enabled','true');
insert into `ofproperty` (`name`,`propValue`) values('httpbind.enabled','true');
insert into `ofproperty` (`name`,`propValue`) values('httpbind.forwarded.enabled','false');
insert into `ofproperty` (`name`,`propValue`) values('jdbcAuthProvider.passwordSQL','select user_password fromup_org_user WHERE USER_ACCOUNT=?');
insert into `ofproperty` (`name`,`propValue`) values('jdbcAuthProvider.passwordType','md5');
insert into `ofproperty`(`name`, `propValue`)values('jdbcProvider.connectionString','jdbc:oracle:thin:@[host]:[port]:[entry]');
insert into `ofproperty` (`name`,`propValue`) values('jdbcProvider.driver','oracle.jdbc.driver.OracleDriver');
insert into `ofproperty`(`name`, `propValue`) values('jdbcProvider.password','[username]');
insert into `ofproperty`(`name`, `propValue`) values('jdbcProvider.user','[password]');
insert into `ofproperty` (`name`,`propValue`) values('jdbcUserProvider.allUsersSQL','SELECT USER_FULLNAME FROMup_org_user');
insert into `ofproperty` (`name`,`propValue`) values('jdbcUserProvider.emailField','USER_EMAIL');
insert into `ofproperty` (`name`,`propValue`) values('jdbcUserProvider.loadUserSQL','selectt1.USER_ACCOUNT,t1.USER_FULLNAME,t2.USER_EMAIL from up_org_usert1,up_org_user_ext t2 where t1.USER_ID = t2.USER_ID and t1.USER_ACCOUNT=?');
insert into `ofproperty` (`name`,`propValue`) values('jdbcUserProvider.nameField','USER_ACCOUNT');
insert into `ofproperty` (`name`,`propValue`) values('jdbcUserProvider.userCountSQL','SELECT COUNT(*) FROMup_org_user');
insert into `ofproperty` (`name`,`propValue`) values('jdbcUserProvider.usernameField','USER_FULLNAME');
insert into `ofproperty` (`name`,`propValue`) values('passwordKey','7yme2TF29702cfO');
insert into `ofproperty` (`name`,`propValue`)values('provider.admin.className','org.jivesoftware.openfire.admin.DefaultAdminProvider');
insert into `ofproperty` (`name`,`propValue`)values('provider.auth.className','com.neusoft.openfire.plugins.PhAuthProvider');
insert into `ofproperty` (`name`,`propValue`) values('provider.group.className','org.jivesoftware.openfire.group.DefaultGroupProvider');
insert into `ofproperty` (`name`,`propValue`)values('provider.lockout.className','org.jivesoftware.openfire.lockout.DefaultLockOutProvider');
insert into `ofproperty` (`name`,`propValue`) values('provider.securityAudit.className','org.jivesoftware.openfire.security.DefaultSecurityAuditProvider');
insert into `ofproperty` (`name`,`propValue`)values('provider.user.className','com.neusoft.openfire.plugins.PhUserProvider');
insert into `ofproperty` (`name`,`propValue`) values('provider.vcard.className','org.jivesoftware.openfire.vcard.DefaultVCardProvider');
insert into `ofproperty` (`name`,`propValue`) values('update.lastCheck','1404268354511');
insert into `ofproperty` (`name`,`propValue`) values('xmpp.auth.anonymous','true');
insert into `ofproperty` (`name`,`propValue`) values('xmpp.domain','xueyi');
insert into `ofproperty` (`name`,`propValue`) values('xmpp.httpbind.scriptSyntax.enabled','true');
insert into `ofproperty` (`name`,`propValue`) values('xmpp.session.conflict-limit','0');
insert into `ofproperty` (`name`,`propValue`) values('xmpp.socket.ssl.active','true');
其中标红的几句各位改成各自数据库的连接串,直接连接unieap支撑库。
2、在build/lib文件夹下加入Oracle驱动ojdbc14.jar。
3、加入类
com.neusoft.openfire.plugins.PhAuthProvider
com.neusoft.openfire.plugins.PhUserProvider
com.neusoft.openfire.plugins.MdFive
PhAuthProvider是用户验证的类,根据JdbcAuthProvider改写而来。
PhUserProvider是用户相关信息获取的类,根据JdbcUserProvider改写而来。
既然openfire都提供了jdbc和Oracle的支持了,为什么还要自己写呢?原因有两点:
a、因为openfire默认提供的数据库连接方式里面没有给出填写登陆数据库的用户名和密码的接口;
b、openfire提供了各种加密的方式,但是unieap的密码加密方式是先使用md5后使用base64混淆,所以这块要我们自己实现。
4、此步可不做:由于使用ant编译的工程,所以要调试jsp(servlet)的时候会比较麻烦,可以在调试时,把源文件地址指向work/jspc目录下。
5、重新使用ant工具编译工程,重启工程。分别使用spark和strophe连接到服务器测试,截图如下:
6、再截两张控制台的图,如下图。这时,unieap里面自带的用户就已经全部被openfire读出来了。
下图为控制台显示的在线用户。
六、代码(改编自jdbcauthprovider和jdbcuserprovider)
MdFive
public class MdFive {
public static String md5Encoder(Stringstr){
byte abyte0[] = null;
byte abyte1[] = null;
try {
MessageDigest md =MessageDigest.getInstance("MD5");
md.reset();
abyte0 = str.getBytes();
abyte1 = md.digest(abyte0);
}catch(NoSuchAlgorithmExceptione){
e.printStackTrace();
}
BASE64Encoder encoder = newBASE64Encoder();
String result =encoder.encode(abyte1);
return result;
}
public static void main(String args[]){
System.out.println(md5Encoder("1"));
}
}
PhAuthProvider
/**
* $Revision: 1116 $
* $Date: 2005-03-10 20:18:08 -0300 (Thu, 10 Mar 2005) $
*
* Copyright (C) 2005-2008 Jive Software. All rightsreserved.
*
* Licensed under the Apache License,Version 2.0 (the "License");
* you may not use this file except incompliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreedto in writing, software
* distributed under the License is distributedon an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANYKIND, either express or implied.
* See the License for the specific languagegoverning permissions and
* limitations under the License.
*/
package com.neusoft.openfire.plugins;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.auth.AuthFactory;
import org.jivesoftware.openfire.auth.AuthProvider;
import org.jivesoftware.openfire.auth.UnauthorizedException;
importorg.jivesoftware.openfire.user.UserAlreadyExistsException;
import org.jivesoftware.openfire.user.UserManager;
import org.jivesoftware.openfire.user.UserNotFoundException;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The JDBC auth provider allows you toauthenticate users against any database
* that you can connect to with JDBC. It can beused along with the
* {@linkHybridAuthProvider hybrid} authprovider, so that you can also have
* XMPP-only users that won't pollute your externaldata.<p>
*
* To enable this provider, set the followingin the system properties:
* <ul>
* <li><tt>provider.auth.className =org.jivesoftware.openfire.auth.JDBCAuthProvider</tt></li>
* </ul>
*
* You'll also need to set your JDBC driver,connection string, and SQL statements:
*
* <ul>
* <li><tt>jdbcProvider.driver = com.mysql.jdbc.Driver</tt></li>
* <li><tt>jdbcProvider.connectionString = jdbc:mysql://localhost/dbname?user=username&password=secret</tt></li>
* <li><tt>jdbcAuthProvider.passwordSQL = SELECT password FROMuser_account WHERE username=?</tt></li>
* <li><tt>jdbcAuthProvider.passwordType = plain</tt></li>
* <li><tt>jdbcAuthProvider.allowUpdate = true</tt></li>
* <li><tt>jdbcAuthProvider.setPasswordSQL = UPDATE user_account SETpassword=? WHERE username=?</tt></li>
* </ul>
*
* In order to use the configured JDBCconnection provider do not use a JDBC
* connection string, set the followingproperty
*
* <ul>
* <li><tt>jdbcAuthProvider.useConnectionProvider = true</tt></li>
* </ul>
*
* The passwordType setting tells Openfirehow the password is stored. Setting the value
* is optional (when not set, it defaults to"plain"). The valid values are:<ul>
* <li>{@linkPasswordType#plain plain}
* <li>{@linkPasswordType#md5 md5}
* <li>{@linkPasswordType#sha1 sha1}
* <li>{@linkPasswordType#sha256 sha256}
* <li>{@linkPasswordType#sha512 sha512}
* </ul>
*
* @author David Snopek
*/
public class PhAuthProviderimplements AuthProvider {
private static final Logger Log = LoggerFactory.getLogger(PhAuthProvider.class);
private String connectionString;
//add byxue.y@neusoft.com
//增加数据库登陆的用户名和密码字段
private String user;
private String password;
private String passwordSQL;
private String setPasswordSQL;
private PasswordType passwordType;
private boolean allowUpdate;
private boolean useConnectionProvider;
/**
* Constructs a new JDBC authenticationprovider.
*/
public PhAuthProvider() {
// Convert XML based provider setup to Database based
JiveGlobals.migrateProperty("jdbcProvider.driver");
JiveGlobals.migrateProperty("jdbcProvider.connectionString");
JiveGlobals.migrateProperty("jdbcAuthProvider.passwordSQL");
JiveGlobals.migrateProperty("jdbcAuthProvider.passwordType");
JiveGlobals.migrateProperty("jdbcAuthProvider.setPasswordSQL");
JiveGlobals.migrateProperty("jdbcAuthProvider.allowUpdate");
//add by xue.y@neusoft.com
//增加数据库登陆的用户名和密码字段
JiveGlobals.migrateProperty("jdbcProvider.user");
JiveGlobals.migrateProperty("jdbcProvider.password");
useConnectionProvider = JiveGlobals.getBooleanProperty("jdbcAuthProvider.useConnectionProvider");
if (!useConnectionProvider) {
// Load the JDBC driver and connection string.
String jdbcDriver = JiveGlobals.getProperty("jdbcProvider.driver");
try {
Class.forName(jdbcDriver).newInstance();
}
catch (Exception e) {
Log.error("Unable to load JDBC driver: " + jdbcDriver, e);
return;
}
connectionString = JiveGlobals.getProperty("jdbcProvider.connectionString");
//add by xue.y@neusoft.com
//增加数据库登陆的用户名和密码字段
user = JiveGlobals.getProperty("jdbcProvider.user");
password = JiveGlobals.getProperty("jdbcProvider.password");
}
// Load SQL statements.
passwordSQL = JiveGlobals.getProperty("jdbcAuthProvider.passwordSQL");
setPasswordSQL = JiveGlobals.getProperty("jdbcAuthProvider.setPasswordSQL");
allowUpdate = JiveGlobals.getBooleanProperty("jdbcAuthProvider.allowUpdate",false);
passwordType = PasswordType.plain;
try {
passwordType = PasswordType.valueOf(
JiveGlobals.getProperty("jdbcAuthProvider.passwordType", "plain"));
}
catch (IllegalArgumentException iae) {
Log.error(iae.getMessage(), iae);
}
}
public void authenticate(String username, Stringpassword) throws UnauthorizedException {
if (username == null || password == null) {
throw new UnauthorizedException();
}
username =username.trim().toLowerCase();
if (username.contains("@")) {
// Check that the specified domain matches the server's domain
int index = username.indexOf("@");
String domain =username.substring(index + 1);
if (domain.equals(XMPPServer.getInstance().getServerInfo().getXMPPDomain())){
username =username.substring(0, index);
} else {
// Unknown domain. Return authentication failed.
throw new UnauthorizedException();
}
}
String userPassword;
try {
userPassword =getPasswordValue(username);
}
catch (UserNotFoundException unfe) {
throw new UnauthorizedException();
}
// If the user's password doesn't match the password passed in,authentication
// should fail.
if (passwordType == PasswordType.md5) {
//modify by xue.y@neusoft.com
//password = StringUtils.hash(password, "MD5");
password = MdFive.md5Encoder(password);
}
else if (passwordType == PasswordType.sha1) {
password = StringUtils.hash(password,"SHA-1");
}
else if (passwordType == PasswordType.sha256) {
password = StringUtils.hash(password,"SHA-256");
}
else if (passwordType == PasswordType.sha512) {
password = StringUtils.hash(password,"SHA-512");
}
if (!password.equals(userPassword)) {
throw new UnauthorizedException();
}
// Got this far, so the user must be authorized.
createUser(username);
}
public void authenticate(String username, String token,String digest)
throws UnauthorizedException
{
if (passwordType != PasswordType.plain) {
throw new UnsupportedOperationException("Digest authentication not supported for "
+ "password type " + passwordType);
}
if (username == null || token == null || digest == null) {
throw new UnauthorizedException();
}
username =username.trim().toLowerCase();
if (username.contains("@")) {
// Check that the specified domain matches the server's domain
int index = username.indexOf("@");
String domain =username.substring(index + 1);
if (domain.equals(XMPPServer.getInstance().getServerInfo().getXMPPDomain())){
username =username.substring(0, index);
} else {
// Unknown domain. Return authentication failed.
throw new UnauthorizedException();
}
}
String password;
try {
password =getPasswordValue(username);
}
catch (UserNotFoundException unfe) {
throw new UnauthorizedException();
}
String anticipatedDigest = AuthFactory.createDigest(token,password);
if(!digest.equalsIgnoreCase(anticipatedDigest)) {
throw new UnauthorizedException();
}
// Got this far, so the user must be authorized.
createUser(username);
}
public boolean isPlainSupported() {
// If the auth SQL is defined, plain text authentication issupported.
return (passwordSQL != null);
}
public boolean isDigestSupported() {
// The auth SQL must be defined and the password type is supported.
return (passwordSQL != null && passwordType == PasswordType.plain);
}
public String getPassword(Stringusername) throws UserNotFoundException,
UnsupportedOperationException
{
if (!supportsPasswordRetrieval()) {
throw new UnsupportedOperationException();
}
if (username.contains("@")) {
// Check that the specified domain matches the server's domain
int index = username.indexOf("@");
String domain =username.substring(index + 1);
if (domain.equals(XMPPServer.getInstance().getServerInfo().getXMPPDomain())){
username =username.substring(0, index);
} else {
// Unknown domain.
throw new UserNotFoundException();
}
}
return getPasswordValue(username);
}
public void setPassword(String username, Stringpassword)
throws UserNotFoundException,UnsupportedOperationException
{
if (allowUpdate && setPasswordSQL != null) {
setPasswordValue(username,password);
} else {
throw new UnsupportedOperationException();
}
}
public boolean supportsPasswordRetrieval() {
return (passwordSQL != null && passwordType == PasswordType.plain);
}
private Connection getConnection() throws SQLException {
if (useConnectionProvider)
return DbConnectionManager.getConnection();
//modify by xue.y
//return DriverManager.getConnection(connectionString);
return DriverManager.getConnection(connectionString, user, this.password);
}
/**
* Returns the value of the password field.It will be in plain text or hashed
* format, depending on the password type.
*
* @param username user to retrievethe password field for
* @return the password value.
* @throws UserNotFoundException if thegiven user could not be loaded.
*/
private String getPasswordValue(Stringusername) throws UserNotFoundException {
String password = null;
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
if (username.contains("@")) {
// Check that the specified domain matches the server's domain
int index = username.indexOf("@");
String domain =username.substring(index + 1);
if (domain.equals(XMPPServer.getInstance().getServerInfo().getXMPPDomain())){
username =username.substring(0, index);
} else {
// Unknown domain.
throw new UserNotFoundException();
}
}
try {
con = getConnection();
pstmt = con.prepareStatement(passwordSQL);
pstmt.setString(1, username);
rs = pstmt.executeQuery();
// If the query had no results, the username and password
// did not match a user record. Therefore, throw an exception.
if (!rs.next()) {
throw new UserNotFoundException();
}
password = rs.getString(1);
}
catch (SQLException e) {
Log.error("Exception in JDBCAuthProvider", e);
throw new UserNotFoundException();
}
finally {
DbConnectionManager.closeConnection(rs,pstmt, con);
}
return password;
}
private void setPasswordValue(String username, Stringpassword) throws UserNotFoundException {
Connection con = null;
PreparedStatement pstmt = null;
if (username.contains("@")) {
// Check that the specified domain matches the server's domain
int index = username.indexOf("@");
String domain =username.substring(index + 1);
if (domain.equals(XMPPServer.getInstance().getServerInfo().getXMPPDomain())){
username =username.substring(0, index);
} else {
// Unknown domain.
throw new UserNotFoundException();
}
}
try {
con = getConnection();
pstmt = con.prepareStatement(setPasswordSQL);
pstmt.setString(2, username);
if (passwordType == PasswordType.md5) {
//modify by xue.y@neusoft.com
//password = StringUtils.hash(password, "MD5");
password= MdFive.md5Encoder(password);
}
else if (passwordType == PasswordType.sha1) {
password = StringUtils.hash(password,"SHA-1");
}
else if (passwordType == PasswordType.sha256) {
password = StringUtils.hash(password,"SHA-256");
}
else if (passwordType == PasswordType.sha512) {
password = StringUtils.hash(password,"SHA-512");
}
pstmt.setString(1, password);
pstmt.executeQuery();
}
catch (SQLException e) {
Log.error("Exception in JDBCAuthProvider", e);
throw new UserNotFoundException();
}
finally {
DbConnectionManager.closeConnection(pstmt,con);
}
}
/**
* Indicates how the password is stored.
*/
@SuppressWarnings({"UnnecessarySemicolon"}) // Support for QDox Parser
public enum PasswordType {
/**
* The password is stored as plaintext.
*/
plain,
/**
* The password is stored as a hex-encoded MD5 hash.
*/
md5,
/**
* The password is stored as a hex-encoded SHA-1 hash.
*/
sha1,
/**
* The password is stored as a hex-encoded SHA-256 hash.
*/
sha256,
/**
* The password is stored as a hex-encoded SHA-512 hash.
*/
sha512;
}
/**
* Checks to see if the user exists; ifnot, a new user is created.
*
* @param username the username.
*/
private static void createUser(String username) {
// See if the user exists in the database. If not, automatically createthem.
UserManager userManager = UserManager.getInstance();
try {
userManager.getUser(username);
}
catch (UserNotFoundException unfe) {
try {
Log.debug("JDBCAuthProvider: Automatically creating new user account for " + username);
UserManager.getUserProvider().createUser(username,StringUtils.randomString(8),
null, null);
}
catch (UserAlreadyExistsException uaee) {
// Ignore.
}
}
}
}
PhUserProvider
/**
* $Revision: $
* $Date: $
*
* Copyright (C) 2005-2008 Jive Software. All rightsreserved.
*
* Licensed under the Apache License,Version 2.0 (the "License");
* you may not use this file except incompliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreedto in writing, software
* distributed under the License is distributedon an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANYKIND, either express or implied.
* See the License for the specific languagegoverning permissions and
* limitations under the License.
*/
package com.neusoft.openfire.plugins;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.user.User;
importorg.jivesoftware.openfire.user.UserAlreadyExistsException;
import org.jivesoftware.openfire.user.UserCollection;
import org.jivesoftware.openfire.user.UserNotFoundException;
import org.jivesoftware.openfire.user.UserProvider;
import org.jivesoftware.util.JiveGlobals;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.JID;
/**
* The JDBC user provider allows you to use anexternal database to define the users.
* It is best used with the JDBCAuthProvider& JDBCGroupProvider to provide integration
* between your external system and Openfire.All data is treated as read-only so any
* set operations will result in anexception.<p/>
*
* For the seach facility, the SQL willbe constructed from the SQL in the <i>search</i>
* section below, as well as the <i>usernameField</i>, the <i>nameField</i> and the
* <i>emailField</i>.<p/>
*
* To enable this provider, set the followingin the system properties:<p/>
*
* <ul>
* <li><tt>provider.user.className =org.jivesoftware.openfire.user.JDBCUserProvider</tt></li>
* </ul>
*
* Then you need to set your driver, connectionstring and SQL statements:
* <p/>
* <ul>
* <li><tt>jdbcProvider.driver = com.mysql.jdbc.Driver</tt></li>
* <li><tt>jdbcProvider.connectionString = jdbc:mysql://localhost/dbname?user=username&password=secret</tt></li>
* <li><tt>jdbcUserProvider.loadUserSQL = SELECT name,email FROMmyUser WHERE user = ?</tt></li>
* <li><tt>jdbcUserProvider.userCountSQL = SELECT COUNT(*) FROMmyUser</tt></li>
* <li><tt>jdbcUserProvider.allUsersSQL = SELECT user FROM myUser</tt></li>
* <li><tt>jdbcUserProvider.searchSQL = SELECT user FROM myUserWHERE</tt></li>
* <li><tt>jdbcUserProvider.usernameField = myUsernameField</tt></li>
* <li><tt>jdbcUserProvider.nameField = myNameField</tt></li>
* <li><tt>jdbcUserProvider.emailField = mymailField</tt></li>
* </ul>
*
* In order to use the configured JDBCconnection provider do not use a JDBC
* connection string, set the followingproperty
*
* <ul>
* <li><tt>jdbcUserProvider.useConnectionProvider = true</tt></li>
* </ul>
*
*
* @author Huw Richards huw.richards@gmail.com
*/
public class PhUserProviderimplements UserProvider {
private static final Logger Log = LoggerFactory.getLogger(PhUserProvider.class);
private String connectionString;
//add byxue.y@neusoft.com
//增加数据库登陆的用户名和密码字段
private String user;
private String password;
private String loadUserSQL;
private String userCountSQL;
private String allUsersSQL;
private String searchSQL;
private String usernameField;
private String nameField;
private String emailField;
private boolean useConnectionProvider;
private static final boolean IS_READ_ONLY = true;
/**
* Constructs a new JDBC user provider.
*/
public PhUserProvider() {
// Convert XML based provider setup to Database based
JiveGlobals.migrateProperty("jdbcProvider.driver");
JiveGlobals.migrateProperty("jdbcProvider.connectionString");
JiveGlobals.migrateProperty("jdbcUserProvider.loadUserSQL");
JiveGlobals.migrateProperty("jdbcUserProvider.userCountSQL");
JiveGlobals.migrateProperty("jdbcUserProvider.allUsersSQL");
JiveGlobals.migrateProperty("jdbcUserProvider.searchSQL");
JiveGlobals.migrateProperty("jdbcUserProvider.usernameField");
JiveGlobals.migrateProperty("jdbcUserProvider.nameField");
JiveGlobals.migrateProperty("jdbcUserProvider.emailField");
//add by xue.y@neusoft.com
//增加数据库登陆的用户名和密码字段
JiveGlobals.migrateProperty("jdbcProvider.user");
JiveGlobals.migrateProperty("jdbcProvider.password");
useConnectionProvider = JiveGlobals.getBooleanProperty("jdbcUserProvider.useConnectionProvider");
// Load the JDBC driver and connection string.
if (!useConnectionProvider) {
String jdbcDriver = JiveGlobals.getProperty("jdbcProvider.driver");
try {
Class.forName(jdbcDriver).newInstance();
}
catch (Exception e) {
Log.error("Unable toload JDBC driver: " + jdbcDriver, e);
return;
}
connectionString = JiveGlobals.getProperty("jdbcProvider.connectionString");
//add by xue.y@neusoft.com
//增加数据库登陆的用户名和密码字段
user = JiveGlobals.getProperty("jdbcProvider.user");
password = JiveGlobals.getProperty("jdbcProvider.password");
}
// Load database statementsfor user data.
loadUserSQL = JiveGlobals.getProperty("jdbcUserProvider.loadUserSQL");
userCountSQL = JiveGlobals.getProperty("jdbcUserProvider.userCountSQL");
allUsersSQL = JiveGlobals.getProperty("jdbcUserProvider.allUsersSQL");
searchSQL = JiveGlobals.getProperty("jdbcUserProvider.searchSQL");
usernameField = JiveGlobals.getProperty("jdbcUserProvider.usernameField");
nameField = JiveGlobals.getProperty("jdbcUserProvider.nameField");
emailField = JiveGlobals.getProperty("jdbcUserProvider.emailField");
}
public User loadUser(String username) throwsUserNotFoundException {
if(username.contains("@")) {
if (!XMPPServer.getInstance().isLocal(new JID(username))) {
throw new UserNotFoundException("Cannot load user of remote server: " + username);
}
username =username.substring(0,username.lastIndexOf("@"));
}
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = getConnection();
pstmt = con.prepareStatement(loadUserSQL);
pstmt.setString(1, username);
rs = pstmt.executeQuery();
if (!rs.next()) {
throw new UserNotFoundException();
}
String name = rs.getString(1);
String email = rs.getString(2);
return new User(username, name, email, new Date(), new Date());
}
catch (Exception e) {
throw new UserNotFoundException(e);
}
finally {
DbConnectionManager.closeConnection(rs, pstmt,con);
}
}
public User createUser(String username, String password, Stringname, String email)
throws UserAlreadyExistsException {
// Reject the operation sincethe provider is read-only
throw new UnsupportedOperationException();
}
public void deleteUser(String username) {
// Reject the operation sincethe provider is read-only
throw new UnsupportedOperationException();
}
public int getUserCount() {
int count = 0;
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = getConnection();
pstmt = con.prepareStatement(userCountSQL);
rs = pstmt.executeQuery();
if (rs.next()) {
count = rs.getInt(1);
}
}
catch (SQLException e) {
Log.error(e.getMessage(), e);
}
finally {
DbConnectionManager.closeConnection(rs, pstmt,con);
}
return count;
}
public Collection<User> getUsers() {
Collection<String> usernames = getUsernames(0, Integer.MAX_VALUE);
return new UserCollection(usernames.toArray(newString[usernames.size()]));
}
public Collection<String> getUsernames() {
return getUsernames(0, Integer.MAX_VALUE);
}
private Collection<String> getUsernames(int startIndex, int numResults) {
List<String> usernames = new ArrayList<String>(500);
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = getConnection();
if ((startIndex==0) &&(numResults==Integer.MAX_VALUE))
{
pstmt = con.prepareStatement(allUsersSQL);
// Set the fetch size. This will prevent some JDBC drivers from trying
// to load the entire result set into memory.
DbConnectionManager.setFetchSize(pstmt,500);
rs = pstmt.executeQuery();
while (rs.next()) {
usernames.add(rs.getString(1));
}
}
else {
pstmt = DbConnectionManager.createScrollablePreparedStatement(con,allUsersSQL);
DbConnectionManager.limitRowsAndFetchSize(pstmt,startIndex, numResults);
rs = pstmt.executeQuery();
DbConnectionManager.scrollResultSet(rs,startIndex);
int count = 0;
while (rs.next() && count < numResults){
usernames.add(rs.getString(1));
count++;
}
}
if (Log.isDebugEnabled()) {
Log.debug("Results: " +usernames.size());
LogResults(usernames);
}
}
catch (SQLException e) {
Log.error(e.getMessage(), e);
}
finally {
DbConnectionManager.closeConnection(rs,pstmt, con);
}
return usernames;
}
public Collection<User>getUsers(int startIndex, int numResults) {
Collection<String> usernames =getUsernames(startIndex, numResults);
return newUserCollection(usernames.toArray(new String[usernames.size()]));
}
public void setName(String username, String name) throwsUserNotFoundException {
// Reject the operation sincethe provider is read-only
throw new UnsupportedOperationException();
}
public void setEmail(String username, String email) throwsUserNotFoundException {
// Reject the operation sincethe provider is read-only
throw new UnsupportedOperationException();
}
public void setCreationDate(String username, Date creationDate) throwsUserNotFoundException {
// Reject the operation sincethe provider is read-only
throw new UnsupportedOperationException();
}
public void setModificationDate(String username, DatemodificationDate) throws UserNotFoundException {
// Reject the operation sincethe provider is read-only
throw new UnsupportedOperationException();
}
public Set<String>getSearchFields() throws UnsupportedOperationException {
if (searchSQL == null) {
throw new UnsupportedOperationException();
}
return new LinkedHashSet<String>(Arrays.asList("Username", "Name", "Email"));
}
public Collection<User> findUsers(Set<String>fields, String query) throws UnsupportedOperationException {
return findUsers(fields, query, 0, Integer.MAX_VALUE);
}
public Collection<User> findUsers(Set<String>fields, String query, int startIndex,
int numResults) throws UnsupportedOperationException
{
if (searchSQL == null) {
throw new UnsupportedOperationException();
}
if (fields.isEmpty()) {
return Collections.emptyList();
}
if (!getSearchFields().containsAll(fields)) {
throw new IllegalArgumentException("Search fields " + fields + " are not valid.");
}
if (query == null || "".equals(query)) {
return Collections.emptyList();
}
// SQL LIKE queries don't mapdirectly into a keyword/wildcard search like we want.
// Therefore, we do a best approximiationby replacing '*' with '%' and then
// surrounding the wholequery with two '%'. This will return more data than desired,
// but is better thanreturning less data than desired.
query = "%" + query.replace('*', '%') + "%";
if (query.endsWith("%%")) {
query = query.substring(0, query.length() - 1);
}
List<String> usernames = newArrayList<String>(50);
Connection con = null;
PreparedStatement pstmt = null;
int queries=0;
ResultSet rs = null;
try {
StringBuilder sql = new StringBuilder(90);
sql.append(searchSQL);
boolean first = true;
if (fields.contains("Username")) {
sql.append(" username LIKE ?");
queries++;
first = false;
}
if (fields.contains("Name")) {
if (!first) {
sql.append(" AND");
}
sql.append(" name LIKE ?");
queries++;
first = false;
}
if (fields.contains("Email")) {
if (!first) {
sql.append(" AND");
}
sql.append(" email LIKE ?");
queries++;
}
con = getConnection();
if ((startIndex==0) &&(numResults==Integer.MAX_VALUE))
{
pstmt =con.prepareStatement(sql.toString());
for (int i=1; i<=queries; i++)
{
pstmt.setString(i, query);
}
rs = pstmt.executeQuery();
while (rs.next()) {
usernames.add(rs.getString(1));
}
} else {
pstmt = DbConnectionManager.createScrollablePreparedStatement(con,sql.toString());
DbConnectionManager.limitRowsAndFetchSize(pstmt,startIndex, numResults);
for (int i=1; i<=queries; i++)
{
pstmt.setString(i, query);
}
rs = pstmt.executeQuery();
// Scroll to the start index.
DbConnectionManager.scrollResultSet(rs,startIndex);
int count = 0;
while (rs.next() && count < numResults){
usernames.add(rs.getString(1));
count++;
}
}
if (Log.isDebugEnabled())
{
Log.debug("Results: " +usernames.size());
LogResults(usernames);
}
}
catch (SQLException e) {
Log.error(e.getMessage(), e);
}
finally {
DbConnectionManager.closeConnection(rs,pstmt, con);
}
return newUserCollection(usernames.toArray(new String[usernames.size()]));
}
public boolean isReadOnly() {
return IS_READ_ONLY;
}
public boolean isNameRequired() {
return false;
}
public boolean isEmailRequired() {
return false;
}
/**
* Make sure thatLog.isDebugEnabled()==true before calling this method.
* Twenty elements will be logged in everylog line, so for 81-100 elements
* five log lines will be generated
* @param listElements a list ofStrings which will be logged
*/
private void LogResults(List<String> listElements){
String callingMethod = Thread.currentThread().getStackTrace()[3].getMethodName();
StringBuilder sb = newStringBuilder(256);
int count = 0;
for (String element : listElements)
{
if (count > 20)
{
Log.debug(callingMethod + " results: " + sb.toString());
sb.delete(0, sb.length());
count = 0;
}
sb.append(element).append(",");
count++;
}
sb.append(".");
Log.debug(callingMethod + " results: " + sb.toString());
}
private Connection getConnection() throws SQLException {
if (useConnectionProvider) {
returnDbConnectionManager.getConnection();
} else
{
//modify by xue.y
//return DriverManager.getConnection(connectionString);
return DriverManager.getConnection(connectionString, user, this.password);
}
}
}