openfire是一个基于XMPP协议开源的就即时通信服务器系统。利用它再加上同样开源的spark,可以轻松的为你的网站用户提供一个类似QQ这样的及时通讯软件,来扩展网站服务,增加用户黏度。openfire拥有强大功能的同时还拥有强劲的性能,据称单台普通配置的服务器可以支撑1W+的并发。另外它还拥有一个完备的插件系统,可以通过插件实现你需要的任何功能。总之好处多多,务须多言,如果你的网站或者应用系统有IM的需求,openfire是不二选择。

openfire架构
openfire的安装配置很简单,这里就不再赘述。本文要讲的内容是将openfire同既有系统结合的第一步:整合用户系统,即让网站现有用户使用自己的帐号进行登录。由于公司的网站基于django开发,使用的是django自带的用户系统,因此本文内容及提供的源代码适用于所有使用django开发的网站。
本文测试用的客户端使用的是spark,可以到这里下载。
一、使用外部用户系统的配置
安装openfire,启动服务控制台,进入管理界面。首次进入管理,会自动进入配置界面,配置很简单。注意一点,在选择数据库时,使用“外部数据库”,openfire支持常用的数据库,包括mysql、oracle、mssql等。我使用的是mysql,这样openfire就会自动在指定的mysql数据库上建表,并写入初始数据。
openfire自带有一套用户系统,实现了用户登录验证功能,同时还允许通过配置,使用JDBC访问指定的数据库,这样就可以通过配置访问外部的用户系统了。最简单的方式是直接修改数据库进行配置。
1.在mysql客户端中打开openfire的数据库,打开表ofproperty,在里面添加4条记录:
name
|
propValue
|
jdbcProvider.driver |
com.mysql.jdbc.Driver |
jdbcProvider.connectionString |
jdbc:mysql://[host]:3306/[database]?user=[user]&password=[pwd] |
jdbcProvider.passwordSQL |
select password from auth_user where username=? |
jdbcProvider.passwordType |
md5 |
2.修改name为provider.auth.className的记录的propValue字段值为:org.jivesoftware.openfire.auth.JDBCAuthProvider
3.重启openfire控制台,如果你网站的密码字段采用的是标准MD5算法(另外也支持明文和SHA算法),那么现在就可以启动spark使用网站的帐号进行登录了。
4.在上表添加记录:
admin.authorizedJIDs ——[username]@[openfire-domain]
username是网站用户表内存在的一个用户名,openfire-domain是你的openfire的域。这样再需要登录管理界面时,使用这个用户名即可。
二、django用户系统的问题
是不是很简单?但如果你跟我一样,使用django开发网站,并且使用了django提供的用户系统。那么很遗憾,经过上面的步骤,你依然无法登录。原因就在于django的密码字段并不是单纯保存了密码,而是采用了和unix的shadow文件一样的格式,像下面这样:
sha1$3d5fd$8bf87829f5749c57f8336007c5fc6cbb9cfbb1f3
这个字段里包含了3个信息,即:本密码的算法、盐、密文,使用$进行分隔。上面这个JDBCAuthProvider使用的是常规的密码验证方法,即:
1.读取指定用户名的密码字段值;
2.使用passwordType指定的算法加密用户输入的密码;
3.比较本次加密的的字符串和读取出来的密码字段值是否相等。
由此可以看出,用指定算法加密的密码不可能和django的用户表中的密码字段值相等。
三、搞定它
问题找到了,是JDBCAuthProvider的密码验证算法不能满足要求,那么就从它开刀了。下载openfire的源码(猛击这里),用eclipse打开,设置方法请参见这里,很nice的一篇文章,清晰、翔实、可操作性强,我这个java门外汉照着步骤做都很快搞定了,只提醒初学者一点,不要把你的源码放在名字有中文的目录下——高手请无视:)。
openfire源码很赞,这里就不再多说了。在org.jivesoftware.openfire.auth这个包里可以找到JDBCAuthProvider这个类,验证密码的函数就是authenticate。
那么,我们修改它吧?呃,这样子似乎有点太暴力了,另外,如果将来官方在新版本里修改了这个类,就杯具了……
正确的做法:新建一个类,像JDBCAuthProvider一样实现AuthProvider接口的相关方法,这样子验证函数就随便你怎么折腾了,下面是我的类,你可以拿去直接使用:
package org.firefishsoftware.openfire.plugin;
import org.jivesoftware.openfire.user.UserAlreadyExistsException;
import org.jivesoftware.openfire.user.UserManager;
import org.jivesoftware.openfire.user.UserNotFoundException;
import org.jivesoftware.openfire.auth.*;
import org.jivesoftware.openfire.auth.JDBCAuthProvider.PasswordType;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.Log;
import org.jivesoftware.util.StringUtils;
import org.jivesoftware.database.DbConnectionManager;
import java.io.UnsupportedEncodingException;
import java.sql.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* Created by firefish
* Date: 2010-5-6
* Time: 11:06
* 仿照JDBCAuthProvider
*/
public class DjangoAuthProvider implements AuthProvider {
private String connectionString;
private String passwordSQL;
private String userIdSQL;
private PasswordType passwordType;
private boolean useConnectionProvider;
/**
* Constructs a new JDBC authentication provider.
*/
public DjangoAuthProvider() {
// Convert XML based provider setup to Database based
JiveGlobals.migrateProperty(“jdbcProvider.driver”);
JiveGlobals.migrateProperty(“jdbcProvider.connectionString”);
JiveGlobals.migrateProperty(“djangoAuthProvider.passwordSQL”);
JiveGlobals.migrateProperty(“djangoAuthProvider.userIdSQL”);
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”);
}
// Load SQL statements.
passwordSQL = JiveGlobals.getProperty(“djangoAuthProvider.passwordSQL”);
userIdSQL = JiveGlobals.getProperty(“djangoAuthProvider.userIdSQL”);
passwordType = PasswordType.sha1;
}
public void authenticate(String username, String password) 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();
}
String[] passwordData = userPassword.split(“\$”);
if (passwordData.length != 3){
Log.info(“无效的密码数据:”+userPassword);
throw new UnauthorizedException();
}
// If the user’s password doesn’t match the password passed in, authentication
// should fail.
MessageDigest digst;
try {
digst = MessageDigest.getInstance(“SHA-1″);
digst.update(passwordData[1].getBytes(“UTF8″));
digst.update(password.getBytes(“UTF8″));
password = StringUtils.encodeHex(digst.digest());
} catch (NoSuchAlgorithmException e) {
Log.info(“加密失败:”+e.toString());
} catch (UnsupportedEncodingException e) {
Log.info(“加密失败:”+e.toString());
}
if (!password.equals(passwordData[2])) {
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
{
}
public boolean isPlainSupported() {
// If the auth SQL is defined, plain text authentication is supported.
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(String username) 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, String password)
throws UserNotFoundException, UnsupportedOperationException
{
}
public boolean supportsPasswordRetrieval() {
return (passwordSQL != null && passwordType == PasswordType.plain);
}
private Connection getConnection() throws SQLException {
if (useConnectionProvider)
return DbConnectionManager.getConnection();
return DriverManager.getConnection(connectionString);
}
/**
* 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 retrieve the password field for
* @return the password value.
* @throws UserNotFoundException if the given user could not be loaded.
*/
private String getPasswordValue(String username) throws UserNotFoundException {
String password = null;
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
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;
}
/**
* Checks to see if the user exists; if not, 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 create them.
UserManager userManager = UserManager.getInstance();
try {
userManager.getUser(username);
}
catch (UserNotFoundException unfe) {
try {
Log.debug(“DjangoAuthProvider: Automatically creating new user account for ” + username);
UserManager.getUserProvider().createUser(username, StringUtils.randomString(8),
null, null);
}
catch (UserAlreadyExistsException uaee) {
// Ignore.
}
}
}
}
删掉了一些不需要代码,修改了authenticate函数,将读出来的密码进行拆分,获取盐和密文。将用户输入的密码用sha算法加上盐进行加密后,比较密文是否相等。简单起见,本文只实现了sha算法,再做的完善一点可以使用密码字段中指定的算法进行加密。
最后修改ofproperty表的provider.auth.className记录对应的值为:org.firefishsoftware.openfire.plugin.DjangoAuthProvider,重启openfire控制台,再用客户端登录试试!
如果你的密码采用了自定义的算法进行加密,那么上面的方法同样适用你。
相关推荐
- 从官方源或可信的第三方市场下载安装客户端应用。 - 如果是首次使用,需要有可用的Openfire服务器地址和账户信息。 在使用过程中,可能会遇到的问题包括网络连接问题、服务器配置问题、账号认证问题等。这些问题...
- Openfire支持多种客户端,包括官方的Spark,以及第三方的Jabber客户端、移动应用如Gajim、 Psi等。 - 用户可以通过这些客户端进行IM聊天、群聊、文件传输等操作。 5. **安全与性能优化** - 定期更新Openfire,...
你也可以选择其他第三方的Android XMPP库,如 asmack 或 converse.js。这些库提供了连接、登录、发送接收消息、处理事件等功能的API。 4. **连接与认证**:在Android应用中,需要编写代码连接到Openfire服务器,并...
- **下载Spark客户端**:访问Spark官方网站或第三方资源网站下载Spark客户端最新版本。 - **安装过程**:按照安装向导提示完成安装。 - **配置服务器信息**:在Spark客户端中输入Openfire服务器的地址和端口信息。 -...
你还可以使用第三方工具进行性能监控和报警设置。 10. **扩展功能**:Openfire支持插件扩展,如会议、监控、报表等,可以根据需求安装和配置。这些插件可以在Openfire管理界面的"插件管理"部分找到并安装。 通过...
- **解压文件**:使用`unrar`命令或者第三方工具解压"Openfire-linux-4.6.4.rar"。 - **启动安装**:进入解压后的目录,执行安装脚本,例如:`./install.sh`。 - **配置服务**:安装过程中,会提示配置数据库连接...
- **插件生态**:Openfire拥有丰富的插件库,如Adium、Pidgin等第三方客户端都支持与Openfire的连接。 5. **维护与优化** - **日志管理**:Openfire会记录系统运行日志,便于排查问题和性能监控。 - **性能调优*...
官方插件库和第三方社区提供了丰富的插件资源。 8. 安全性和维护:为了保障Openfire的安全,你需要定期更新到最新版本,防止已知漏洞被利用。同时,监控服务器性能,确保其稳定运行,并定期备份数据库,以防数据...
在使用android openfire 作为IM服务的时候 利用第三方账号 如果第三方账号中含有@符号的话 登录会报错 可以利用这个代码进行escapeNode 一下 然后传递到openfire 在查询数据库之前进行解析 这是在openfire的源代码中...
首先,从Openfire官网或者第三方资源下载适用于Mac的Openfire安装包,通常会是名为"Openfire.pkg"的文件。这个文件是一个标准的Mac OS X安装程序,用于引导用户完成整个安装过程。 安装步骤如下: 1. **双击安装包...
此外,还有第三方SDK,如Smack,可以简化客户端开发。 10. **移动设备支持**:Openfire与各种XMPP兼容的移动客户端良好协作,如Gajim、Spark、iOS上的XMPPFramework等,使得员工可以在任何地方通过移动设备接入企业...
插件可以扩展Openfire的功能,如集成第三方服务、提供企业应用集成接口等。 10. **API与SDK**:Openfire提供了丰富的API供开发者使用,包括Java API、REST API等,使得开发者能够轻松地与其他系统集成或者开发...
在Android应用开发中,使用第三方库ASMACK实现与Openfire的XMPP协议连接。ASMACK是一个针对Android优化的Smack库,实现了XMPP协议。 1. 引入ASMACK库:在Android项目中添加ASMACK的依赖,通常通过Gradle或Maven导入...
Openfire的监控通常通过管理控制台或第三方监控工具完成,管理员需要了解如何监控服务器的性能、活动用户数和系统日志。同时,定期备份和更新Openfire到最新版本也是维护工作的重要组成部分。 7. 故障排除 当...
6. **语音与视频通话**:这涉及到更复杂的实时音视频编解码技术,可以使用WebRTC或其他第三方库实现。 7. **通知与推送**:为了确保用户及时收到消息,需要集成Google的Firebase Cloud Messaging (FCM) 或者其他推...
- **集成第三方应用**:Openfire可以通过API与其他应用程序集成,如CRM系统、办公软件等。 5. **开发与扩展**: - **API文档**:Openfire提供详尽的开发者文档,帮助开发者理解和利用其API进行二次开发。 - **...
在iOS开发中,我们通常会使用第三方库如“XMPPFramework”或“Swift XMPP”来简化工作。这些库封装了XMPP协议的复杂性,提供了易于使用的API。在这个“MyXmpp”项目中,可能包含了以下关键组件: 1. **连接管理**:...
- **隐私威胁**:第三方可能监听用户的通信内容,侵犯用户的隐私权。 - **病毒威胁**:恶意软件可通过即时通讯渠道传播,损害用户的设备安全。 #### 三、企业内部即时通讯系统的必要性 为了避免上述提到的安全威胁...