最近公司用到android的即時通訊功能。折騰了一天,和同僚決定用xmpp協定openfire來作為IM伺服器。
安裝,此處省略若幹字。由于安裝比較簡單,網上有很多的安裝資料,找到一篇文章就可以正确的安裝。這裡不多費口舌了。
安裝完成之後,發現需要在openfire裡面注冊新的賬号才行。直接瞪眼!這不苦逼了,原來的使用者非得要再重新注冊一個賬号才行?這麼做肯定不行。想想,既然這個工具已經如此受到歡迎,不可能有這麼打一個卻低啊。于是就在網上找了一下,還真有呢!!!再次灰常感謝那些編寫openfire的工程師門。
好了,廢話不多說,直接些方法。
綁定第三方賬号:
擴充綁定第三方的賬号主要有兩種方法,網上有很多介紹的。再次做一個備份。雖然是兩種方法,但是感覺都差不過的,都是通過修改一個openfire系統表裡的一個叫做ofProperty的表來實作功能的。(再次小編批注一下,我用的是将openfire的系統安裝到mysql一個第三方的資料庫中,沒有使用openfire自帶的資料庫。這樣的話,即時由于修改錯了參數的話,直接在資料中,将資料的參數update修改回來,也很友善。 要不然,找不到openfire自帶的資料庫,萬一修改錯了參數,就進不去配置界面。那時候,就叫天天不應,叫地地不靈了。 估計也有能夠找到openfire自帶資料在哪裡的,不過小編太懶了,沒去關注這方面的資料。如果有知道的朋友們,可以直接給我留言,分享進步嘛~ 我得意滴笑!)
方法一: 通過配置界面,進入到控制前台。 小編是将openfire服務安裝到自己的機器上,是以位址是: http://localhsot:9090 如果安裝到其他的地方,将localhost換成IP就好了進入到如下界面:

