killme2008 / xmemcached

High performance, easy to use multithreaded memcached client in java.
http://fnil.net/xmemcached
Apache License 2.0
757 stars 281 forks source link

Authentication failure on CRAM-MD5 #144

Open SaeedZhiany opened 8 months ago

SaeedZhiany commented 8 months ago

Hello @killme2008

I'm using this library to connect a Memcached node with CRAM-MD5 SASL authentication. but I'm getting an authentication error.

I should mention that I tried similar code using the library net.spy.memcached and its client can successfully connect and fetch the data.

the versions I'm using: this library: 2.4.8 library net.spy.memcached: 2.12.3

here is a reproducible code. just remember I created and ran 3 different Memcached nodes on 3 different ports on my local PC to test these two libraries against all the authentication methods (no authentication, PLAIN, and CRAM-MD5). both libraries successfully connect and fetch data using no authentication and PLAIN methods. but when I'm trying CRAM-MD5, only net.spy.memcached client can pass the authentication and get the data. Xmemcached client failed to authenticate. I'm wondering why. it seems I'm using closely similar configurations in both libraries. you can create similar nodes on your machine and then test the code below yourself.

My question is what should I do to make the Xmemcached client pass the CRAM-MD5 authentication? I prefer using your library due to its synchronized methods. I'm using Memcached in an environment that does not need more than one node and also one client. so I don't want to struggle with the multithreading problems.

Thanks for your help.

import com.google.code.yanf4j.core.Session;
import net.rubyeye.xmemcached.MemcachedClientBuilder;
import net.rubyeye.xmemcached.XMemcachedClient;
import net.rubyeye.xmemcached.XMemcachedClientBuilder;
import net.rubyeye.xmemcached.auth.AuthInfo;
import net.rubyeye.xmemcached.auth.PlainCallbackHandler;
import net.rubyeye.xmemcached.command.BinaryCommandFactory;
import net.rubyeye.xmemcached.exception.MemcachedException;
import net.rubyeye.xmemcached.networking.MemcachedSession;
import net.spy.memcached.*;
import net.spy.memcached.auth.AuthDescriptor;
import net.spy.memcached.protocol.TCPMemcachedNodeImpl;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.TimeoutException;

public class App {
    public enum MemcachedAuthenticationMethod {
        NONE(null, null, null, 11213),
        PLAIN("PLAIN", "username", "password", 11211),
        CRAM_MD5("CRAM-MD5", "username", "123456", 11212);
        private final String value;
        private final String username;
        private final String password;
        private final Integer port;

        MemcachedAuthenticationMethod(String value, String username, String password, Integer port) {
            this.value = value;
            this.username = username;
            this.password = password;
            this.port = port;
        }

        public String getValue() {
            return value;
        }

        public String getUsername() {
            return username;
        }

        public String getPassword() {
            return password;
        }

        public Integer getPort() {
            return port;
        }
    }

    public static final MemcachedAuthenticationMethod memcachedAuthMethod = MemcachedAuthenticationMethod.CRAM_MD5;
    public static final InetSocketAddress inetSocketAddress = new InetSocketAddress("memcached.obj.com", memcachedAuthMethod.getPort());

    public static void showStatInfo(Map<String, String> generalStats, Map<String, String> settingsStats) throws Exception {
        if (generalStats == null || generalStats.isEmpty()) {
            throw new Exception("general stats is empty");
        }
        System.out.println(inetSocketAddress);
        System.out.println("version: " + generalStats.get("version"));
        System.out.println("pid: " + generalStats.get("pid"));
        System.out.println("accepting_conns: " + generalStats.get("accepting_conns"));
        System.out.println("tcpport: " + settingsStats.get("tcpport"));
        System.out.println("udpport: " + settingsStats.get("udpport"));
        System.out.println("evictions: " + settingsStats.get("evictions"));
        System.out.println("item_size_max: " + settingsStats.get("item_size_max"));
        System.out.println("cas_enabled: " + settingsStats.get("cas_enabled"));
        System.out.println("maxconns_fast: " + settingsStats.get("maxconns_fast"));
        System.out.println("lru_crawler: " + settingsStats.get("lru_crawler"));
        System.out.println("proxy_enabled: " + settingsStats.get("proxy_enabled"));
    }

    public static void main(String[] args) {
        spyMain();
        System.out.println();
        xMain();
    }

