Add HTTP-proxies support

This commit is contained in:
games647
2017-08-18 16:08:34 +02:00
parent 22a56862b0
commit 551441cdc4
12 changed files with 98 additions and 78 deletions

View File

@ -1,5 +1,6 @@
### 1.10
* Add support for HTTP proxies
* Set the fake offline UUID on lowest priority (-> as soon as possible)
* Remove bungee chatcolor for Bukkit to support KCauldron
* Minor cleanup using inspections + Https

View File

@ -41,7 +41,7 @@ public class EncryptionUtil {
public static byte[] getServerIdHash(String serverId, Key publicKey, Key secretKey) {
return digestOperation("SHA-1"
, new byte[][]{serverId.getBytes(Charsets.ISO_8859_1), secretKey.getEncoded(), publicKey.getEncoded()});
, serverId.getBytes(Charsets.ISO_8859_1), secretKey.getEncoded(), publicKey.getEncoded());
}
private static byte[] digestOperation(String algo, byte[]... content) {

View File

@ -195,7 +195,7 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
@Override
public Map<String, Object> loadYamlFile(Reader reader) {
YamlConfiguration config = YamlConfiguration.loadConfiguration(reader);
return config.getValues(false);
return config.getValues(true);
}
@Override
@ -215,7 +215,8 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
}
@Override
public MojangApiConnector makeApiConnector(Logger logger, List<String> addresses, int requests) {
return new MojangApiBukkit(logger, addresses, requests);
public MojangApiConnector makeApiConnector(Logger logger, List<String> addresses, int requests
, Map<String, Integer> proxies) {
return new MojangApiBukkit(logger, addresses, requests, proxies);
}
}

View File

@ -8,6 +8,7 @@ import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -20,8 +21,8 @@ public class MojangApiBukkit extends MojangApiConnector {
//mojang api check to prove a player is logged in minecraft and made a join server request
private static final String HAS_JOINED_URL = "https://sessionserver.mojang.com/session/minecraft/hasJoined?";
public MojangApiBukkit(Logger logger, List<String> localAddresses, int rateLimit) {
super(logger, localAddresses, rateLimit);
public MojangApiBukkit(Logger logger, List<String> localAddresses, int rateLimit, Map<String, Integer> proxies) {
super(logger, localAddresses, rateLimit, proxies);
}
@Override

View File

@ -8,17 +8,12 @@ import com.github.games647.fastlogin.core.shared.MojangApiConnector;
import com.github.games647.fastlogin.core.shared.PlatformPlugin;
import com.google.common.collect.Maps;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.nio.file.Files;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ThreadFactory;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
@ -70,28 +65,6 @@ public class FastLoginBungee extends Plugin implements PlatformPlugin<CommandSen
}
}
public void saveDefaultFile(String fileName) {
if (!getDataFolder().exists()) {
getDataFolder().mkdir();
}
File configFile = new File(getDataFolder(), fileName);
if (!configFile.exists()) {
InputStream in = getResourceAsStream(fileName);
try {
Files.copy(in, configFile.toPath());
} catch (IOException ioExc) {
getLogger().log(Level.SEVERE, "Error saving default " + fileName, ioExc);
} finally {
try {
in.close();
} catch (IOException ex) {
getLogger().log(Level.SEVERE, null, ex);
}
}
}
}
public FastLoginCore<ProxiedPlayer, CommandSender, FastLoginBungee> getCore() {
return core;
}
@ -139,7 +112,8 @@ public class FastLoginBungee extends Plugin implements PlatformPlugin<CommandSen
}
@Override
public MojangApiConnector makeApiConnector(Logger logger, List<String> addresses, int requests) {
return new MojangApiBungee(logger, addresses, requests);
public MojangApiConnector makeApiConnector(Logger logger, List<String> addresses, int requests
, Map<String, Integer> proxies) {
return new MojangApiBungee(logger, addresses, requests, proxies);
}
}

View File

