当使用 Smack 4.1.0 API 作为 Google 的 GCM CCS 的 XMPP 客户端时,SecurityMode.required 不起作用

Posted

技术标签:

【中文标题】当使用 Smack 4.1.0 API 作为 Google 的 GCM CCS 的 XMPP 客户端时,SecurityMode.required 不起作用【英文标题】:When using Smack 4.1.0 API as a XMPP Client for Google’s GCM CCS, SecurityMode.required is not working 【发布时间】:2015-05-18 16:38:29 【问题描述】:

我正在使用 Smack 4.1.0 API 作为 XMPP 客户端连接到 Google Cloud Messaging Cloud Connection Server (GCM CCS) 的 Java 服务器,以便将消息发送到 android 应用程序。我从这个例子开始(https://developer.android.com/google/gcm/ccs.html),但是随着 Smack API 的改变,我相应地调整了代码。至此,我的 Smack 客户端已成功连接到 GCM CCS,发送消息并接收 ack/nack/control 响应。

不幸的是,只有在我指定 XMPPTCPConnectionConfiguration.Builder.setSecurityMode(SecurityMode.ifpossible) 或 (SecurityMode.disabled) 时,连接才能正常工作。这样做时 XMPPTCPConnection.isSecureConnection() 返回 false。请参阅下面的(相关)代码:

    static final String GCM_SERVER = "gcm.googleapis.com";
    static final int GCM_PORT = 5235;
    private XMPPTCPConnection connection;
    private SSLContext sslCtx;

    ...

    try 
        KeyStore windowsRootTruststore = KeyStore.getInstance("Windows-ROOT", "SunMSCAPI");
        windowsRootTruststore.load(null, null);
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(windowsRootTruststore);
        sslCtx = SSLContext.getInstance("TLS");
        sslCtx.init(null, tmf.getTrustManagers(), null);
     catch (KeyStoreException | NoSuchProviderException | NoSuchAlgorithmException
            | KeyManagementException | CertificateException e) 
        e.printStackTrace();
    

    ...

    XMPPTCPConnectionConfiguration config = XMPPTCPConnectionConfiguration.builder()
        .setHost(GCM_SERVER)
        .setPort(GCM_PORT)
        .setServiceName(GCM_SERVER)
        .setUsernameAndPassword(GCM_SENDER_ID + "@gcm.googleapis.com", GCM_PASSWORD)
        .setCompressionEnabled(false)
        .setSecurityMode(SecurityMode.ifpossible)
        .setSendPresence(false)
        .setSocketFactory(sslCtx.getSocketFactory())
        .build();
    connection = new XMPPTCPConnection(config);
    Roster roster = Roster.getInstanceFor(connection);
    roster.setRosterLoadedAtLogin(false);
    connection.addConnectionListener(this);
    connection.addAsyncStanzaListener(this, new StanzaTypeFilter(Message.class));
    connection.addPacketInterceptor(new StanzaListener() 
            @Override
            public void processPacket(Stanza packet) throws NotConnectedException 
                System.out.println("CCS_Client sent the following message: " + packet.toXML());
            
        , new StanzaTypeFilter(Message.class));
    connection.connect();
    connection.login();
    System.out.println(connection.isSecureConnection());

根据 Google “连接有两个重要要求:1) 您必须启动传输层安全 (TLS) 连接。 ……”(见上面的链接)。在我看来,这听起来像是拒绝非 TLS 加密连接。我的问题与 SecurityMode.required 有关。使用它时,Smack 会抛出以下错误代码:

org.jivesoftware.smack.SmackException$SecurityRequiredByClientException: SSL/TLS required by client but not supported by server 
    at org.jivesoftware.smack.tcp.XMPPTCPConnection.afterFeaturesReceived(XMPPTCPConnection.java:898) 
    at org.jivesoftware.smack.AbstractXMPPConnection.parseFeatures(AbstractXMPPConnection.java:1367) 
    at org.jivesoftware.smack.tcp.XMPPTCPConnection.access$800(XMPPTCPConnection.java:139)
    at org.jivesoftware.smack.tcp.XMPPTCPConnection$PacketReader.parsePackets(XMPPTCPConnection.java:998) 
    at org.jivesoftware.smack.tcp.XMPPTCPConnection$PacketReader.access$200(XMPPTCPConnection.java:937) 
    at org.jivesoftware.smack.tcp.XMPPTCPConnection$PacketReader$1.run(XMPPTCPConnection.java:952) 
    at java.lang.Thread.run(Thread.java:744)