    public static void spyMain() {
        System.out.println("**************************** Fetch using the library 'net.spy.memcached' *****************************");
        MemcachedClient memcachedClient = null;
        try {
            ConnectionFactory connectionFactory = new DefaultConnectionFactory();
            if (memcachedAuthMethod.getValue() != null) {
                System.setProperty("net.spy.memcached.auth.AuthThreshold", "1");
                final AuthDescriptor ad = new AuthDescriptor(
                        new String[]{memcachedAuthMethod.getValue()},
                        new net.spy.memcached.auth.PlainCallbackHandler(memcachedAuthMethod.getUsername(), memcachedAuthMethod.getPassword())
                );
                connectionFactory = new ConnectionFactoryBuilder()
                        .setProtocol(ConnectionFactoryBuilder.Protocol.BINARY)
                        .setAuthDescriptor(ad)
                        .setAuthWaitTime(5000)
                        .build();
            }

            memcachedClient = new MemcachedClient(connectionFactory, Collections.singletonList(inetSocketAddress));

            MemcachedNode node = memcachedClient.getNodeLocator().getAll().stream().findFirst().orElse(null);
            if (node == null) {
                return;
            } else if (node instanceof TCPMemcachedNodeImpl && !node.isAuthenticated()) {
                return;
            } else if (!node.isActive()) {
                return;
            }

            Map<String, String> generalStats = memcachedClient.getStats().get(inetSocketAddress);
            Map<String, String> settingsStats = memcachedClient.getStats("settings").get(inetSocketAddress);

            showStatInfo(generalStats, settingsStats);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (Exception e) {
            System.out.println("Exception discovering Memcached object on " + e);
            throw new RuntimeException(e);
        } finally {
            if (memcachedClient != null) {
                memcachedClient.shutdown();
            }
        }
    }

    public static void xMain() {
        System.out.println("**************************** Fetch using the library 'Xmemcached' *****************************");
        XMemcachedClient memcachedClient = null;
        try {
            MemcachedClientBuilder builder = new XMemcachedClientBuilder(Collections.singletonList(inetSocketAddress));
            if (memcachedAuthMethod.getValue() != null) {
                System.setProperty("net.rubyeye.xmemcached.auth_max_attempts", "0");
                final AuthInfo ad = new AuthInfo(
                        new PlainCallbackHandler(memcachedAuthMethod.getUsername(), memcachedAuthMethod.getPassword()),
                        new String[]{memcachedAuthMethod.getValue()}
                );
                builder.addAuthInfo(inetSocketAddress, ad);
                builder.setCommandFactory(new BinaryCommandFactory());
                builder.setConnectTimeout(5000);
                builder.setOpTimeout(5000);
            }

            memcachedClient = (XMemcachedClient) builder.build();

            Session session = memcachedClient.getConnector().getSessionSet().stream().findFirst().orElse(null);
            if (session == null) {
                return;
            } else if (session instanceof MemcachedSession && ((MemcachedSession) session).isAuthFailed()) {
                return;
            } else if (session.isClosed()) {
                return;
            }

            Map<String, String> generalStats = memcachedClient.getStats().get(inetSocketAddress);
            Map<String, String> settingsStats = memcachedClient.getStatsByItem("settings").get(inetSocketAddress);

            showStatInfo(generalStats, settingsStats);
        } catch (InterruptedException | MemcachedException | TimeoutException | IOException e) {
            throw new RuntimeException(e);
        } catch (Exception e) {
            System.out.println("Exception discovering Memcached object on " + e);
            throw new RuntimeException(e);
        } finally {
            if (memcachedClient != null) {
                try {
                    memcachedClient.shutdown();
                } catch (IOException ignored) {
                }
            }
        }
    }
}

and here is the console result:

**************************** Fetch using the library 'net.spy.memcached' *****************************
2024-01-07 14:56:57.784 INFO net.spy.memcached.MemcachedConnection:  Setting retryQueueSize to -1
2024-01-07 14:56:57.790 INFO net.spy.memcached.MemcachedConnection:  Added {QA sa=memcached.obj.com/20.150.30.16:11212, #Rops=0, #Wops=0, #iq=0, topRop=null, topWop=null, toWrite=0, interested=0} to connect queue
2024-01-07 14:56:58.033 INFO net.spy.memcached.auth.AuthThread:  Authenticated to memcached.obj.com/20.150.30.16:11212
2024-01-07 14:56:58.037 INFO net.spy.memcached.MemcachedConnection:  Shut down memcached client
2024-01-07 14:56:58.037 WARN net.spy.memcached.auth.AuthThreadMonitor:  Connection shutdown in progress - interrupting waiting authentication thread.
2024-01-07 14:56:58.037 WARN net.spy.memcached.auth.AuthThread:  Authentication failed to memcached.obj.com/20.150.30.16:11212, Status: {OperationStatus success=false:  cancelled}
memcached.obj.com/20.150.30.16:11212
version: 1.6.22
pid: 1
accepting_conns: 1
tcpport: 11211
udpport: 0
evictions: on
item_size_max: 1048576
cas_enabled: yes
maxconns_fast: yes
lru_crawler: yes
proxy_enabled: null

**************************** Fetch using the library 'Xmemcached' *****************************
[main] INFO net.rubyeye.xmemcached.XMemcachedClient - XMemcachedClient is using Binary protocol
[main] INFO com.google.code.yanf4j.nio.impl.SelectorManager - Creating 8 reactors...
[main] INFO com.google.code.yanf4j.core.impl.AbstractController - The Controller started at localhost/127.0.0.1:0 ...
[Thread-12] ERROR net.rubyeye.xmemcached.auth.AuthTask - Authentication failed to memcached.obj.com/20.150.30.16:11212
[Thread-12] WARN net.rubyeye.xmemcached.auth.AuthTask - Reopen connection to memcached.obj.com/20.150.30.16:11212,beacause auth fail
[Xmemcached-Reactor-0] INFO com.google.code.yanf4j.core.impl.AbstractController - Add a session: 20.150.30.16:11212
[main] INFO com.google.code.yanf4j.core.impl.AbstractController - Controller has been stopped.