摘要:主要分析Openfire管理员后台无法登陆问题
问题
在安装Openfire后,讨论群里经常有人说初始化后在管理员页面输入帐号admin和密码admin后无法登陆到服务器,或者更改了某些配置导致无法登录,如下:

原因
可能的原因有多种情况,需要一步步分析,通常有以下可能:
- 权限问题(帐号不是管理员、帐号密码错误、限制IP等)
- 服务器域与管理员域不一致(这种情况通常是修改域后导致)
- 重载了provider问题(有用户不是在官网上下载openfire安装后出现这类问题)
分析
首先需要了解加载管理员帐号过程,登录页源码在openfire_src\src\web\login.jsp文件中,认证代码为:
1 2 3 4 5 6 7 8 9 10 11 12 13
| if (loginUsername != null) { if (LoginLimitManager.getInstance().hasHitConnectionLimit(loginUsername, request.getRemoteAddr())) { throw new UnauthorizedException("User '" + loginUsername +"' or address '" + request.getRemoteAddr() + "' has his login attempt limit."); } if (!AdminManager.getInstance().isUserAdmin(loginUsername, true)) { throw new UnauthorizedException("User '" + loginUsername + "' not allowed to login."); } authToken = AuthFactory.authenticate(loginUsername, password); } else { errors.put("unauthorized", LocaleUtils.getLocalizedString("login.failed.unauthorized")); }
|
第一步 判断是否IP地址限制连接
LoginLimitManager::hasHitConnectionLimit位于org.jivesoftware.admin.LoginLimitManager,用来判断是否尝试连接次数过多,防止暴力破解。
第二步 判断用户是否有管理员权限
AdminManager::isUserAdmin位于org.jivesoftware.openfire.admin.AdminManager,用来判断用户是否在管理员列表中。
1 2 3 4 5 6 7 8 9 10
| public boolean isUserAdmin(String username, boolean allowAdminIfEmpty) { if (adminList == null) { loadAdminList(); } if (allowAdminIfEmpty && adminList.isEmpty()) { return "admin".equals(username); } JID userJID = XMPPServer.getInstance().createJID(username, null); return adminList.contains(userJID); }
|
加载管理员列表
AdminManager保存了管理员列表adminList,如果为空将首先从provider中加载。
1 2 3
| private void loadAdminList() { adminList = provider.getAdmins(); }
|
其中provider通过属性provider.admin.className来获取,意为管理员提供者,通过查询openfire数据库中的ofProperty属性表可知默认情况下provider对象为org.jivesoftware.openfire.admin.DefaultAdminProvider的实例,ofProperty表如下:

继续跟踪DefaultAdminProvider::getAdmins()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public List<JID> getAdmins() { List<JID> adminList = new ArrayList<JID>(); String jids = JiveGlobals.getProperty("admin.authorizedJIDs"); jids = (jids == null || jids.trim().length() == 0) ? "" : jids; StringTokenizer tokenizer = new StringTokenizer(jids, ","); while (tokenizer.hasMoreTokens()) { String jid = tokenizer.nextToken().toLowerCase().trim(); try { adminList.add(new JID(jid)); } catch (IllegalArgumentException e) { … } } if (adminList.isEmpty()) { adminList.add(new JID("admin", XMPPServer.getInstance().getServerInfo().getXMPPDomain(), null, true)); } return adminList; }
|
这里会去获取属性值admin.authorizeJIDs,以上图为例返回的是admin@bfc,yzw@bfc,现在管理员列表中有admin@bfc以及yzw@bfc的JID。
创建JID
1
| JID userJID = XMPPServer.getInstance().createJID(username, null);
|
这句通过用户名来创建资源ID为空的JID,例如当前输入帐号为yzw,当前域为bfc,那么创建的值为yzw@bfc的纯JID。如果当前服务器域为bbk,那么创建的为yzw@bbk。其中域通过ofProperty表中的xmpp.domain来获取。
比较JID
return adminList.contains(userJID);
这里判断是否在管理员列表里是否有登录用户的JID。如果用户登录服务器的域和管理员的域不一致,那么就不能登录了。
第三步 认证
AuthFactory.authenticate位于org.jivesoftware.openfire.auth. AuthFactory,用来验证帐号密码是否错误,并且返回认证信令。
1 2 3 4 5 6 7 8 9
| public static AuthToken authenticate(String username, String password) throws UnauthorizedException, ConnectionException, InternalUnauthenticatedException { if (LockOutManager.getInstance().isAccountDisabled(username)) { LockOutManager.getInstance().recordFailedLogin(username); throw new UnauthorizedException(); } authProvider.authenticate(username, password); return new AuthToken(username); }
|
LockOutManager.getInstance().isAccountDisabled用来判断是否帐号被禁用了。执行到authProvider.authenticate(username, password),通过查看数据库表ofProperty表中的provider.auth.className可知默认情况下authProvider指向org.jivesoftware.openfire.auth.DefaultAuthProvider。执行DefaultAuthProvider:: authenticate(String,String):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public void authenticate(String username, String password) throws UnauthorizedException { if (username == null || password == null) { throw new UnauthorizedException(); } username = username.trim().toLowerCase(); if (username.contains("@")) { int index = username.indexOf("@"); String domain = username.substring(index + 1); if (domain.equals(XMPPServer.getInstance().getServerInfo().getXMPPDomain())) { username = username.substring(0, index); } else { throw new UnauthorizedException(); } } try { if (!password.equals(getPassword(username))) { throw new UnauthorizedException(); } } catch (UserNotFoundException unfe) { throw new UnauthorizedException(); } }
|
如果登录帐号有域的话需要认证域是否相同,然后认证密码是否正确。注意用户名是不含域的,所以比较后需要先去掉域的部分再获取密码。getPassword默认情况是从ofUser表中去获取密码,默认admin密码为明文的admin。
总结
在ofProperty表登录认证的最主要的几个值为:
- admin.authorizeJIDs提供了管理员帐号,包括了所在域,例如admin@bfc。
- provider.admin.className对应的类提供了管理员列表,默认从admin.authorizeJIDs加载。
- provider.auth.className对应的类实现了认证方法。
- xmpp.domain指出了服务器所在的域。
如果在初始化之后无法登录管理员,需要只修改上述几个值,在ofUser表中添加admin帐号,明文密码admin后即可用admin登录。