HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavaMinor

Authenticating with the official Mojang (Minecraft) server

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
theserverwithminecraftmojangauthenticatingofficial

Problem

Minecraft is a game which also has multiplayer capabilities. You need to have an account (which needs to be verified) for playing on servers. The authentication procedure looks like this:

  • Authenticate at the Mojang (creators of Minecraft) server with username and password



  • Retrieve a session id



  • Contact the server you want to play on and send your username and session id (for verification that you own that name)



  • Send every 5 minutes a keep-alive request to the Mojang server so that the id does not timeout



This class is supposed to handle step 1, 2 and 4.

The class is used like this:

Authentication authentication = new Authentication(username, password);
AuthenticationResponse response = AuthenticationResponse.UNKNOWN;
try {
    response = authentication.authenticate();
} catch (UnsupportedEncodingException ex) {
    LOGGER.log(Level.SEVERE, "Authentication failed!", ex);
} catch (MalformedURLException ex) {
    LOGGER.log(Level.SEVERE, "Authentication failed!", ex);
} catch (IOException ex) {
    LOGGER.log(Level.SEVERE, "Authentication failed!", ex);
}
if (response == AuthenticationResponse.SUCCESS) {
    // Do something with the session id (authentication.getSessionId())
}


What you need to know along the way:

  • Credentials is a simple mutable container for a username and a password



  • AuthenticationResponse is an enum



  • I designed it with the thought to give complete control to the client, that's why every single field is exposed to be changed



Here's the code:

```
package org.bonsaimind.minecraftmiddleknife.pre16;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import org.bonsaimind.minecraftmiddleknife.Credentials;

/**
* Deals with the authentication at the Mojang, or any other server.
*/
public

Solution

The logic and data encapsulation are a little disconnected here. I think it may because I have misunderstood where this class fits in to your class hierarchy.... but I don't think so. There are a few things I don't like:

  • This Authenticate is really a 'AuthenticatedSession' and not a special type of Credential. i.e. this class uses a credential to create a session, and then it keeps that session alive.



  • Why do you have so many constructors?



  • you say that this class takes care of the 5-minute keep-alive, but all I see is a method and no timer



  • I would expect this class to be immutable - no setters should be necessary .... will the login details and login server change at all?



  • The httpRequest throws too many exceptions. I would catch all the exceptions in httpRequest and throw a single custom exception (initialized with the right cause) like AuthenticationException



So, from the context you provide, I would have called this something like an AutheticatedSession, and I would have a factory method on it that takes a server, and credential as parameters. There should be no need to 'remember' the credentials after the initial login.

Given the challenge:


Create a session logged in to an authentication server, that requires a regular heartbeat, and provides some additional data about the user.

I would have a class something like:

public class AuthenticatedSession {

    /**
     * Factory method to create AuthenticatedSession instances.
     */
    public static AuthenticatedSession connect(String serverurl, Credential credential) throws AuthenticationException {

        // do the work of setting up the right URL and content.
        String response = httpRequest(serverurl, content);

        // use the string to build your AuthenticatedSession...
        return new AuthenticatedSession(....session details....);
    }

    // this timer will run the heart-beat.
    private static final ScheduledThreadPoolExecutor TIMEDEXECUTOR = new ScheduledThreadPoolExecutor (1);

    // This is the runnable instance that will sit in the timer schedule
    private final class KeepAlive implements Runnable {
        public void run() {
            // call the keepalive method on the main class.
            // if keepalive throws an exception it will stop this timer thread.
            keepAlive();
        }
    }

    // if a heartbeat fails, will be set false.
    private final AtomicBoolean linkAlive = new AtomicBoolean(true);
    private final String hbserver;
    private final String hbcontent;
    private final ...... // all the fields that are supplied by the server.

    // private constructor, called by the factory.
    private AuthenticatedSession(String .... all the fields that are supplied by the server) {
         this. ... fields  = input fields;

         this.hbserver = ...; // details for the heartbeat URL
         this.hbcontent = ...; // details for the heartbeat Content. 
         // start the keepalive.
         TIMEDEXECUTOR.scheduleAtFixedRate(new KeepAlive(), long 5, long 5, TimeUnit.MINUTE);
    }

    public boolean isAlive() {
        return linkAlive.get();
    }

    // the heartbeat can be private.
    private void heartBeat() {
        try {
            httpRequest(hbserver, hbcontent);
        } catch (AuthenticationException afe) {
            // need to throw an exception so that the timer on the heartbeat thread stops.
            // communicate the session is dead.
            linkActive.set(false);
            throw new IllegalStateException("Unable to renew session", ioe);
        }
    }

    public String get....() {
        // have getters for the session's values (real name, etc.)
    }
}


The advantages of this class are that:

  • it is self-contained



  • it is immutable, and thus fully thread-safe



  • it is self-managing - no need for external intervention to keep-alive



  • it does not hang on to unnecessary data (credentials).

Code Snippets

public class AuthenticatedSession {

    /**
     * Factory method to create AuthenticatedSession instances.
     */
    public static AuthenticatedSession connect(String serverurl, Credential credential) throws AuthenticationException {

        // do the work of setting up the right URL and content.
        String response = httpRequest(serverurl, content);

        // use the string to build your AuthenticatedSession...
        return new AuthenticatedSession(....session details....);
    }

    // this timer will run the heart-beat.
    private static final ScheduledThreadPoolExecutor TIMEDEXECUTOR = new ScheduledThreadPoolExecutor (1);

    // This is the runnable instance that will sit in the timer schedule
    private final class KeepAlive implements Runnable {
        public void run() {
            // call the keepalive method on the main class.
            // if keepalive throws an exception it will stop this timer thread.
            keepAlive();
        }
    }


    // if a heartbeat fails, will be set false.
    private final AtomicBoolean linkAlive = new AtomicBoolean(true);
    private final String hbserver;
    private final String hbcontent;
    private final ...... // all the fields that are supplied by the server.

    // private constructor, called by the factory.
    private AuthenticatedSession(String .... all the fields that are supplied by the server) {
         this. ... fields  = input fields;

         this.hbserver = ...; // details for the heartbeat URL
         this.hbcontent = ...; // details for the heartbeat Content. 
         // start the keepalive.
         TIMEDEXECUTOR.scheduleAtFixedRate(new KeepAlive(), long 5, long 5, TimeUnit.MINUTE);
    }

    public boolean isAlive() {
        return linkAlive.get();
    }

    // the heartbeat can be private.
    private void heartBeat() {
        try {
            httpRequest(hbserver, hbcontent);
        } catch (AuthenticationException afe) {
            // need to throw an exception so that the timer on the heartbeat thread stops.
            // communicate the session is dead.
            linkActive.set(false);
            throw new IllegalStateException("Unable to renew session", ioe);
        }
    }

    public String get....() {
        // have getters for the session's values (real name, etc.)
    }
}

Context

StackExchange Code Review Q#37259, answer score: 4

Revisions (0)

No revisions yet.