我已经尝试了两天来弄清楚为什么我无法建立 SecurityMode.required 连接但失败了。

使用与上面相同的 SSLContext/SSLSocketFactory,它可以正常工作,如果我在没有 Smack API 的情况下连接到 GCM CCS,只需打开一个 TLS 加密连接。根据上面谷歌的评论,当将常规的 SocketFactory(不是 SSLSocketFactory)传递给 XMPPTCPConnectionConfiguration 时,无法建立连接:

org.jivesoftware.smack.SmackException$NoResponseException: No response received within reply timeout

所以我猜测(如果我错了,请纠正我)GCM CCS 确实只接受 TLS 连接。但如果是这样的话,为什么我的 SecurityMode.required 连接尝试会被“客户端需要 SSL/TLS 但服务器不支持”拒绝?

我还想知道 SecurityMode.disabled/SecurityMode.ifpossible 是否真的成功建立了 TLS 连接,但 isSecureConnection() 仍然返回 false?这有可能吗?为了测试这个假设,我想测试在 Smack 中创建的底层 SSLsocket(在完成握手后使用 SSLSocket.getSession().getCipherSuite() 和 getProtocol())。为了做到这一点,我试图将一个定制的 SSLSocketFactory 传递给 XMPPTCPConnectionConfiguration,它产生一个定制的 SSLSocket(它只会在完成握手后输出 CipherSuite 和协议)。但我似乎也无法让它发挥作用。

如何在 SecurityMode.required 已建立且 isSecureConnection() 返回 true 的情况下与 GCM CCS 建立连接?

任何帮助将不胜感激!

【问题讨论】:

可能是 Truststore 可以解决您的问题,我不确定使用 gcm css 是否可以正常工作,但使用 xmpp server[openfire] 可以正常工作!!! ***.com/questions/24819441/… 感谢您的建议。正如您在代码 sn-p 中看到的,我已经在使用信任库(“Windows-ROOT”)来提供所需的信任锚。我检查了生成的 TrustManagers,看看这里是否有问题。但是他们正确地“提取”了信任材料。我还使用默认的 Java 信任库测试了整个事情。通过与 GCM CCS 的常规 TLS 连接,两个信任库都可以正常工作。所以我猜信任库应该按预期工作。使用任一信任库时,SecurityMode.required 连接的 Smack 错误代码保持不变。 【参考方案1】:

同样的问题,我对这个错误的看法是:

https://groups.google.com/forum/#!topic/android-gcm/5mA7FImpTGo

这表明 SSL 会话缓存有问题(被谷歌禁用)。我没有运气在服务器端(我这边)禁用它。

另一方面,您可能会看到更多类似的内容:

org.jivesoftware.smack.roster.Roster     : Exception reloading roster
org.jivesoftware.smack.SmackException$NoResponseException: No response received within reply timeout. Timeout was 5000ms (~5s).

如果是这样,您可以在此处看到问题: https://jira.spring.io/browse/INT-3972

我现在的解决方法是:https://github.com/puel/training/blob/master/gcm/gcm-server/src/main/java/org/sterl/gcm/_example/server/config/GcmConfig.java

【讨论】:

【参考方案2】:

我在使用 Smack 4.1.6 时遇到了同样的问题。

    SecurityMode 不得设置为 required (https://community.igniterealtime.org/thread/55808)。

    在填写用户名时,必须使用项目编号,而不是项目 ID(可以在 https://console.cloud.google.com 找到)。

    我的愚蠢错误,但仍然如此。在为您的项目创建 API 密钥时,请仔细选择平台:出于某种神秘原因,Android API 密钥不适用于普通的 Java 应用程序 (lol)。

    看来,服务名可以是任何东西,只是不能为空。

【讨论】:

以上是关于当使用 Smack 4.1.0 API 作为 Google 的 GCM CCS 的 XMPP 客户端时,SecurityMode.required 不起作用的主要内容,如果未能解决你的问题,请参考以下文章

smack 使用 4.1.0 rc1 在 muc 中接收消息

使用 Smack 4.1.0 的 GCM XMPP 服务器 - 需要添加哪些 jar

Smack 4.1 所需的最低 Android API 级别是多少?

XMPP Smack 4.1.0 rc1 ACCES(权限被拒绝)在 Android 上

调用 XMPPTCPConnection.login (Smack-4.1.0) 时抛出 SmackException$NoResponseException

在 MainActivity android 中使用 smack API 与服务器连接