11 12 25
Сделал закрытие сесси сервером если пароль не верный
This commit is contained in:
parent
7f91c60d26
commit
dbf1f22bac
@ -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;
|
||||
}
|
||||
|
||||
// --- обновляем контекст ---
|
||||
|
||||
@ -0,0 +1,56 @@
|
||||
package server.ws;
|
||||
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import server.logic.ws_protocol.JSON.ActiveConnectionsRegistry;
|
||||
import server.logic.ws_protocol.JSON.ConnectionContext;
|
||||
|
||||
/**
|
||||
* Утилита для работы с WebSocket-подключениями.
|
||||
*/
|
||||
public final class WsConnectionUtils {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(WsConnectionUtils.class);
|
||||
|
||||
private WsConnectionUtils() {
|
||||
// utility
|
||||
}
|
||||
|
||||
/**
|
||||
* Корректно закрывает WebSocket-соединение:
|
||||
* - удаляет контекст из ActiveConnectionsRegistry;
|
||||
* - очищает ConnectionContext;
|
||||
* - закрывает сам WebSocket (если ещё открыт).
|
||||
*
|
||||
* @param ctx контекст соединения
|
||||
* @param statusCode код закрытия WebSocket (например, 1000, 4001)
|
||||
* @param reason причина закрытия (для логов/клиента)
|
||||
*/
|
||||
public static void closeConnection(ConnectionContext ctx, int statusCode, String reason) {
|
||||
if (ctx == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Session ws = ctx.getWsSession();
|
||||
|
||||
try {
|
||||
// Удаляем контекст из реестра активных соединений
|
||||
ActiveConnectionsRegistry.getInstance().remove(ctx);
|
||||
|
||||
// Чистим контекст
|
||||
ctx.reset();
|
||||
|
||||
// Закрываем WebSocket-сессию
|
||||
if (ws != null && ws.isOpen()) {
|
||||
try {
|
||||
ws.close(statusCode, reason);
|
||||
} catch (Exception e) {
|
||||
log.warn("Не удалось закрыть WebSocket-сессию (statusCode={}, reason={})", statusCode, reason, e);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Ошибка при закрытии WebSocket-соединения", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user