【Openfire】网页版的用户注册、登录、修改密码

时间:2020-12-08 19:53:52

Openfire客户端的开发是借助Smack提供的方法。Smack可以从官网下载。如果是Openfire3.7.1的版本,建议使用Smack3.2.2与其匹配。官网地址:http://www.igniterealtime.org/downloadServlet?filename=smack/smack_3_2_2.zip

下载之后解压,将里面的4个jar复制到网络工程WEB-INF下的lib文件夹,这样,即可以使用smack的api进行开发。

【Openfire】网页版的用户注册、登录、修改密码

目标是做出如下样子的工程,在网页版提供用户注册、登录和密码修改的功能。

【Openfire】网页版的用户注册、登录、修改密码

网络工程的目录结构如下:

【Openfire】网页版的用户注册、登录、修改密码

web.xml,没什么好说的,仅仅是一些简单的struts2配置,没有其它任何的内容。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
	version="2.4">
	<filter>
		<filter-name>struts2</filter-name>
		<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>struts2</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
</web-app>  

DB.java这是在《【Java】利用单例模式、可变参数优化Java操作Mysql数据库、JDBC代码的写作》( 点击打开链接)我已经详细说明过的工具类,这里不再多说了。

package tool;

import java.sql.*;  
import java.util.*;  
  
public class DB {  
    // 一、单例初始化连接  
    private Connection con;  
  