不用說,你懂得,使用者密碼全都放上,沒啥也,進入到管理界面。
進去之後, 左上角那去找 伺服器->伺服器管理器->系統屬性 進入到系統屬性編輯界面。如下圖:
然後,拖到最下面,去找添加屬性的配置
屬性名稱: jdbcProvider.driver 值:com.mysql.jdbc.Driver
屬性名稱: jdbcProvider.connectionString 值:jdbc:mysql://localhost/同步的資料庫名?user=資料庫使用者名&password=資料庫密碼
屬性名稱: admin.authorizedJIDs 值:[email protected] //admin是openfire的管理者賬号,@amy-tang是安裝openfire時填寫的域名,也可以寫成@192.168.0.1即openfire安裝伺服器電腦的IP位址
屬性名稱: jdbcAuthProvider.passwordSQL 值:SELECT Password FROM Profiles WHERE NickName=? //Profiles是使用者表,NickName使用者名,Password登入碼。
屬性名稱: jdbcAuthProvider.passwordType 值:md5 //選擇的加密方式,可以選擇MD5,plain(明文)或者其他隻要和現有使用者表加密方式一樣就行
屬性名稱: jdbcUserProvider.loadUserSQL 值:SELECT NickName,Email FROM Profiles WHERE NickName=?
屬性名稱: jdbcUserProvider.userCountSQL 值:SELECT COUNT(*) FROM Profiles
屬性名稱: jdbcUserProvider.allUsersSQL 值:SELECT NickName FROM Profiles
屬性名稱: jdbcUserProvider.usernameField 值:NickName
屬性名稱: jdbcUserProvider.nameField 值:NickName
屬性名稱: jdbcUserProvider.emailField 值:Email
然後再更新兩個
屬性名稱:provider.user.className 值:org.jivesoftware.openfire.user.JDBCUserProvider
屬性名稱:provider.auth.className 值:org.jivesoftware.openfire.auth.JDBCAuthProvider
這樣就好了, 第二種方法就是直接取背景修改sql語句将上面的資料用sql寫入到資料庫中去。 insert 語句在這裡就省去了~ 自己寫就好了。直接用工具, 複制+粘貼也能搞定,沒必要非得寫sql語句的。
如果你的資料庫中的密碼用的是标準md5加密,那麼 jdbcAuthProvider.passwordType 設定為md5就好了,如果明文(情況很少,并不是沒有)那就是plain了。
(此處感謝 尋夢 在她的空間部落格裡面找到的這些資料,對我有很大的幫助。 http://blog.sina.com.cn/u/1785328994)
好了,修改完了,重新開機一下openfire重新進入openfire的管理界面。
下面是見證奇迹的時刻:
到登入态,輸入使用者名,密碼 進入。此時,會有兩個情況:
情況一: 能進去,嘛事沒有! 此人必有老天幫助。這個時候你可以 出門鞭炮眼花一起放,敲鑼打鼓慶祝吧,因為你成功了。
情況二: 苦逼小編我就是這樣的情況,進不去,就是說我使用者名密碼不對。(登入背景的使用者名密碼 是 你在上面設定屬性的時候的 屬性名稱: admin.authorizedJIDs 值:[email protected] 的賬号,其他的,就是錯的了,哪怕你是密碼正确呢,因為你沒有這個權利啊。)我用的是md5 編碼啊,怎麼會不對呢。好吧,被鄙視了,确定問題,尋找解決辦法。
我用的方法就是,在背景,将使用者名密碼全都貼出來, 然後修改秘密編碼格式為明文。我将機密後的密碼當做密碼登陸。 修改完了之後,重新開機openfire,激動人心的時刻到了,使用者名密碼,能進去了!!!! 我去, 真的被鄙視了,丫的!!!!!
修改密碼編碼格式:
好了,被鄙視之後,就找到問題所在了。找到問題,解決問題。
在網上找一下,嘿,果然有相關資料。 但是内容總結出來就是一句話:修改源檔案(萬能得開源啊)
好吧,去官網下載下傳源檔案:
下載下傳下來之後,去找org.jivesoftware.openfire.auth這個包裡可以找到JDBCAuthProvider這個類,驗證密碼的函數就是authenticate。一進去,代碼還不少嘞。
/**
* $Revision: 1116 $
* $Date: 2005-03-10 20:18:08 -0300 (Thu, 10 Mar 2005) $
*
* Copyright (C) 2005-2008 Jive Software. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance 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 agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.openfire.auth;
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.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 to authenticate users against any database
* that you can connect to with JDBC. It can be used along with the
* {@link HybridAuthProvider hybrid} auth provider, so that you can also have
* XMPP-only users that won't pollute your external data.<p>
*
* To enable this provider, set the following in 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 FROM user_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 SET password=? WHERE username=?</tt></li>
* </ul>
*
* In order to use the configured JDBC connection provider do not use a JDBC
* connection string, set the following property
*
* <ul>
* <li><tt>jdbcAuthProvider.useConnectionProvider = true</tt></li>
* </ul>
*
* The passwordType setting tells Openfire how the password is stored. Setting the value
* is optional (when not set, it defaults to "plain"). The valid values are:<ul>
* <li>{@link PasswordType#plain plain}
* <li>{@link PasswordType#md5 md5}
* <li>{@link PasswordType#sha1 sha1}
* <li>{@link PasswordType#sha256 sha256}
* <li>{@link PasswordType#sha512 sha512}
* </ul>
*
* @author David Snopek
*/
public class JDBCAuthProvider implements AuthProvider {
private static final Logger Log = LoggerFactory.getLogger(JDBCAuthProvider.class);
private String connectionString;
private String passwordSQL;
private String setPasswordSQL;
private PasswordType passwordType;
private boolean allowUpdate;
private boolean useConnectionProvider;
/**
* Constructs a new JDBC authentication provider.
*/
public JDBCAuthProvider() {
// 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");
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("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, 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();
}
// If the user's password doesn't match the password passed in, authentication
// should fail.
if (passwordType == PasswordType.md5) {
password = StringUtils.hash(password, "MD5");
}
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 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
{
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();
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;
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, String password) 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) {
password = StringUtils.hash(password, "MD5");
}
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 plain text.
*/
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; 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("JDBCAuthProvider: Automatically creating new user account for " + username);
UserManager.getUserProvider().createUser(username, StringUtils.randomString(8),
null, null);
}
catch (UserAlreadyExistsException uaee) {
// Ignore.
}
}
}
}
好吧,好多,但是代碼看起來很是很不錯的,能看懂。下面就修改了, 都說在源代碼上修改時屠夫的做法,太暴力了,咱也君子一下,新添加一個類,繼承 AuthProvider 這個類 寫一個新的類就好了。
小編我比較懶,就在三個地方,添加了點代碼。
在 authenticate方法中,在
if (passwordType == PasswordType.md5) {
password = StringUtils.hash(password, "MD5");
}
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 (passwordType == PasswordType.md5) {
password = StringUtils.hash(password, "MD5");
}
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");
}else if(passwordType == PasswordType.demo){
password = demoPassword(password);
}
然後,自己寫一個 demoPassword的密碼編碼格式就好了。最後,還要再 public enum PasswordType {……} 枚舉裡面添加你自己寫的那個demo就好了。(果真懶到家了) 完成之後,生成class,(應該有兩個,一個是這個編碼class 還有一個是type枚舉的),然後,将class放到安裝目錄下的Openfire\lib\openfire.jar包中對應的位置就好了。添加的時候,也費率一點勁,想偷懶,結果消耗時間了。用要壓軟體死活就是添加不進去,沒辦法,安裝了一個WinRAR來添加的,再次被鄙視,囧啊~!!!!
将寫的java生成jar包,然後放到openfire.jar裡面,記錄一下路徑。然後,在後天資料庫中,修改一下 ‘provider.auth.className’ 屬性對應的值。 我上面的類名為 ZYJDBCAuthProvider 路徑為 org.zysecondedeveloper.openfire.plugin 是以,我就将值修改為 org.zysecondedeveloper.openfire.plugin.ZYJDBCAuthProvider
然後,替換點安裝路徑下的openfire 重新開機openfire就好了
安裝完了之後,重新開機。 輸入使用者名密碼, 恩,不錯,這回能進去了。 小得意一下。
再釋出的時候遇到的問題一:
有的時候,自己弄的class檔案放到openfire.jar包中去。當替換掉安裝目錄下的時候,一啟動,就報錯,說是不支援,粗無内容如下:
Unsupported major.minor version 51.0 糾結了一小段時間,發現時javajdk版本不比對。 可以用eclipse 修改一下java的jdk 的編碼格式,解決方法參考:http://www.111cn.net/jsp/Jsp-Servlet/45158.htm 然後重新打成jar包,将class放到openfire.jar裡面就好了。
修改密碼這部分,感謝 hustpzb 的文章http://blog.csdn.net/hustpzb/article/details/7647801
和 hetylei的文章http://hetylei.iteye.com/blog/290519 對我的幫助。
希望這邊文章對大家有幫助。 QQ: 2281879713 有問題可以留言或者qq