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控制台,再用客户端登录试试!
如果你的密码采用了自定义的算法进行加密,那么上面的方法同样适用你。

转载于:https://blog.51cto.com/firefish/312464

使用django的用户帐号登录openfire相关推荐

  1. 微信小程序:小程序内用户帐号登录规范调整和优化建议

    昨天晚上大概九点多,公众平台安全助手突然发来一条推送 <小程序内用户帐号登录规范调整通知> 粗略的看一眼推送,还以为自己的小程序被发警告信,惊吓出一身冷汗,迅速打开推送文章,细度一番才安下 ...

  2. mysql 同一帐号多次登录_freeradius2.1.3 防止用户帐号重复登录

    freeradius2.1.3 防止用户帐号重复登录 一.修改 etc/raddb/sites-enabled 目录中的default 及inner-tunnel 这两个文件中的 #  Session ...

  3. 登录用友显示java已被阻止_解决Spring Security 用户帐号已被锁定问题

    1.问题描述 主要就是org.springframework.security.authentication.LockedException: 用户帐号已被锁定这个异常,完整异常如下: [2020-0 ...

  4. vsftpd虚拟用户帐号

    vsftpd虚拟用户帐号的设置步骤:   1.建立虚拟用户口令库文件   2.生成vsftpd的认证文件   3.建立虚拟用户所需的PAM配置文件   4.建立虚拟用户所要访问的目录并设置相应权限   ...

  5. 在AD中批量添加多个用户帐号

    问题: 如何批量的创建帐号? 解决方案: 在利用CSVDE.EXE或LDIFDE.EXE来批量创建用户帐号 实验环境: Windows 2003 如果我们要想批量的创建帐号的话,我们可以首先利用文字编 ...

  6. 使用 OpenLDAP 集中管理用户帐号

    关键字: OpenLDAP ReiserFS SCTP nmon 正则表达式 使用轻量级目录访问协议(LDAP)构建集中的身份验证系统可以减少管理成本,增强安全性,避免数据复制的问题,并提高数据的一致 ...

  7. 在 phpMyAdmin 里添加新用户帐号

    为了数据库的安全性,尽量不用 root 来连接网站的数据库,以前用 cPanel 面板时,这个不是问题,添加帐号很简单,现在 VPS 不提供任何面板,只能用phpmyadmin 来创建新用户帐号了,这 ...

  8. 有没有计算机用户号,刚做的系统怎么有账户-用户帐号系统设计的原则有哪些?...

    用户帐号系统设计的原则有哪些? http://developer.okta.com 直接用人家的SDK 啊哈 刚买的电脑创建了一个微软账户,我的微软账户需要验证身份,但我收不到验证码怎么办,现在啥也干 ...

  9. linux定时情况root mail,Linux_Linux系统下mail命令使用,我经常用root帐号登录RHEL5,在 - phpStudy...

    Linux系统下mail命令使用 我经常用root帐号登录RHEL5,在终端经常出现: You have new mail in /var/spool/mail/root 这肯定是系统给root超级管 ...

最新文章

  1. Android 对BaseAdapter做优化处理
  2. 行家来信 | 功能安全会成为自动驾驶的紧箍咒吗?
  3. vim windows版本_大概是篇Vim入门教程(1): 基本的一些东西
  4. 百度交易中台之账房系统架构浅析
  5. python控制摄像头拍照_python+opencv+pyqt5控制摄像头在Qlabel上显示
  6. PPC系统常见启动项(StartUp)解说
  7. LeetCode 126. 单词接龙 II(图的BFS)
  8. linux libbz2.so.1,libbz2.so.1.0: cannot open shared object file: No such file or directory
  9. 对不起,你以前学的 ElasticSearch 都是垃圾!
  10. 改造Python中文拼音扩展库pypinyin补充自定义声母全过程
  11. matlab 常用 api(七)—— 常见判断(返回 logical)
  12. python dicom 测量_python对DICOM图像的读取方法详解
  13. 阶段3 2.Spring_08.面向切面编程 AOP_5 切入点表达式的写法
  14. 解决TextView中文英文数字混排导致的自动换行问题
  15. springboot word excel ppt 图片aspose 转换PDF 在线预览
  16. 2040: [蓝桥杯2022初赛] 砍竹子(优先队列)
  17. 字节跳动高工面试:java高级程序员面试宝典蔡羽豆瓣
  18. 如何将Python打包后的exe还原成.py?
  19. [Mitchell 机器学习读书笔记]——人工神经网络
  20. Python3字典合并的几种方法

热门文章

  1. [转载]ArcMap中如何自制符号库(二)
  2. mysql更新一个表里的字段等于另一个表某字段的值
  3. Android 系统工具类SystemUtils
  4. android SDK Manager 代理服务器设置
  5. 输出stage.displayState等于null原因
  6. 基于Linux系统中进程调度分析
  7. 基于 REST 的 Web 服务:基础
  8. OpenCV Mat 简介
  9. Redis命令——Keys相关
  10. MAC下PHP7.1.23安装intl3.0.0