android服务端框架:SpringMVC+Hibernate+Tomcat(jetty)
用到工具包:Mina, Tinder, Ecache
时序图
Androidpn服务器端启动流程,如下图所示
服务器端的消息处理流程(消息接收和消息推送)
用户未注册消息处理流程(时序图)
对照rf3920文档可以看到如下会话内容:
一客户端与服务端建立联接:
1: 客户端初始化流给服务器:
<stream:stream to="10.0.2.2" xmlns="jabber:client"xmlns:stream="http://etherx.jabber.org/streams"version="1.0">
2: 服务器发送一个流标签给客户端作为应答:
<?xml version='1.0'encoding='UTF-8'?><stream:streamxmlns:stream="http://etherx.jabber.org/streams"xmlns="jabber:client" from="127.0.0.1" id="c8d0ff7c"xml:lang="en" version="1.0">
3: 服务器发送 STARTTLS 范围给客户端(包括验证机制和任何其他流特性):
<stream:features><starttlsxmlns="urn:ietf:params:xml:ns:xmpp-tls"></starttls><authxmlns="http://jabber.org/features/iq-auth"/><registerxmlns="http://jabber.org/features/iq-register"/></stream:features>
4: 客户端发送 STARTTLS 命令给服务器:
<starttlsxmlns="urn:ietf:params:xml:ns:xmpp-tls"/>
5: 服务器通知客户端可以继续进行:
<proceed xmlns="urn:ietf:params:xml:ns:xmpp-tls"/>
(或者): 服务器通知客户端 TLS 握手失败并关闭流和TCP连接:
<failure xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>
</stream:stream>
6客户端和服务器尝试通过已有的TCP连接完成 TLS 握手.
(或者): 如果 TLS 握手不成功, 服务器关闭TCP 连接.
<stream:stream to="127.0.0.1" xmlns="jabber:client"xmlns:stream="http://etherx.jabber.org/streams"version="1.0">
7: 服务器发送一个流头信息应答客户端,其中包括任何可用的流特性:
<?xml version='1.0'encoding='UTF-8'?><stream:streamxmlns:stream="http://etherx.jabber.org/streams"xmlns="jabber:client" from="127.0.0.1"id="c8d0ff7c" xml:lang="en" version="1.0"><stream:features><authxmlns="http://jabber.org/features/iq-auth"/><registerxmlns="http://jabber.org/features/iq-register"/></stream:features>
TLS的定义
保证流安全的方法来防止篡改和偷听.这个传输层安全协议[TLS]的频道加密方法, 模拟了类似的其他"STARTTLS"(见RFC 2595 [USINGTLS])的扩展,如 IMAP [IMAP], POP3 [POP3], and ACAP [ACAP]."STARTTLS"的扩展名字空间是'urn:ietf:params:xml:ns:xmpp-tls'.
二用户注册:
1客户端发送注册指令
<iq id="12Sf7-0"type="set"><query
xmlns="jabber:iq:register"><password>86615c6b98f7454bb26d92af5fcf139c</password><username>0b3e76b776314187b79b6a63b124ef13</username></query></iq>
2 服务端发送注册完成响应指令
<iq type="result"id="12Sf7-0" to="127.0.0.1/a821f04e"/>
三用户登录:
1 客户端发送查询验证用户指令:
<iq id="12Sf7-1"type="get"><query
xmlns="jabber:iq:auth"><username>0b3e76b776314187b79b6a63b124ef13</username></query></iq>
2 服务端发送查询验证用户响应:
<iq type="result" id="12Sf7-1"><query
xmlns="jabber:iq:auth"><username>0b3e76b776314187b79b6a63b124ef13</username><password/><digest/><resource/></query></iq>
3 客户端发送指令:
<iq id="12Sf7-2"type="set"><query
xmlns="jabber:iq:auth"><username>0b3e76b776314187b79b6a63b124ef13</username><digest>774b8c076e346766ff71ef39d5ac41763e97fd91</digest><resource>AndroidpnClient</resourc
e></query></iq>
4 服务端发送用户的JID响应:
<iq type="result"id="12Sf7-2"
to="0b3e76b776314187b79b6a63b124ef13@127.0.0.1/AndroidpnClient"/>
5 客户端发送:
查询用户名录,程序未作处理
<iq id="12Sf7-3"type="get"><query xmlns="jabber:iq:roster"></query></iq>
设置广播机制
<presenceid="12Sf7-4"></presence>
问题:
SSLConfig不能正常加载:
{ERROR} [2016-12-25 15:06:57,981]<org.androidpn.server.xmpp.net.XmppIoHandler> :java.lang.NoClassDefFoundError: Could not initialize classorg.androidpn.server.xmpp.ssl.SSLConfig
解决办法:
修改SSLConfig.java文件,示例代码如下:
....
private SSLConfig(){
classPath = SSLConfig.class.getResource("/");
storeType = Config.getString("xmpp.ssl.storeType","JKS");
keyStoreLocation = Config.getString("xmpp.ssl.keystore","conf" + File.separator +"security" + File.separator +"keystore");
keyStoreLocation =classPath.getPath() +File.separator +keyStoreLocation;
keyPass = Config.getString("xmpp.ssl.keypass","changeit");
trustStoreLocation = Config.getString("xmpp.ssl.truststore","conf" + File.separator +"security" + File.separator +"truststore");
trustStoreLocation =classPath.getPath() +File.separator +trustStoreLocation;
trustPass = Config.getString("xmpp.ssl.trustpass","changeit");
log.debug("keyStoreLocation=" +keyStoreLocation);
log.debug("trustStoreLocation=" +trustStoreLocation);
// Load keystore
try {
keyStore = KeyStore.getInstance(storeType);
keyStore.load(new FileInputStream(keyStoreLocation),keyPass.toCharArray());
} catch (Exception e) {
log.error("SSLConfig startup problem.\n" +" storeType: [" + storeType +"]\n" + " keyStoreLocation:[" +keyStoreLocation + "]\n"
+ " keyPass: [" +keyPass + "]", e);
keyStore = null;
}
// Load truststore
try {
trustStore = KeyStore.getInstance(storeType);
trustStore.load(new FileInputStream(trustStoreLocation),trustPass.toCharArray());
} catch (Exception e) {
try {
trustStore = KeyStore.getInstance(storeType);
trustStore.load(null,trustPass.toCharArray());
} catch (Exception ex) {
log.error("SSLConfig startup problem.\n" +" storeType: [" + storeType +"]\n" + " trustStoreLocation: [" +trustStoreLocation
+ "]\n" + " trustPass: [" + trustPass +"]", e);
trustStore =null;
}
}
// Init factory
try {
sslContext = SSLContext.getInstance("TLS");
KeyManagerFactorykeyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyFactory.init(keyStore,SSLConfig.getKeyPassword().toCharArray());
TrustManagerFactoryc2sTrustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
c2sTrustFactory.init(trustStore);
sslContext.init(keyFactory.getKeyManagers(),c2sTrustFactory.getTrustManagers(),newjava.security.SecureRandom());
} catch (Exception e) {
log.error("SSLConfig factory setup problem." +" storeType: [" + storeType +"]\n" + " keyStoreLocation:[" +keyStoreLocation + "]\n"
+ " keyPass: [" +keyPass + "]\n" + " trustStoreLocation: [" + trustStoreLocation + "]\n" + " trustPass: [" + trustPass +"]",
e);
keyStore = null;
trustStore =null;
}
}
public static SSLConfig getInstance() {
if (instance ==null) {
synchronized (SSLConfig.class) {
instance =new SSLConfig();
}
}
return instance;
}
....
修改Connection.java文件中对SSLConfig的引用
SSLConfig.getInstance();
KeyStore ksKeys = SSLConfig.getKeyStore();
修改SSLKeyManagerFactory.java文件同上
修改SSLTrustManagerFactory.java文件同上
重启服务器,查看日志输出如下:
{DEBUG} [2016-12-26 15:59:44,035]<org.androidpn.server.xmpp.net.XmppIoHandler> : messageReceived()...
{DEBUG} [2016-12-26 15:59:44,035] <org.androidpn.server.xmpp.net.XmppIoHandler>: RCVD: <starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"/>
{DEBUG} [2016-12-26 15:59:44,042]<org.androidpn.server.xmpp.net.Connection> : startTLS()...
{DEBUG} [2016-12-26 15:59:44,051] <org.androidpn.server.xmpp.ssl.SSLConfig>:keyStoreLocation=/D:/tools/service/apache-tomcat-7.0.2/webapps/ROOT/WEB-INF/classes/\conf/security/keystore
{DEBUG} [2016-12-26 15:59:44,052]<org.androidpn.server.xmpp.ssl.SSLConfig> :trustStoreLocation=/D:/tools/service/apache-tomcat-7.0.2/webapps/ROOT/WEB-INF/classes/\conf/security/truststore
{DEBUG}[2016-12-26 15:59:44,443] <org.androidpn.server.xmpp.net.Connection> :SENT: <proceed xmlns="urn:ietf:params:xml:ns:xmpp-tls"/>
客户端添加tls
客户端在开启服务建立联接时报错信息
12-26 08:17:09.156: W/System.err(547):java.security.KeyStoreException: KeyStore jks implementation not found
12-26 08:17:09.166:W/System.err(547): atjava.security.KeyStore.getInstance(KeyStore.java:119)
12-26 08:17:09.166:W/System.err(547): atorg.jivesoftware.smack.ServerTrustManager.<init>(ServerTrustManager.java:61)
12-26 08:17:09.166:W/System.err(547): atorg.jivesoftware.smack.XMPPConnection.proceedTLSReceived(XMPPConnection.java:759)
12-26 08:17:09.166:W/System.err(547): atorg.jivesoftware.smack.PacketReader.parsePackets(PacketReader.java:267)
12-26 08:17:09.166:W/System.err(547): atorg.jivesoftware.smack.PacketReader.access$1(PacketReader.java:220)
12-26 08:17:09.166:W/System.err(547): atorg.jivesoftware.smack.PacketReader$1.run(PacketReader.java:70)
解决方法如下:
在connectTask方法中添加如下内容
connConfig.setReconnectionAllowed(true); connConfig.setTruststorePath("system/etc/security/cacerts.bks");
connConfig.setTruststorePassword("changeit");
connConfig.setTruststoreType("bks");
客户端在关闭服务后,服务端报错: (未解决不影响正常用运)
javax.net.ssl.SSLException: Inbound closed before receivingpeer's close_notify: possible truncation attack?
服务器端:发送广播消息和发送指定用户接收消息中的离线用户消息处理(未测试)
服务器重启后,在线用户session状态
客户端: 用户重启机器,服务的运行状态(未测试)
退出重新进入应用不能正常联接(断开不能重联)