    // 以下代码,保证该类只能有一个实例  
    private DB() {  
        try {  
            Class.forName("com.mysql.jdbc.Driver");  
            // 其中test是我们要链接的数据库,user是数据库用户名,password是数据库密码。  
            // 3306是mysql的端口号,一般是这个  
            // 后面那串长长的参数是为了防止乱码,免去每次都需要在任何语句都加入一条SET NAMES UTF8  
            String url = "jdbc:mysql://localhost:3306/openfire?useUnicode=true&characterEncoding=utf8&useOldAliasMetadataBehavior=true";  
            String user = "root";  
            String password = "root";  
            con = DriverManager.getConnection(url, user, password);  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    } // 私有无参构造方法  
  
    // 在自己内部定义自己的一个实例,只供内部调用  
    private static DB db = null;  
  
    // 这个类必须自动向整个系统提供这个实例对象  
    // 这里提供了一个供外部访问本class的静态方法,可以直接访问  
    public static DB getInstance() {  
        if (db == null) {  
        	db = new DB();  
        }  
        return db;  
    }  
  
    // 二、查询  
    // 使用SQL查询,查询的结果是一个结果集(视图)  
    public List<Object[]> getBySql(String sql) {  
        List<Object[]> result_list = new ArrayList<Object[]>();  
        try {  
            PreparedStatement ps = con.prepareStatement(sql);  
            ResultSet rs = ps.executeQuery();  
            while (rs.next()) {  
                ResultSetMetaData md = rs.getMetaData();  
                int columnCount = md.getColumnCount();  
                Object[] row_data_set = new Object[columnCount];  
                for (int i = 1; i <= columnCount; i++) {  
                    row_data_set[i - 1] = rs.getObject(i);  
                }  
                result_list.add(row_data_set);  
            }  
            return result_list;  
        } catch (SQLException e) {  
            e.printStackTrace();  
            return null;  
        }  
    }  
  
    // 查询sql语句带参数的情况  
    public List<Object[]> getBySql(String sql, Object[] param) {  
        List<Object[]> result_list = new ArrayList<Object[]>();  
        try {  
            PreparedStatement ps = con.prepareStatement(sql);  
            for (int i = 0; i < param.length; i++) {  
                ps.setObject(i + 1, param[i]);  
            }  
            ResultSet rs = ps.executeQuery();  
            while (rs.next()) {  
                ResultSetMetaData md = rs.getMetaData();  
                int columnCount = md.getColumnCount();  
                Object[] row_data_set = new Object[columnCount];  
                for (int i = 1; i <= columnCount; i++) {  
                    row_data_set[i - 1] = rs.getObject(i);  
                }  
                result_list.add(row_data_set);  
            }  
            return result_list;  
        } catch (SQLException e) {  
            e.printStackTrace();  
            return null;  
        }  
    }  
  
    // 使用SQL查询,查询的结果是唯一  
    public Object getBySql_result_unique(String sql) {  
        try {  
            PreparedStatement ps = con.prepareStatement(sql);  
            ResultSet rs = ps.executeQuery();  
            rs.next();  
            return rs.getObject(1);  
        } catch (SQLException e) {  
            e.printStackTrace();  
            return null;  
        }  
    }  
  
    // 查询sql语句带参数的情况  
    public Object getBySql_result_unique(String sql, Object[] param) {  
        try {  
            PreparedStatement ps = con.prepareStatement(sql);  
            for (int i = 0; i < param.length; i++) {  
                ps.setObject(i + 1, param[i]);  
            }  
            ResultSet rs = ps.executeQuery();  
            rs.next();  
            return rs.getObject(1);  
        } catch (SQLException e) {  
            e.printStackTrace();  
            return null;  
        }  
    }  
  
    // 三、增删改  
    // insert、update、delete等修改数据库的语句  
    public void setBySql(String sql) {  
        try {  
            PreparedStatement ps = con.prepareStatement(sql);  
            ps.executeUpdate();  
        } catch (SQLException e) {  
            e.printStackTrace();  
        }  
    }  
  
    // sql语句带参数的情况  
    public void setBySql(String sql, Object[] param) {  
        try {  
            PreparedStatement ps = con.prepareStatement(sql);  
            for (int i = 0; i < param.length; i++) {  
                ps.setObject(i + 1, param[i]);  
            }  
            ps.executeUpdate();  
        } catch (SQLException e) {  
            e.printStackTrace();  
        }  
    }  
  
    // 析构函数,中断数据库的连接  
    protected void finalize() throws Exception {  
        if (!con.isClosed() || con != null) {  
            con.close();  
        }  
    }  
  
  
}  

index.jsp也就是如上的效果截图,布局很简单,就三个表单指向相应action,<%@ page isELIgnored="false"%>是为了保证EL表达式,能够在Tomcat5.5使用。

<%@ page language="java" contentType="text/html; charset=utf-8"
	pageEncoding="utf-8"%>
<%@ page isELIgnored="false"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>index</title>
</head>
<body>
	${msg}
	<hr>
	<p>
		用户注册<br>
		<form method="post" action="register">
			用户名:<input type="text" name="username" /><br>
			密码:<input type="password" name="password" /><br>
			<input type="submit" value="注册" />
		</form>
	</p>
	<hr>
	<p>
		用户登录<br>
		<form method="post" action="login">
			用户名:<input type="text" name="username" /><br>
			密码:<input type="password" name="password" /><br>
			<input type="submit" value="登录" />
		</form>
	</p>
	<hr>
	<p>
		修改密码<br>
		<form method="post" action="modify_password">
			用户名:<input type="text" name="username" /><br>
			旧密码:<input type="password" name="password" /><br>
			新密码:<input type="password" name="newpassword" /><br>
			<input type="submit" value="修改" />
		</form>
	</p>
</body>
</html>

chat.jsp是用户成功登录,所跳转的页面,很简单。

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>聊天</title>
</head>
<body>
	欢迎,${sessionScope.username},<a href="logout">登出</a>
</body>
</html>

核心关键在于两个,一个是从Openfire源代码中拷贝过来的Blowfish.java,这个文件的详细说明请看《【Openfire】验证用户输入密码是否正确》( 点击打开链接)。

这个文件很长,修改的地方,主要是里面关于org.slf4j记录日志的信息的注释掉,然后是对其42-53行的构造方法进行修改,这样以后用到就不用每次都要写代码,在数据库中查询ofproperty中passwordKey所对应的值。修改之后如下:

public Blowfish() {// 改过
	// hash down the password to a 160bit key
	DB db = DB.getInstance();
	String password = (String) db
			.getBySql_result_unique("select propValue from ofproperty where name='passwordKey'");
	MessageDigest digest = null;
	try {
		digest = MessageDigest.getInstance("SHA1");
		digest.update(password.getBytes());
	} catch (Exception e) {
		// Log.error(e.getMessage(), e);
	}

	// setup the encryptor (use a dummy IV)
	m_bfish = new BlowfishCBC(digest.digest(), 0);
	digest.reset();
}

最后,所有核心方法都在test.user这个类中。对照struts.xml所配的action:

<?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE struts PUBLIC    
      "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"    
      "http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
	<package name="test" extends="struts-default">
		<action name="register" class="test.user" method="register">
			<result name="success" type="redirect">/index.jsp</result>
		</action>
	 	<action name="login" class="test.user" method="login">
			<result name="success" type="redirect">/chat.jsp</result>
			<result name="input" type="redirect">/index.jsp</result>
		</action>
		<action name="logout" class="test.user" method="logout">
			<result name="success" type="redirect">/index.jsp</result>
		</action>
		<action name="modify_password" class="test.user" method="modify_password">
			<result name="success" type="redirect">/index.jsp</result>
		</action>
	</package>
</struts>   
这个user.java的代码如下:

package test;

import java.util.Map;

import org.jivesoftware.smack.AccountManager;
import org.jivesoftware.smack.Connection;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;

import tool.Blowfish;
import tool.DB;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;

public class user extends ActionSupport {
	private static final long serialVersionUID = 5570391705428296402L;
	private String username;
	private String password;
	private String newpassword;
	private String msg;

