mirror of
https://github.com/exzork/GCAuth.git
synced 2024-11-27 18:01:14 +00:00
use otp instead of token, implement createAccount and verifyUser, add some config
This commit is contained in:
parent
66df9935d2
commit
547a1b03c7
@ -10,7 +10,7 @@ sourceCompatibility = 17
|
|||||||
targetCompatibility = 17
|
targetCompatibility = 17
|
||||||
|
|
||||||
group 'me.exzork.gcauth'
|
group 'me.exzork.gcauth'
|
||||||
version '2.2.1'
|
version '2.3.0'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
@ -20,7 +20,7 @@ dependencies {
|
|||||||
implementation files('lib/grasscutter-1.1.2-dev.jar')
|
implementation files('lib/grasscutter-1.1.2-dev.jar')
|
||||||
implementation 'org.springframework.security:spring-security-crypto:5.6.3'
|
implementation 'org.springframework.security:spring-security-crypto:5.6.3'
|
||||||
implementation 'commons-logging:commons-logging:1.2'
|
implementation 'commons-logging:commons-logging:1.2'
|
||||||
implementation 'com.auth0:java-jwt:3.19.1'
|
implementation 'com.auth0:java-jwt:3.19.2'
|
||||||
implementation 'javax.servlet:javax.servlet-api:4.0.1'
|
implementation 'javax.servlet:javax.servlet-api:4.0.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ jar{
|
|||||||
jar.baseName = 'gcauth'
|
jar.baseName = 'gcauth'
|
||||||
from{
|
from{
|
||||||
configurations.runtimeClasspath.collect{
|
configurations.runtimeClasspath.collect{
|
||||||
if (it.name in ['spring-security-crypto-5.6.3.jar','commons-logging-1.2.jar','javax.servlet-api-4.0.1.jar','java-jwt-3.19.1.jar','jackson-annotations-2.13.2.jar','jackson-core-2.13.2.jar','jackson-databind-2.13.2.2.jar']) {
|
if (it.name in ['spring-security-crypto-5.6.3.jar','commons-logging-1.2.jar','javax.servlet-api-4.0.1.jar','java-jwt-3.19.2.jar','jackson-annotations-2.13.2.jar','jackson-core-2.13.2.jar','jackson-databind-2.13.2.2.jar']) {
|
||||||
zipTree(it)
|
zipTree(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@ import me.exzork.gcauth.utils.Authentication;
|
|||||||
public final class Config {
|
public final class Config {
|
||||||
public String Hash = "BCRYPT";
|
public String Hash = "BCRYPT";
|
||||||
public String jwtSecret = Authentication.generateRandomString(32);
|
public String jwtSecret = Authentication.generateRandomString(32);
|
||||||
|
public long jwtExpiration = 86400;
|
||||||
|
public long otpExpiration = 300;
|
||||||
public String[] defaultPermissions = new String[]{""};
|
public String[] defaultPermissions = new String[]{""};
|
||||||
public String ACCESS_KEY = "";
|
public String accessKey = "";
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import emu.grasscutter.auth.DefaultAuthentication;
|
|||||||
import emu.grasscutter.plugin.Plugin;
|
import emu.grasscutter.plugin.Plugin;
|
||||||
import static emu.grasscutter.Configuration.ACCOUNT;
|
import static emu.grasscutter.Configuration.ACCOUNT;
|
||||||
|
|
||||||
|
import emu.grasscutter.plugin.PluginManager;
|
||||||
import me.exzork.gcauth.handler.*;
|
import me.exzork.gcauth.handler.*;
|
||||||
import me.exzork.gcauth.utils.Authentication;
|
import me.exzork.gcauth.utils.Authentication;
|
||||||
|
|
||||||
@ -21,6 +22,9 @@ public class GCAuth extends Plugin {
|
|||||||
private static Config config;
|
private static Config config;
|
||||||
private File configFile;
|
private File configFile;
|
||||||
private static final Gson gson = new GsonBuilder().setPrettyPrinting().create();
|
private static final Gson gson = new GsonBuilder().setPrettyPrinting().create();
|
||||||
|
public static GCAuth getInstance() {
|
||||||
|
return (GCAuth) Grasscutter.getPluginManager().getPlugin("GCAuth");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
@ -65,10 +69,6 @@ public class GCAuth extends Plugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Config getConfigStatic() {
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Config getConfig() {
|
public Config getConfig() {
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
package me.exzork.gcauth.handler;
|
package me.exzork.gcauth.handler;
|
||||||
|
|
||||||
import emu.grasscutter.auth.*;
|
import emu.grasscutter.auth.*;
|
||||||
|
import emu.grasscutter.database.DatabaseHelper;
|
||||||
import emu.grasscutter.game.Account;
|
import emu.grasscutter.game.Account;
|
||||||
import emu.grasscutter.server.http.objects.ComboTokenResJson;
|
import emu.grasscutter.server.http.objects.ComboTokenResJson;
|
||||||
import emu.grasscutter.server.http.objects.LoginResultJson;
|
import emu.grasscutter.server.http.objects.LoginResultJson;
|
||||||
|
import me.exzork.gcauth.utils.Authentication;
|
||||||
|
|
||||||
public class GCAuthAuthenticationHandler implements AuthenticationSystem {
|
public class GCAuthAuthenticationHandler implements AuthenticationSystem {
|
||||||
private final Authenticator<LoginResultJson> gcAuthAuthenticator = new GCAuthenticators.GCAuthAuthenticator();
|
private final Authenticator<LoginResultJson> gcAuthAuthenticator = new GCAuthenticators.GCAuthAuthenticator();
|
||||||
@ -13,7 +15,8 @@ public class GCAuthAuthenticationHandler implements AuthenticationSystem {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void createAccount(String username, String password) {
|
public void createAccount(String username, String password) {
|
||||||
// Unhandled.
|
password = Authentication.generateHash(password);
|
||||||
|
DatabaseHelper.createAccountWithPassword(username, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -23,7 +26,11 @@ public class GCAuthAuthenticationHandler implements AuthenticationSystem {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Account verifyUser(String s) {
|
public Account verifyUser(String s) {
|
||||||
return null;
|
String uid = Authentication.getUsernameFromJwt(s);
|
||||||
|
if (uid == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return DatabaseHelper.getAccountById(uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -45,4 +52,9 @@ public class GCAuthAuthenticationHandler implements AuthenticationSystem {
|
|||||||
public ExternalAuthenticator getExternalAuthenticator() {
|
public ExternalAuthenticator getExternalAuthenticator() {
|
||||||
return externalAuthenticator;
|
return externalAuthenticator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OAuthAuthenticator getOAuthAuthenticator() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ public class GCAuthExternalAuthenticator implements ExternalAuthenticator {
|
|||||||
authResponse.jwt = "";
|
authResponse.jwt = "";
|
||||||
} else {
|
} else {
|
||||||
LoginGenerateToken loginGenerateToken = new Gson().fromJson(requestBody, LoginGenerateToken.class);
|
LoginGenerateToken loginGenerateToken = new Gson().fromJson(requestBody, LoginGenerateToken.class);
|
||||||
if (!GCAuth.getConfigStatic().ACCESS_KEY.isEmpty() && !GCAuth.getConfigStatic().ACCESS_KEY.equals(loginGenerateToken.access_key)){
|
if (!GCAuth.getInstance().getConfig().accessKey.isEmpty() && !GCAuth.getInstance().getConfig().accessKey.equals(loginGenerateToken.access_key)){
|
||||||
authResponse.success = false;
|
authResponse.success = false;
|
||||||
authResponse.message = "ERROR_ACCESS_KEY"; // ENG = "Error access key was sent with the request"
|
authResponse.message = "ERROR_ACCESS_KEY"; // ENG = "Error access key was sent with the request"
|
||||||
authResponse.jwt = "";
|
authResponse.jwt = "";
|
||||||
@ -77,7 +77,7 @@ public class GCAuthExternalAuthenticator implements ExternalAuthenticator {
|
|||||||
authResponse.jwt = "";
|
authResponse.jwt = "";
|
||||||
} else {
|
} else {
|
||||||
RegisterAccount registerAccount = new Gson().fromJson(requestBody, RegisterAccount.class);
|
RegisterAccount registerAccount = new Gson().fromJson(requestBody, RegisterAccount.class);
|
||||||
if (!GCAuth.getConfigStatic().ACCESS_KEY.isEmpty() && !GCAuth.getConfigStatic().ACCESS_KEY.equals(registerAccount.access_key)){
|
if (!GCAuth.getInstance().getConfig().accessKey.isEmpty() && !GCAuth.getInstance().getConfig().accessKey.equals(registerAccount.access_key)){
|
||||||
authResponse.success = false;
|
authResponse.success = false;
|
||||||
authResponse.message = "ERROR_ACCESS_KEY"; // ENG = "Error access key was sent with the request"
|
authResponse.message = "ERROR_ACCESS_KEY"; // ENG = "Error access key was sent with the request"
|
||||||
authResponse.jwt = "";
|
authResponse.jwt = "";
|
||||||
@ -130,8 +130,8 @@ public class GCAuthExternalAuthenticator implements ExternalAuthenticator {
|
|||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
if (authResponse.success) {
|
if (authResponse.success) {
|
||||||
if (GCAuth.getConfigStatic().defaultPermissions.length > 0) {
|
if (GCAuth.getInstance().getConfig().defaultPermissions.length > 0) {
|
||||||
for (String permission : GCAuth.getConfigStatic().defaultPermissions) {
|
for (String permission : GCAuth.getInstance().getConfig().defaultPermissions) {
|
||||||
account.addPermission(permission);
|
account.addPermission(permission);
|
||||||
}
|
}
|
||||||
account.save();
|
account.save();
|
||||||
@ -154,7 +154,7 @@ public class GCAuthExternalAuthenticator implements ExternalAuthenticator {
|
|||||||
authResponse.jwt = "";
|
authResponse.jwt = "";
|
||||||
} else {
|
} else {
|
||||||
ChangePasswordAccount changePasswordAccount = new Gson().fromJson(requestBody, ChangePasswordAccount.class);
|
ChangePasswordAccount changePasswordAccount = new Gson().fromJson(requestBody, ChangePasswordAccount.class);
|
||||||
if (!GCAuth.getConfigStatic().ACCESS_KEY.isEmpty() && !GCAuth.getConfigStatic().ACCESS_KEY.equals(changePasswordAccount.access_key)){
|
if (!GCAuth.getInstance().getConfig().accessKey.isEmpty() && !GCAuth.getInstance().getConfig().accessKey.equals(changePasswordAccount.access_key)){
|
||||||
authResponse.success = false;
|
authResponse.success = false;
|
||||||
authResponse.message = "ERROR_ACCESS_KEY"; // ENG = "Error access key was sent with the request"
|
authResponse.message = "ERROR_ACCESS_KEY"; // ENG = "Error access key was sent with the request"
|
||||||
authResponse.jwt = "";
|
authResponse.jwt = "";
|
||||||
|
@ -18,11 +18,10 @@ public class GCAuthenticators {
|
|||||||
var requestData = authenticationRequest.getPasswordRequest();
|
var requestData = authenticationRequest.getPasswordRequest();
|
||||||
assert requestData != null; // This should never be null.
|
assert requestData != null; // This should never be null.
|
||||||
|
|
||||||
Account account = Authentication.getAccountByOneTimeToken(requestData.account);
|
Account account = Authentication.getAccountByOTP(requestData.account);
|
||||||
if(account == null) {
|
if(account == null) {
|
||||||
Grasscutter.getLogger().info("[GCAuth] Client " + requestData.account + " tried to login with invalid one time token.");
|
|
||||||
response.retcode = -201;
|
response.retcode = -201;
|
||||||
response.message = "Token is invalid";
|
response.message = "OTP invalid";
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,11 +8,15 @@ import me.exzork.gcauth.GCAuth;
|
|||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;
|
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;
|
||||||
|
|
||||||
|
import java.sql.Date;
|
||||||
|
import java.time.Instant;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
|
||||||
public final class Authentication {
|
public final class Authentication {
|
||||||
public static final HashMap<String,String> tokens = new HashMap<>();
|
public static final HashMap<String, String> otps = new HashMap<>();
|
||||||
private static Algorithm key = Algorithm.HMAC256(generateRandomString(32));
|
private static Algorithm key = Algorithm.HMAC256(generateRandomNumber(32));
|
||||||
public static Algorithm getKey() {
|
public static Algorithm getKey() {
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
@ -20,44 +24,59 @@ public final class Authentication {
|
|||||||
public static Account getAccountByUsernameAndPassword(String username, String password) {
|
public static Account getAccountByUsernameAndPassword(String username, String password) {
|
||||||
Account account = DatabaseHelper.getAccountByName(username);
|
Account account = DatabaseHelper.getAccountByName(username);
|
||||||
if (account == null) return null;
|
if (account == null) return null;
|
||||||
if(account.getPassword() != null && !account.getPassword().isEmpty()) {
|
if (account.getPassword() != null && !account.getPassword().isEmpty()) {
|
||||||
if(!verifyPassword(password, account.getPassword())) account = null;
|
if (!verifyPassword(password, account.getPassword())) account = null;
|
||||||
}
|
}
|
||||||
return account;
|
return account;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Account getAccountByOneTimeToken(String token) {
|
public static Account getAccountByOTP(String otp) {
|
||||||
String username = Authentication.tokens.get(token);
|
String username = Authentication.otps.get(otp);
|
||||||
if (username == null) return null;
|
if (username == null) return null;
|
||||||
Authentication.tokens.remove(token);
|
Authentication.otps.remove(otp);
|
||||||
return DatabaseHelper.getAccountByName(username);
|
return DatabaseHelper.getAccountByName(username);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String generateRandomString(int length){
|
public static String generateRandomNumber(int length) {
|
||||||
String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
String chars = "0123456789";
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
for(int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
sb.append(chars.charAt((int) (Math.random() * chars.length())));
|
sb.append(chars.charAt((int) (Math.random() * chars.length())));
|
||||||
}
|
}
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String generateOneTimeToken(Account account) {
|
public static String generateRandomString(int length) {
|
||||||
String token = Authentication.generateRandomString(32);
|
String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||||
Authentication.tokens.put(token, account.getUsername());
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
sb.append(chars.charAt((int) (Math.random() * chars.length())));
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String generateOTP(Account account) {
|
||||||
|
String token = Authentication.generateRandomNumber(6);
|
||||||
|
Authentication.otps.put(token, account.getUsername());
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String generateJwt(Account account) {
|
public static String generateJwt(Account account) {
|
||||||
|
String otp = generateOTP(account);
|
||||||
|
while (otps.get(otp) != null) {
|
||||||
|
otp = generateOTP(account);
|
||||||
|
}
|
||||||
|
watchOTP(otp);
|
||||||
return JWT.create()
|
return JWT.create()
|
||||||
.withClaim("token",generateOneTimeToken(account))
|
.withClaim("token", otp)
|
||||||
.withClaim("username",account.getUsername())
|
.withClaim("username", account.getUsername())
|
||||||
.withClaim("uid",account.getPlayerUid())
|
.withClaim("uid", account.getPlayerUid())
|
||||||
|
.withExpiresAt(Date.from(Instant.ofEpochSecond(System.currentTimeMillis() / 1000 + GCAuth.getInstance().getConfig().jwtExpiration)))
|
||||||
.sign(key);
|
.sign(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String generateHash(String password) {
|
public static String generateHash(String password) {
|
||||||
return switch (GCAuth.getConfigStatic().Hash.toLowerCase()) {
|
return switch (GCAuth.getInstance().getConfig().Hash.toLowerCase()) {
|
||||||
case "bcrypt" -> new BCryptPasswordEncoder().encode(password);
|
case "bcrypt" -> new BCryptPasswordEncoder().encode(password);
|
||||||
case "scrypt" -> new SCryptPasswordEncoder().encode(password);
|
case "scrypt" -> new SCryptPasswordEncoder().encode(password);
|
||||||
default -> password;
|
default -> password;
|
||||||
@ -65,10 +84,33 @@ public final class Authentication {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static boolean verifyPassword(String password, String hash) {
|
private static boolean verifyPassword(String password, String hash) {
|
||||||
return switch (GCAuth.getConfigStatic().Hash.toLowerCase()) {
|
return switch (GCAuth.getInstance().getConfig().Hash.toLowerCase()) {
|
||||||
case "bcrypt" -> new BCryptPasswordEncoder().matches(password, hash);
|
case "bcrypt" -> new BCryptPasswordEncoder().matches(password, hash);
|
||||||
case "scrypt" -> new SCryptPasswordEncoder().matches(password, hash);
|
case "scrypt" -> new SCryptPasswordEncoder().matches(password, hash);
|
||||||
default -> password.equals(hash);
|
default -> password.equals(hash);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getUsernameFromJwt(String jwt) {
|
||||||
|
String username = null;
|
||||||
|
try {
|
||||||
|
username = JWT.require(key)
|
||||||
|
.build()
|
||||||
|
.verify(jwt)
|
||||||
|
.getClaim("username")
|
||||||
|
.asString();
|
||||||
|
}catch (Exception ignored) {}
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void watchOTP(String otp){
|
||||||
|
Timer timer = new Timer();
|
||||||
|
timer.schedule(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
otps.remove(otp);
|
||||||
|
timer.cancel();
|
||||||
|
}
|
||||||
|
},GCAuth.getInstance().getConfig().otpExpiration * 1000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user