@ -4,14 +4,15 @@ import com.github.games647.fastlogin.core.shared.LoginSession;
import com.github.games647.fastlogin.core.shared.MojangApiConnector;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import net.md_5.bungee.BungeeCord;
public class MojangApiBungee extends MojangApiConnector {
public MojangApiBungee(Logger logger, List<String> localAddresses, int rateLimit) {
super(logger, localAddresses, rateLimit);
public MojangApiBungee(Logger logger, List<String> localAddresses, int rateLimit, Map<String, Integer> proxies) {
super(logger, localAddresses, rateLimit, proxies);
}
@Override

View File

@ -28,41 +28,39 @@ public class AuthStorage {
, String user, String pass, boolean useSSL) {
this.core = core;
HikariConfig databaseConfig = new HikariConfig();
databaseConfig.setUsername(user);
databaseConfig.setPassword(pass);
databaseConfig.setDriverClassName(driver);
HikariConfig config = new HikariConfig();
config.setUsername(user);
config.setPassword(pass);
config.setDriverClassName(driver);
//a try to fix https://www.spigotmc.org/threads/fastlogin.101192/page-26#post-1874647
Properties properties = new Properties();
properties.setProperty("date_string_format", "yyyy-MM-dd HH:mm:ss");
properties.setProperty("useSSL", String.valueOf(useSSL));
databaseConfig.setDataSourceProperties(properties);
ThreadFactoryBuilder threadFactoryBuilder = new ThreadFactoryBuilder()
.setNameFormat(core.getPlugin().getName() + " Database Pool Thread #%1$d")
//Hikari create daemons by default
.setDaemon(true);
config.setDataSourceProperties(properties);
ThreadFactory platformThreadFactory = core.getPlugin().getThreadFactory();
if (platformThreadFactory != null) {
threadFactoryBuilder.setThreadFactory(platformThreadFactory);
config.setThreadFactory(new ThreadFactoryBuilder()
.setNameFormat(core.getPlugin().getName() + " Database Pool Thread #%1$d")
//Hikari create daemons by default
.setDaemon(true)
.setThreadFactory(platformThreadFactory)
.build());
}
databaseConfig.setThreadFactory(threadFactoryBuilder.build());
databasePath = databasePath.replace("{pluginDir}", core.getPlugin().getDataFolder().getAbsolutePath());
String jdbcUrl = "jdbc:";
if (driver.contains("sqlite")) {
jdbcUrl += "sqlite" + "://" + databasePath;
databaseConfig.setConnectionTestQuery("SELECT 1");
config.setConnectionTestQuery("SELECT 1");
} else {
jdbcUrl += "mysql" + "://" + host + ':' + port + '/' + databasePath;
}
databaseConfig.setJdbcUrl(jdbcUrl);
this.dataSource = new HikariDataSource(databaseConfig);
config.setJdbcUrl(jdbcUrl);
this.dataSource = new HikariDataSource(config);
}
public DataSource getDataSource() {

View File

@ -41,13 +41,13 @@ public class BalancedSSLFactory extends SSLSocketFactory {
}
@Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
public Socket createSocket(String host, int port) throws IOException {
return oldFactory.createSocket(host, port, getNextLocalAddress(), 0);
}
@Override
public Socket createSocket(String host, int port, InetAddress localAddress, int localPort)
throws IOException, UnknownHostException {
throws IOException {
//default
return oldFactory.createSocket(host, port, localAddress, localPort);
}

View File

@ -31,6 +31,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.stream.Collectors;
/**
* @param <P> Player class
@ -126,7 +127,12 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
List<String> ipAddresses = sharedConfig.get("ip-addresses");
int requestLimit = sharedConfig.get("mojang-request-limit");
this.apiConnector = plugin.makeApiConnector(plugin.getLogger(), ipAddresses, requestLimit);
List<String> proxyList = sharedConfig.get("proxies");
Map<String, Integer> proxies = proxyList.stream()
.collect(Collectors
.toMap(line -> line.split(":")[0], line -> Integer.parseInt(line.split(":")[1])));
this.apiConnector = plugin.makeApiConnector(plugin.getLogger(), ipAddresses, requestLimit, proxies);
}
public MojangApiConnector getApiConnector() {
@ -189,7 +195,7 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
try {
if (sqlite && importPlugin == ImportPlugin.AUTO_IN) {
//load sqlite driver
//load SQLite driver
Class.forName("org.sqlite.JDBC");
String jdbcUrl = "jdbc:sqlite:" + AutoInImporter.getSQLitePath();

View File

@ -1,6 +1,8 @@
package com.github.games647.fastlogin.core.shared;
import com.github.games647.fastlogin.core.BalancedSSLFactory;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import java.io.BufferedReader;
@ -8,9 +10,16 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Proxy.Type;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentMap;
@ -33,9 +42,10 @@ public abstract class MojangApiConnector {
private static final int RATE_LIMIT_CODE = 429;
//compile the pattern only on plugin enable -> and this have to be threadsafe
private final Pattern playernameMatcher = Pattern.compile(VALID_PLAYERNAME);
//compile the pattern only on plugin enable -> and this have to be thread-safe
private final Pattern nameMatcher = Pattern.compile(VALID_PLAYERNAME);
private final Iterator<Proxy> proxies;
private final ConcurrentMap<Object, Object> requests = FastLoginCore.buildCache(10, -1);
private final BalancedSSLFactory sslFactory;
private final int rateLimit;
@ -43,7 +53,8 @@ public abstract class MojangApiConnector {
protected final Logger logger;
public MojangApiConnector(Logger logger, Collection<String> localAddresses, int rateLimit) {
public MojangApiConnector(Logger logger, Collection<String> localAddresses, int rateLimit
, Map<String, Integer> proxies) {
this.logger = logger;
if (rateLimit > 600) {
@ -72,26 +83,37 @@ public abstract class MojangApiConnector {
this.sslFactory = new BalancedSSLFactory(HttpsURLConnection.getDefaultSSLSocketFactory(), addresses);
}
List<Proxy> proxyBuilder = Lists.newArrayList();
for (Entry<String, Integer> proxy : proxies.entrySet()) {
proxyBuilder.add(new Proxy(Type.HTTP, new InetSocketAddress(proxy.getKey(), proxy.getValue())));
}
this.proxies = Iterables.cycle(proxyBuilder).iterator();
}
/**
*
* @param playerName
* @return null on non-premium
*/
public UUID getPremiumUUID(String playerName) {
//check if it's a valid playername
if (playernameMatcher.matcher(playerName).matches()) {
// only make a API call if the name is valid existing mojang account
if (requests.size() >= rateLimit || System.currentTimeMillis() - lastRateLimit < 1_000 * 60 * 10) {
return null;
}
requests.put(new Object(), new Object());
//check if it's a valid player name
if (nameMatcher.matcher(playerName).matches()) {
try {
HttpsURLConnection connection = getConnection(UUID_LINK + playerName);
HttpsURLConnection connection;
if (requests.size() >= rateLimit || System.currentTimeMillis() - lastRateLimit < 1_000 * 60 * 10) {
synchronized (proxies) {
if (proxies.hasNext()) {
connection = getConnection(UUID_LINK + playerName, proxies.next());
} else {
return null;
}
}
} else {
requests.put(new Object(), new Object());
connection = getConnection(UUID_LINK + playerName);
}
if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line = reader.readLine();
@ -101,13 +123,14 @@ public abstract class MojangApiConnector {
} else if (connection.getResponseCode() == RATE_LIMIT_CODE) {
logger.info("RATE_LIMIT REACHED");
lastRateLimit = System.currentTimeMillis();
return null;
if (!connection.usingProxy()) {
return getPremiumUUID(playerName);
}
}
//204 - no content for not found
} catch (Exception ex) {
logger.log(Level.SEVERE, "Failed to check if player has a paid account", ex);
}
//this connection doesn't need to be closed. So can make use of keep alive in java
}
return null;
@ -117,18 +140,23 @@ public abstract class MojangApiConnector {
protected abstract String getUUIDFromJson(String json);
protected HttpsURLConnection getConnection(String url) throws IOException {
HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection();
protected HttpsURLConnection getConnection(String url, Proxy proxy) throws IOException {
HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection(proxy);
connection.setConnectTimeout(TIMEOUT);
connection.setReadTimeout(2 * TIMEOUT);
//the new Mojang API just uses json as response
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("User-Agent", USER_AGENT);
//this connection doesn't need to be closed. So can make use of keep alive in java
if (sslFactory != null) {
connection.setSSLSocketFactory(sslFactory);
}
return connection;
}
protected HttpsURLConnection getConnection(String url) throws IOException {
return getConnection(url, Proxy.NO_PROXY);
}
}

View File

@ -23,5 +23,6 @@ public interface PlatformPlugin<C> {
String translateColorCodes(char colorChar, String rawMessage);
MojangApiConnector makeApiConnector(Logger logger, List<String> addresses, int requests);
MojangApiConnector makeApiConnector(Logger logger, List<String> addresses, int requests
, Map<String, Integer> proxies);
}

View File

@ -176,3 +176,12 @@ database: '{pluginDir}/FastLogin.db'
# It's strongly recommended to enable SSL and setup a SSL certificate if the MySQL server isn't running on the same
# machine
#useSSL: false
# HTTP proxies for connecting to the Mojang servers in order to check if the username of a player is premium.
# This is a workaround to prevent rate-limiting by Mojang. These proxies will only be used once your server hit
# the rate-limit or the custom value above.
# Please make sure you use reliable proxies.
proxies:
# 'IP:Port' or 'Domain:Port'
# - 'xyz.com:1337'
# - 'test.com:5131'