	// 判断输入的密码是否与数据库密码一致
	private boolean checkPassword(String username, String password) {
		Blowfish blowfish = new Blowfish();
		DB db = DB.getInstance();
		String db_password = (String) db.getBySql_result_unique(
				"select encryptedPassword from ofuser where username=?",
				new Object[] { username });
		if (blowfish.decryptString(db_password).equals(password)) {
			return true;
		} else {
			return false;
		}
	}

	// 用户注册
	public String register() throws XMPPException {
		DB db = DB.getInstance();
		if (username.length() == 0) {
			msg = "用户名不得为空!";
			return SUCCESS;
		}
		if (db.getBySql("select * from ofuser where username=?",
				new Object[] { username }).size() > 0) {
			msg = "该用户名已经存在!";
		} else {
			ConnectionConfiguration config = new ConnectionConfiguration(
					"127.0.0.1", 5222);
			Connection connection = new XMPPConnection(config);
			connection.connect();
			AccountManager amgr = connection.getAccountManager();
			amgr.createAccount(username, password);// 会自动使用Openfire的Blowfish编码加密密码。
			msg = "注册成功!";
			System.out.println("新用户注册:" + username + ",密码:" + password);
			connection.disconnect();
		}
		return SUCCESS;
	}

	// 用户登录
	public String login() throws XMPPException {
		if (checkPassword(username, password)) {
			msg = "登录成功!";
			ConnectionConfiguration config = new ConnectionConfiguration(
					"127.0.0.1", 5222);
			Connection connection = new XMPPConnection(config);
			connection.connect();
			connection.login(username, password);
			Map session = ActionContext.getContext().getSession();
			session.put("username", username);
			session.put("connection", connection);
			return SUCCESS;
		} else {
			msg = "密码错误!";
			return INPUT;
		}
	}

	// 用户登出
	public String logout() {
		Map session = ActionContext.getContext().getSession();
		Connection connection = (Connection) session.get("connection");
		connection.disconnect();
		session.clear();
		return SUCCESS;
	}

	// 修改密码
	public String modify_password() throws XMPPException {
		if (checkPassword(username, password)) {
			ConnectionConfiguration config = new ConnectionConfiguration(
					"127.0.0.1", 5222);
			Connection connection = new XMPPConnection(config);
			connection.connect();
			connection.login(username, password);// 登录之后才能修改密码
			connection.getAccountManager().changePassword(newpassword);
			System.out.println("用户:" + username + ",修改密码从:" + password + ",变为:"
					+ newpassword);
			connection.disconnect();
			msg = "修改成功!";
		} else {
			msg = "密码错误!";
		}
		return SUCCESS;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public String getMsg() {
		return msg;
	}

	public void setMsg(String msg) {
		this.msg = msg;
	}

	public String getNewpassword() {
		return newpassword;
	}

	public void setNewpassword(String newpassword) {
		this.newpassword = newpassword;
	}

}

注册、登录、修改密码本来没有什么好说的,因为smack已经提供了一定量的api。

但是,由于smack并没有提供判断用户所输入密码是否正确的方法,只能自己手动到数据库,再配合Blowfish算法查询。如果密码是不正确的,直接利用connect.login方法的话,会直接报错,导致这个网络工程崩溃,try和catch也是没用的。所以在connect.login之前,需要自己查询用户输入的密码是否正确。

再有一点是,如果用户登录正确,那么这个connect对象就应该压入session,跟着他所登录的用户名一起走,为后续的聊天和加好友操作的提供依据,也就是以后的所有动作,最简单就是上面代码的登录,都要使用这个connection来操作,而不是自己再new一个。