Сделал закрытие сесси сервером если пароль не верный
This commit is contained in:
AidarKC
2025-12-11 16:03:46 +03:00
parent 7f91c60d26
commit dbf1f22bac
4 changed files with 115 additions and 20 deletions
@@ -11,8 +11,9 @@ import shine.db.entities.ActiveSession;
public class ConnectionContext {
// Статусы аутентификации
public static final int AUTH_STATUS_NONE = 0; // анонимный или не авторизованный пользователь
public static final int AUTH_STATUS_USER = 1; // авторизованный пользователь
public static final int AUTH_STATUS_NONE = 0; // анонимный / не авторизован
public static final int AUTH_STATUS_AUTH_IN_PROGRESS = 1; // получен AuthChallenge, ждём CreateAuthSession
public static final int AUTH_STATUS_USER = 2; // авторизованный пользователь
// Полный пользователь из БД (solana_users)
private SolanaUser solanaUser;
@@ -26,10 +27,15 @@ public class ConnectionContext {
private String sessionId;
/**
* Временный секрет шага 1, который используется на шаге 2 и хранится в БД.
* Временный секрет шага 1, который используется на шаге 2 и хранится в БД,
* а после успешной авторизации — настоящий секрет сессии.
*/
private String sessionPwd;
/**
* Текущий статус аутентификации.
* См. константы AUTH_STATUS_*
*/
private int authenticationStatus = AUTH_STATUS_NONE;
/**
@@ -132,4 +138,4 @@ public class ConnectionContext {
", authenticationStatus=" + authenticationStatus +
'}';
}
}
}
@@ -13,6 +13,12 @@ import shine.db.entities.SolanaUser;
import java.security.SecureRandom;
import java.util.Base64;
/**
* Шаг 1 авторизации: запрос выдачи временного nonce (authNonce).
*
* Клиент по логину просит сервер сгенерировать случайный authNonce,
* который будет использован на втором шаге при подписи.
*/
public class Net_AuthChallenge_Handler implements JsonMessageHandler {
private static final SecureRandom RANDOM = new SecureRandom();
@@ -32,7 +38,7 @@ public class Net_AuthChallenge_Handler implements JsonMessageHandler {
);
}
// 1) Проверка: в контексте никто не авторизован
// Если по этому соединению уже есть залогиненный пользователь — не даём повторную авторификацию
if (ctx.getLogin() != null) {
return NetExceptionResponseFactory.error(
req,
@@ -57,6 +63,9 @@ public class Net_AuthChallenge_Handler implements JsonMessageHandler {
// 3) Заполняем контекст пользователем
ctx.setSolanaUser(solanaUser);
// 3.1) Отмечаем, что по этому соединению начата авторификация
ctx.setAuthenticationStatus(ConnectionContext.AUTH_STATUS_AUTH_IN_PROGRESS);
// 4) Генерируем одноразовый authNonce = base64(32 случайных байт)
byte[] buf = new byte[32];
RANDOM.nextBytes(buf);
@@ -2,6 +2,7 @@ package server.logic.ws_protocol.JSON.handlers.auth;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.eclipse.jetty.websocket.api.Session;
import server.logic.ws_protocol.JSON.ActiveConnectionsRegistry;
import server.logic.ws_protocol.JSON.ConnectionContext;
import server.logic.ws_protocol.JSON.entyties.Auth.Net_CreateAuthSession_Response;
@@ -11,6 +12,7 @@ import server.logic.ws_protocol.JSON.entyties.Auth.Net_CreateAuthSession_Request
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
import server.logic.ws_protocol.WireCodes;
import server.ws.WsConnectionUtils;
import shine.db.dao.ActiveSessionsDAO;
import shine.db.entities.ActiveSession;
import shine.db.entities.SolanaUser;
@@ -18,8 +20,6 @@ import shine.geo.ClientInfoService;
import shine.geo.GeoLookupService;
import utils.crypto.Ed25519Util;
import org.eclipse.jetty.websocket.api.Session;
import java.nio.charset.StandardCharsets;
import java.sql.SQLException;
import java.security.SecureRandom;
@@ -44,6 +44,9 @@ import java.util.Base64;
* - sessionCreatedAtMs и lastAuthirificatedAtMs = текущее время;
* - заполняются поля clientIp, clientInfoFromClient, clientInfoFromRequest, userLanguage;
* - возвращается sessionId и sessionPwd в ответе.
*
* При ошибке авторификации (битые данные, подпись, время и т.п.) —
* соединение закрывается через WsConnectionUtils.
*/
public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
@@ -58,52 +61,63 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
// --- базовые проверки контекста ---
if (ctx == null || ctx.getSolanaUser() == null || ctx.getSessionPwd() == null) {
return NetExceptionResponseFactory.error(
Net_Response err = NetExceptionResponseFactory.error(
req,
WireCodes.Status.BAD_REQUEST,
"NO_STEP1_CONTEXT",
"Шаг 1 авторизации не был корректно выполнен для данного соединения"
);
WsConnectionUtils.closeConnection(ctx, 4001, "Auth failed: no step1 context");
return err;
}
if (!ctx.isAnonymous()) {
return NetExceptionResponseFactory.error(
// Ожидаем, что перед этим был AuthChallenge и статус = AUTH_IN_PROGRESS
if (ctx.getAuthenticationStatus() != ConnectionContext.AUTH_STATUS_AUTH_IN_PROGRESS) {
Net_Response err = NetExceptionResponseFactory.error(
req,
WireCodes.Status.BAD_REQUEST,
"ALREADY_AUTHED",
"Пользователь уже авторизован по текущему соединению"
"BAD_AUTH_FLOW_STATE",
"Неожиданное состояние авторификации для данного соединения"
);
WsConnectionUtils.closeConnection(ctx, 4001, "Auth failed: bad auth flow state");
return err;
}
SolanaUser user = ctx.getSolanaUser();
Long loginId = user.getLoginId();
if (loginId == null) {
return NetExceptionResponseFactory.error(
Net_Response err = NetExceptionResponseFactory.error(
req,
WireCodes.Status.SERVER_DATA_ERROR,
"NO_LOGIN_ID",
"Для пользователя не задан loginId в БД"
);
WsConnectionUtils.closeConnection(ctx, 4001, "Auth failed: no loginId");
return err;
}
String storagePwd = req.getStoragePwd();
if (storagePwd == null || storagePwd.isBlank()) {
return NetExceptionResponseFactory.error(
Net_Response err = NetExceptionResponseFactory.error(
req,
WireCodes.Status.BAD_REQUEST,
"EMPTY_STORAGE_PWD",
"Пустой storagePwd"
);
WsConnectionUtils.closeConnection(ctx, 4001, "Auth failed: empty storagePwd");
return err;
}
String signatureB64 = req.getSignatureB64();
if (signatureB64 == null || signatureB64.isBlank()) {
return NetExceptionResponseFactory.error(
Net_Response err = NetExceptionResponseFactory.error(
req,
WireCodes.Status.BAD_REQUEST,
"EMPTY_SIGNATURE",
"Пустая цифровая подпись"
);
WsConnectionUtils.closeConnection(ctx, 4001, "Auth failed: empty signature");
return err;
}
long timeMs = req.getTimeMs();
@@ -111,12 +125,14 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
long diff = Math.abs(nowMs - timeMs);
if (diff > ALLOWED_SKEW_MS) {
return NetExceptionResponseFactory.error(
Net_Response err = NetExceptionResponseFactory.error(
req,
WireCodes.Status.BAD_REQUEST,
"TIME_SKEW",
"Время клиента отличается от сервера более чем на 30 секунд"
);
WsConnectionUtils.closeConnection(ctx, 4001, "Auth failed: time skew");
return err;
}
// Короткая строка clientInfo от клиента (до 50 символов)
@@ -128,12 +144,14 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
// --- выбираем публичный ключ pubkey1 ---
String pubKeyB64 = user.getDeviceKey();
if (pubKeyB64 == null || pubKeyB64.isBlank()) {
return NetExceptionResponseFactory.error(
Net_Response err = NetExceptionResponseFactory.error(
req,
WireCodes.Status.BAD_REQUEST,
"NO_PUBKEY1",
"Отсутствует публичный ключ pubkey1 для пользователя"
);
WsConnectionUtils.closeConnection(ctx, 4001, "Auth failed: no pubkey");
return err;
}
byte[] publicKey32;
@@ -142,12 +160,14 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
publicKey32 = Ed25519Util.keyFromBase64(pubKeyB64);
signature64 = Base64.getDecoder().decode(signatureB64);
} catch (IllegalArgumentException ex) {
return NetExceptionResponseFactory.error(
Net_Response err = NetExceptionResponseFactory.error(
req,
WireCodes.Status.BAD_REQUEST,
"BAD_BASE64",
"Некорректный формат Base64 для ключа или подписи"
);
WsConnectionUtils.closeConnection(ctx, 4001, "Auth failed: bad base64");
return err;
}
// --- authNonce (challenge) мы сохранили в ctx.sessionPwd на шаге 1 ---
@@ -159,12 +179,14 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
boolean sigOk = Ed25519Util.verify(preimage, signature64, publicKey32);
if (!sigOk) {
return NetExceptionResponseFactory.error(
Net_Response err = NetExceptionResponseFactory.error(
req,
WireCodes.Status.UNVERIFIED,
"BAD_SIGNATURE",
"Подпись не прошла проверку"
);
WsConnectionUtils.closeConnection(ctx, 4001, "Auth failed: bad signature");
return err;
}
// --- Генерируем настоящий секрет сессии (sessionPwd) и sessionId ---
@@ -222,12 +244,14 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
dao.insert(activeSession);
} catch (SQLException e) {
log.error("Ошибка БД при создании новой сессии для loginId={}", loginId, e);
return NetExceptionResponseFactory.error(
Net_Response err = NetExceptionResponseFactory.error(
req,
WireCodes.Status.SERVER_DATA_ERROR,
"DB_ERROR_SESSION_CREATE",
"Ошибка БД при создании сессии"
);
WsConnectionUtils.closeConnection(ctx, 4001, "Auth failed: db error");
return err;
}
// --- обновляем контекст ---