本章介绍openfire中的用户注册,分成两步介绍。
客户端首先发起一个xmpp请求,如下所示
<iq id="#id" to="#serverName" type="get">
<query xmlns="jabber:iq:register"></query>
</iq>
这里,#id和#serverName都是在上一章中创建的,该请求最终会到达ClientStanzaHandler的process函数,该函数从mina框架中的IoSession取出处理器ClientStanzaHandler,该ClientStanzaHandler在第一次创建IoSession时构造的,第五章中分析了,然后调用其process方法,为了方便分析,这里贴出关键代码,
public void process(String stanza, XMPPPacketReader reader) throws Exception {
boolean initialStream = stanza.startsWith("<stream:stream") || stanza.startsWith("<flash:stream");
if (!sessionCreated || initialStream) {
...
}
...
Element doc = reader.read(new StringReader(stanza)).getRootElement();
...
String tag = doc.getName();
process(doc);
}
这里,由于已经创建了LocalClientSession,这里不会进入到第一个if语句里,然后继续调用proc函数,
private void process(Element doc) throws UnauthorizedException {
String tag = doc.getName();
if ("message".equals(tag)) {
...
}
else if ("presence".equals(tag)) {
...
}
else if ("iq".equals(tag)) {
IQ packet;
try {
packet = getIQ(doc);
}
catch (IllegalArgumentException e) {
}
...
processIQ(packet);
}
else {
}
}
getIQ定义在StanzaHandler中,
private IQ getIQ(Element doc) {
Element query = doc.element("query");
if (query != null && "jabber:iq:roster".equals(query.getNamespaceURI())) {
return new Roster(doc);
}
else {
return new IQ(doc, !validateJIDs());
}
}
根据XMPP协议,这里就是构造一个IQ,validateJIDs表是是否需要对to或者from的JID进行验证,这里默认返回true,表示需要对其进行验证,并将其添加进缓存中。
回到process函数中,接下来调用processIQ进行处理,定义在ClientStanzaHandler中,
protected void processIQ(IQ packet) throws UnauthorizedException {
packet.setFrom(session.getAddress());
super.processIQ(packet);
}
这里先要在packet中设置from变量,这里个人认为是为了防止后面取不出from的bug,不管它。其父类的函数如下
protected void processIQ(IQ packet) throws UnauthorizedException {
router.route(packet);
session.incrementClientPacketCount();
}
这里的router是在XMPPServer的start函数中构造的PacketRouterImpl,其route函数如下
public void route(IQ packet) {
iqRouter.route(packet);
}
iqRouter也是在XMPPServer中构造的IQRouter,其route函数如下
public void route(IQ packet) {
JID sender = packet.getFrom();
ClientSession session = sessionManager.getSession(sender);
Element childElement = packet.getChildElement(); // may be null
try {
InterceptorManager.getInstance().invokeInterceptors(packet, session, true, false);
JID to = packet.getTo();
if (session != null && to != null && session.getStatus() == Session.STATUS_CONNECTED &&
!serverName.equals(to.toString())) {
...
}
else if (session == null || session.getStatus() == Session.STATUS_AUTHENTICATED || (
childElement != null && isLocalServer(to) && (
"jabber:iq:auth".equals(childElement.getNamespaceURI()) ||
"jabber:iq:register".equals(childElement.getNamespaceURI()) ||
"urn:ietf:params:xml:ns:xmpp-bind".equals(childElement.getNamespaceURI())))) {
handle(packet);
} else if (packet.getType() == IQ.Type.get || packet.getType() == IQ.Type.set) {
...
}
InterceptorManager.getInstance().invokeInterceptors(packet, session, true, true);
}
catch (PacketRejectedException e) {
}
}
这里会通过拦截器,拦截器和业务代码没有太大关系,本章暂时不分析它,后面有时间再细看。因为刚刚的XMPP消息的命名空间为jabber:iq:register,因此进入第二个if语句,调用handle进行处理,继续看
private void handle(IQ packet) {
JID recipientJID = packet.getTo();
...
try {
...
if (isLocalServer(recipientJID)) {
Element childElement = packet.getChildElement();
String namespace = null;
if (childElement != null) {
namespace = childElement.getNamespaceURI();
}
if (namespace == null) {
...
}
else {
...
IQHandler handler = getHandler(namespace);
if (handler == null) {
...
}
else {
handler.process(packet);
}
}
}
else {
...
}
}
catch (Exception e) {
}
}
这里首先判断客户端请求的XMPP消息是否发往本服务器,如果是,就从该消息中取出namespace(jabber:iq:register),然后调用getHandler根据namespace取出相应的事件处理器。
private IQHandler getHandler(String namespace) {
IQHandler handler = namespace2Handlers.get(namespace);
if (handler == null) {
for (IQHandler handlerCandidate : iqHandlers) {
IQHandlerInfo handlerInfo = handlerCandidate.getInfo();
if (handlerInfo != null && namespace.equalsIgnoreCase(handlerInfo.getNamespace())) {
handler = handlerCandidate;
namespace2Handlers.put(namespace, handler);
break;
}
}
}
return handler;
}
这里就是从iq消息的Handler列表iqHandlers中依次取出Handler,然后比较namespace,如果相等就添加到namespace2Handlers哈希表中,然后返回。哪一个Handler对应到刚才的XMPP消息的命名空间jabber:iq:register呢?IQRegisterHandler!看看其构造函数,
public IQRegisterHandler() {
super("XMPP Registration Handler");
info = new IQHandlerInfo("query", "jabber:iq:register");
}
它是在XMPP中loadModules函数中实例化的,然后在IQRouter的initialize函数中被添加进iqHandlers中的,
public void initialize(XMPPServer server) {
...
iqHandlers.addAll(server.getIQHandlers());
...
}
返回到handle函数中,取出IQRegisterHandler后,调用其process函数继续处理,该函数定义在其父类IQHandler中,
public void process(Packet packet) throws PacketException {
IQ iq = (IQ) packet;
try {
IQ reply = handleIQ(iq);
if (reply != null) {
deliverer.deliver(reply);
}
}
catch (org.jivesoftware.openfire.auth.UnauthorizedException e) {
}
catch (Exception e) {
}
}
首先将消息包Packet强制转化为IQ消息包,然后调用handleIQ进行处理,然后调用deliver函数发送结果。handleIQ函数定义在IQRegisterHandler中,
public IQ handleIQ(IQ packet) throws PacketException, UnauthorizedException {
ClientSession session = sessionManager.getSession(packet.getFrom());
IQ reply = null;
if (IQ.Type.get.equals(packet.getType())) {
if (!registrationEnabled) {
...
}
else {
reply = IQ.createResultIQ(packet);
if (session.getStatus() == Session.STATUS_AUTHENTICATED) {
...
}
else {
reply.setTo((JID) null);
reply.setChildElement(probeResult.createCopy());
}
}
}
else if (IQ.Type.set.equals(packet.getType())) {
...
}
if (reply != null) {
session.process(reply);
}
return null;
}
这里本次客户端的请求类型为get,然后调用createResultIQ构造返回信息,并添加一些基本的信息,
public static IQ createResultIQ(IQ iq) {
IQ result = new IQ(Type.result, iq.getID());
result.setFrom(iq.getTo());
result.setTo(iq.getFrom());
return result;
}
因此,这里就是设置XMPP协议中type、to和from属性。
回到handleIQ函数,接下来先更改reply的to属性为null,然后设置其子元素。probeResult在IQRegisterHandler模块的initialize函数中被设置,
public void initialize(XMPPServer server) {
super.initialize(server);
userManager = server.getUserManager();
rosterManager = server.getRosterManager();
if (probeResult == null) {
probeResult = DocumentHelper.createElement(QName.get("query", "jabber:iq:register"));
probeResult.addElement("username");
probeResult.addElement("password");
probeResult.addElement("email");
probeResult.addElement("name");
final DataForm registrationForm = new DataForm(DataForm.Type.form);
registrationForm.setTitle("XMPP Client Registration");
registrationForm.addInstruction("Please provide the following information");
final FormField fieldForm = registrationForm.addField();
fieldForm.setVariable("FORM_TYPE");
fieldForm.setType(FormField.Type.hidden);
fieldForm.addValue("jabber:iq:register");
final FormField fieldUser = registrationForm.addField();
fieldUser.setVariable("username");
fieldUser.setType(FormField.Type.text_single);
fieldUser.setLabel("Username");
fieldUser.setRequired(true);
final FormField fieldName = registrationForm.addField();
fieldName.setVariable("name");
fieldName.setType(FormField.Type.text_single);
fieldName.setLabel("Full name");
if (UserManager.getUserProvider().isNameRequired()) {
fieldName.setRequired(true);
}
final FormField fieldMail = registrationForm.addField();
fieldMail.setVariable("email");
fieldMail.setType(FormField.Type.text_single);
fieldMail.setLabel("Email");
if (UserManager.getUserProvider().isEmailRequired()) {
fieldMail.setRequired(true);
}
final FormField fieldPwd = registrationForm.addField();
fieldPwd.setVariable("password");
fieldPwd.setType(FormField.Type.text_private);
fieldPwd.setLabel("Password");
fieldPwd.setRequired(true);
probeResult.add(registrationForm.getElement());
}
JiveGlobals.migrateProperty("register.inband");
JiveGlobals.migrateProperty("register.password");
registrationEnabled = JiveGlobals.getBooleanProperty("register.inband", true);
canChangePassword = JiveGlobals.getBooleanProperty("register.password", true);
}
这里就不详细分析了,总之就是构造一个Element,添加对应的元素。回到handleIQ函数中,最后调用LocalClientSession的process函数发送消息,这样最后客户端返回的结果如下,
<iq type="result" id="#id" from="#serverName">
<query xmlns="jabber:iq:register">
<username/><password/><email/><name/>
<x xmlns="jabber:x:data" type="form">
<title>XMPP Client Registration</title>
<instructions>Please provide the following information</instructions>
<field var="FORM_TYPE" type="hidden">
<value>jabber:iq:register</value>
</field>
<field var="username" type="text-single" label="Username">
<required/>
</field>
<field var="name" type="text-single" label="Full name"/>
<field var="email" type="text-single" label="Email"/>
<field var="password" type="text-private" label="Password">
<required/>
</field>
</x>
</query>
</iq>
因此这里就是返回给客户端服务器需要的信息,客户端需要根据这里得到的信息进行相应的填写,并提交给openfire服务器。
这里假设客户端根据刚刚服务器返回的信息,进行填写,然后提交给openfire服务器的消息如下所示,
<iq id="#id" to="#serverName" type="set">
<query xmlns="jabber:iq:register">
<username>123</username>
<email></email>
<name></name>
<password>456</password>
</query>
</iq>
参照上面第一个客户端请求的分析,这里最后会到达handleIQ,但是由于本次请求的type为set,因此这里重新贴一遍该函数,
public IQ handleIQ(IQ packet) throws PacketException, UnauthorizedException {
ClientSession session = sessionManager.getSession(packet.getFrom());
IQ reply = null;
if (IQ.Type.get.equals(packet.getType())) {
...
}
else if (IQ.Type.set.equals(packet.getType())) {
try {
Element iqElement = packet.getChildElement();
if (iqElement.element("remove") != null) {
...
}
else {
String username;
String password = null;
String email = null;
String name = null;
User newUser;
DataForm registrationForm;
FormField field;
Element formElement = iqElement.element("x");
if (formElement != null) {
...
}
else {
username = iqElement.elementText("username");
password = iqElement.elementText("password");
email = iqElement.elementText("email");
name = iqElement.elementText("name");
}
...
if (session.getStatus() == Session.STATUS_AUTHENTICATED) {
...
}
else {
if (!registrationEnabled) {
...
}
else if (password == null || password.trim().length() == 0) {
...
}
else {
newUser = userManager.createUser(username, password, name, email);
}
}
if (newUser != null && name != null && !name.equals(newUser.getName())) {
newUser.setName(name);
}
reply = IQ.createResultIQ(packet);
}
}
catch (Exception e) {
}
}
if (reply != null) {
session.process(reply);
}
return null;
}
这里省略了大部分代码,剩下一些关键代码,因此可以很容易看出这里就是调用UserManager的create函数,然后构造返回信息。
public User createUser(String username, String password, String name, String email)
throws UserAlreadyExistsException
{
...
User user = provider.createUser(username, password, name, email);
userCache.put(username, user);
Map<String,Object> params = Collections.emptyMap();
UserEventDispatcher.dispatchEvent(user, UserEventDispatcher.EventType.user_created, params);
return user;
}
provider默认为DefaultUserProvider,其createUser函数如下,
public User createUser(String username, String password, String name, String email)
throws UserAlreadyExistsException
{
try {
loadUser(username);
}
catch (UserNotFoundException unfe) {
...
Date now = new Date();
Connection con = null;
PreparedStatement pstmt = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(INSERT_USER);
pstmt.setString(1, username);
if (password == null) {
pstmt.setNull(2, Types.VARCHAR);
}
else {
pstmt.setString(2, password);
}
if (encryptedPassword == null) {
pstmt.setNull(3, Types.VARCHAR);
}
else {
pstmt.setString(3, encryptedPassword);
}
if (name == null || name.matches("\\s*")) {
pstmt.setNull(4, Types.VARCHAR);
}
else {
pstmt.setString(4, name);
}
if (email == null || email.matches("\\s*")) {
pstmt.setNull(5, Types.VARCHAR);
}
else {
pstmt.setString(5, email);
}
pstmt.setString(6, StringUtils.dateToMillis(now));
pstmt.setString(7, StringUtils.dateToMillis(now));
pstmt.execute();
}
catch (Exception e) {
}
finally {
DbConnectionManager.closeConnection(pstmt, con);
}
return new User(username, name, email, now, now);
}
}
这里首先调用loadUser查询数据库是否已经有该用户名对应的用户,这里假设没有,然后下面就是将用户的数据存入数据库,接着创建一个User并返回,用来存入缓存。值得注意的是,这里都是一些openfire的默认实现,实际项目中,肯定需要根据具体业务定义一些特定的类或者函数。
返回到UserManager中,接下来就是将User实例添加进缓存,并触发监听函数,例如openfire中存在一些公共的群组,需要更新这些群组信息。
再回到handleIQ,接下来通过createResultIQ构造返回信息,最后返回的信息是,
<iq type="result" id="#id" from="#from" to="#to"/>
到此,关于openfire注册的全部逻辑就分析到这了,下一章分析登录。