patternjavaMinor
Authenticating with the official Mojang (Minecraft) server
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:
This class is supposed to handle step 1, 2 and 4.
The class is used like this:
What you need to know along the way:
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
- 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:
Credentialsis a simple mutable container for a username and a password
AuthenticationResponseis 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:
So, from the context you provide, I would have called this something like an
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:
The advantages of this class are that:
- 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.