Доделал API функции для авторификации и работы с сессиями сервер и документ для разработчиков по

Авторификациии и серверам

Всё работает
This commit is contained in:
AidarKC
2026-03-27 22:06:19 +03:00
parent 51de9779e3
commit 1aabcf4d80
23 changed files with 1117 additions and 574 deletions
@@ -185,7 +185,10 @@ public final class JsonInboundProcessor {
requestId,
WireCodes.Status.BAD_REQUEST,
"BAD_REQUEST_FORMAT",
"Некорректный формат запроса: не удалось распарсить поля payload"
NetExceptionResponseFactory.detailedMessage(
"Некорректный формат запроса: не удалось распарсить поля payload",
mapErr
)
);
String out = writeResponse(err);
@@ -216,7 +219,10 @@ public final class JsonInboundProcessor {
requestId,
WireCodes.Status.INTERNAL_ERROR,
"INTERNAL_HANDLER_ERROR",
"Неожиданная ошибка при обработке операции: " + op
NetExceptionResponseFactory.detailedMessage(
"Неожиданная ошибка при обработке операции: " + op,
handlerError
)
);
String out = writeResponse(err);
@@ -254,7 +260,7 @@ public final class JsonInboundProcessor {
requestId,
WireCodes.Status.INTERNAL_ERROR,
"INTERNAL_ERROR",
"Внутренняя ошибка сервера"
NetExceptionResponseFactory.detailedMessage("Внутренняя ошибка сервера", e)
);
String out = writeResponse(err);
@@ -281,6 +287,7 @@ public final class JsonInboundProcessor {
* "op": ...,
* "requestId": ...,
* "status": ...,
* "ok": true|false,
* "payload": { ... }
* }
*/
@@ -293,18 +300,39 @@ public final class JsonInboundProcessor {
String op = full.hasNonNull("op") ? full.get("op").asText() : null;
String requestId = full.hasNonNull("requestId") ? full.get("requestId").asText() : null;
int status = full.hasNonNull("status") ? full.get("status").asInt() : 0;
boolean ok = status >= 200 && status < 300;
String error = null;
if (!ok) {
if (full.hasNonNull("error")) error = full.get("error").asText();
else if (full.hasNonNull("code")) error = full.get("code").asText();
}
String message = null;
if (!ok && full.hasNonNull("message")) {
message = full.get("message").asText();
}
// Удаляем базовые поля и payload из "полного" объекта,
// всё остальное отправляем внутрь payload.
full.remove("op");
full.remove("requestId");
full.remove("status");
full.remove("ok");
full.remove("error");
full.remove("code");
if (!ok) full.remove("message");
full.remove("payload");
ObjectNode root = JSON_MAPPER.createObjectNode();
if (op != null) root.put("op", op); else root.putNull("op");
if (requestId != null) root.put("requestId", requestId); else root.putNull("requestId");
root.put("status", status);
root.put("ok", ok);
if (!ok) {
if (error != null) root.put("error", error); else root.putNull("error");
if (message != null) root.put("message", message); else root.putNull("message");
}
// payload — это всё, что осталось от full (может быть пустым объектом {})
root.set("payload", full);
@@ -321,7 +349,10 @@ public final class JsonInboundProcessor {
return "{\"op\":\"" + safe(response != null ? response.getOp() : null) +
"\",\"requestId\":\"" + safe(response != null ? response.getRequestId() : null) +
"\",\"status\":" + (response != null ? response.getStatus() : 500) +
",\"payload\":{\"code\":\"SERIALIZATION_ERROR\",\"message\":\"Ошибка сериализации ответа\"}}";
",\"ok\":false" +
",\"error\":\"SERIALIZATION_ERROR\"" +
",\"message\":\"Ошибка сериализации ответа\"" +
",\"payload\":{}}";
}
}
@@ -345,4 +376,4 @@ public final class JsonInboundProcessor {
return String.valueOf(o);
}
}
}
}
@@ -3,11 +3,8 @@ package server.logic.ws_protocol.JSON.entyties;
/**
* Ответ с ошибкой (любой отказ).
*.
* В payload будет:
* {
* "code": "...",
* "message": "..."
* }
* В wire-формате error/message поднимаются на верхний уровень,
* а payload остаётся объектом.
*/
public class Net_Exception_Response extends Net_Response {
@@ -10,6 +10,7 @@ package server.logic.ws_protocol.JSON.entyties;
* "op": "...",
* "requestId": "...",
* "status": 200,
* "ok": true,
* "payload": { ... } // и для успеха, и для ошибки
* }
*/
@@ -29,6 +30,6 @@ public abstract class Net_Response extends Net_Request {
}
public boolean isOk() {
return status == 200;
return status >= 200 && status < 300;
}
}
@@ -49,6 +49,7 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
private static final Logger log = LoggerFactory.getLogger(Net_CreateAuthSession__Handler.class);
private static final SecureRandom RANDOM = new SecureRandom();
private static final long CLOSE_AFTER_ERROR_DELAY_MS = 75L;
public static final long ALLOWED_SKEW_MS = 30_000L;
@@ -68,7 +69,7 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
"NO_STEP1_CONTEXT",
"Шаг 1 авторизации не был корректно выполнен для данного соединения"
);
WsConnectionUtils.closeConnection(ctx, 4001, "Auth failed: no step1 context or bad auth state");
closeConnectionAfterErrorResponse(ctx, 4001, "Auth failed: no step1 context or bad auth state");
return err;
}
@@ -82,7 +83,7 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
"EMPTY_LOGIN",
"Пустой login"
);
WsConnectionUtils.closeConnection(ctx, 4001, "Auth failed: empty login");
closeConnectionAfterErrorResponse(ctx, 4001, "Auth failed: empty login");
return err;
}
if (!login.equals(loginFromContext)) {
@@ -92,7 +93,7 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
"LOGIN_MISMATCH",
"login не соответствует контексту AuthChallenge"
);
WsConnectionUtils.closeConnection(ctx, 4001, "Auth failed: login mismatch");
closeConnectionAfterErrorResponse(ctx, 4001, "Auth failed: login mismatch");
return err;
}
@@ -106,7 +107,7 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
"DB_ERROR_USER_LOOKUP",
"Ошибка БД при получении пользователя"
);
WsConnectionUtils.closeConnection(ctx, 4001, "Auth failed: db user lookup");
closeConnectionAfterErrorResponse(ctx, 4001, "Auth failed: db user lookup");
return err;
}
if (user == null) {
@@ -116,7 +117,7 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
"USER_NOT_FOUND",
"Пользователь не найден"
);
WsConnectionUtils.closeConnection(ctx, 4001, "Auth failed: user not found");
closeConnectionAfterErrorResponse(ctx, 4001, "Auth failed: user not found");
return err;
}
@@ -127,7 +128,7 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
"NO_LOGIN",
"Для пользователя не задан login в БД"
);
WsConnectionUtils.closeConnection(ctx, 4001, "Auth failed: no login");
closeConnectionAfterErrorResponse(ctx, 4001, "Auth failed: no login");
return err;
}
@@ -139,7 +140,7 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
"EMPTY_STORAGE_PWD",
"Пустой storagePwd"
);
WsConnectionUtils.closeConnection(ctx, 4001, "Auth failed: empty storagePwd");
closeConnectionAfterErrorResponse(ctx, 4001, "Auth failed: empty storagePwd");
return err;
}
@@ -151,7 +152,7 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
"EMPTY_SESSION_KEY",
"Пустой sessionKey"
);
WsConnectionUtils.closeConnection(ctx, 4001, "Auth failed: empty session key");
closeConnectionAfterErrorResponse(ctx, 4001, "Auth failed: empty session key");
return err;
}
@@ -165,7 +166,7 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
"UNSUPPORTED_KEY_ALGORITHM",
"sessionKey prefix is not supported"
);
WsConnectionUtils.closeConnection(ctx, 4001, "Auth failed: unsupported session key algorithm");
closeConnectionAfterErrorResponse(ctx, 4001, "Auth failed: unsupported session key algorithm");
return err;
} catch (IllegalArgumentException e) {
Net_Response err = NetExceptionResponseFactory.error(
@@ -174,7 +175,7 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
"BAD_BASE64",
"Некорректный формат sessionKey"
);
WsConnectionUtils.closeConnection(ctx, 4001, "Auth failed: bad session key format");
closeConnectionAfterErrorResponse(ctx, 4001, "Auth failed: bad session key format");
return err;
}
@@ -186,7 +187,7 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
"EMPTY_SIGNATURE",
"Пустая цифровая подпись"
);
WsConnectionUtils.closeConnection(ctx, 4001, "Auth failed: empty signature");
closeConnectionAfterErrorResponse(ctx, 4001, "Auth failed: empty signature");
return err;
}
@@ -200,7 +201,7 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
"TIME_SKEW",
"Время клиента отличается от сервера более чем на 30 секунд"
);
WsConnectionUtils.closeConnection(ctx, 4001, "Auth failed: time skew");
closeConnectionAfterErrorResponse(ctx, 4001, "Auth failed: time skew");
return err;
}
@@ -217,7 +218,7 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
"NO_DEVICE_KEY",
"Отсутствует deviceKey у пользователя"
);
WsConnectionUtils.closeConnection(ctx, 4001, "Auth failed: no deviceKey");
closeConnectionAfterErrorResponse(ctx, 4001, "Auth failed: no deviceKey");
return err;
}
@@ -230,7 +231,7 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
"EMPTY_AUTH_NONCE",
"Пустой authNonce"
);
WsConnectionUtils.closeConnection(ctx, 4001, "Auth failed: empty authNonce");
closeConnectionAfterErrorResponse(ctx, 4001, "Auth failed: empty authNonce");
return err;
}
if (!authNonce.equals(authNonceFromReq)) {
@@ -240,7 +241,7 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
"AUTH_NONCE_MISMATCH",
"authNonce не соответствует контексту AuthChallenge"
);
WsConnectionUtils.closeConnection(ctx, 4001, "Auth failed: authNonce mismatch");
closeConnectionAfterErrorResponse(ctx, 4001, "Auth failed: authNonce mismatch");
return err;
}
@@ -252,7 +253,7 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
"EMPTY_DEVICE_KEY",
"Пустой deviceKey"
);
WsConnectionUtils.closeConnection(ctx, 4001, "Auth failed: empty deviceKey");
closeConnectionAfterErrorResponse(ctx, 4001, "Auth failed: empty deviceKey");
return err;
}
deviceKeyFromReq = deviceKeyFromReq.trim();
@@ -265,7 +266,7 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
"DEVICE_KEY_NOT_ACTUAL",
"device_key не соответствует актуальной версии"
);
WsConnectionUtils.closeConnection(ctx, 4001, "Auth failed: device key mismatch");
closeConnectionAfterErrorResponse(ctx, 4001, "Auth failed: device key mismatch");
return err;
}
@@ -287,7 +288,7 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
"UNSUPPORTED_KEY_ALGORITHM",
"deviceKey algorithm is not supported"
);
WsConnectionUtils.closeConnection(ctx, 4001, "Auth failed: unsupported device key algorithm");
closeConnectionAfterErrorResponse(ctx, 4001, "Auth failed: unsupported device key algorithm");
return err;
} catch (IllegalArgumentException ex) {
Net_Response err = NetExceptionResponseFactory.error(
@@ -296,7 +297,7 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
"BAD_BASE64",
"Некорректный формат Base64 для ключа или подписи"
);
WsConnectionUtils.closeConnection(ctx, 4001, "Auth failed: bad base64");
closeConnectionAfterErrorResponse(ctx, 4001, "Auth failed: bad base64");
return err;
}
@@ -307,7 +308,7 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
"BAD_SIGNATURE",
"Подпись не прошла проверку"
);
WsConnectionUtils.closeConnection(ctx, 4001, "Auth failed: bad signature");
closeConnectionAfterErrorResponse(ctx, 4001, "Auth failed: bad signature");
return err;
}
@@ -364,7 +365,7 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
"DB_ERROR_SESSION_CREATE",
"Ошибка БД при создании сессии"
);
WsConnectionUtils.closeConnection(ctx, 4001, "Auth failed: db error");
closeConnectionAfterErrorResponse(ctx, 4001, "Auth failed: db error");
return err;
}
@@ -414,4 +415,16 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
RANDOM.nextBytes(buf);
return Base64Ws.encode(buf);
}
private static void closeConnectionAfterErrorResponse(ConnectionContext ctx, int statusCode, String reason) {
if (ctx == null) return;
new Thread(() -> {
try {
Thread.sleep(CLOSE_AFTER_ERROR_DELAY_MS);
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
}
WsConnectionUtils.closeConnection(ctx, statusCode, reason);
}, "CreateAuthSessionClose-" + System.identityHashCode(ctx)).start();
}
}
@@ -66,7 +66,7 @@ public class Net_ListSessions_Handler implements JsonMessageHandler {
info.setSessionId(s.getSessionId());
info.setClientInfoFromClient(s.getClientInfoFromClient());
info.setClientInfoFromRequest(s.getClientInfoFromRequest());
info.setLastAuthirificatedAtMs(s.getLastAuthirificatedAtMs());
info.setLastAuthenticatedAtMs(s.getLastAuthirificatedAtMs());
String ip = s.getClientIp();
String geo = GeoLookupService.resolveCountryCityOrIpWithCache(ip);
@@ -83,4 +83,4 @@ public class Net_ListSessions_Handler implements JsonMessageHandler {
return resp;
}
}
}
@@ -17,7 +17,7 @@ import java.util.List;
* "clientInfoFromClient": "...",
* "clientInfoFromRequest": "...",
* "geo": "Country, City" | "unknown",
* "lastAuthirificatedAtMs": 1733310000000
* "lastAuthenticatedAtMs": 1733310000000
* },
* ...
* ]
@@ -56,7 +56,7 @@ public class Net_ListSessions_Response extends Net_Response {
private String geo;
/** Время последней успешной авторизации/refresh (мс с 1970-01-01). */
private long lastAuthirificatedAtMs;
private long lastAuthenticatedAtMs;
// --- getters / setters ---
@@ -92,12 +92,12 @@ public class Net_ListSessions_Response extends Net_Response {
this.geo = geo;
}
public long getLastAuthirificatedAtMs() {
return lastAuthirificatedAtMs;
public long getLastAuthenticatedAtMs() {
return lastAuthenticatedAtMs;
}
public void setLastAuthirificatedAtMs(long lastAuthirificatedAtMs) {
this.lastAuthirificatedAtMs = lastAuthirificatedAtMs;
public void setLastAuthenticatedAtMs(long lastAuthenticatedAtMs) {
this.lastAuthenticatedAtMs = lastAuthenticatedAtMs;
}
}
}
}
@@ -91,7 +91,7 @@ public class Net_GetFriendsLists_Handler implements JsonMessageHandler {
req,
WireCodes.Status.INTERNAL_ERROR,
"INTERNAL_ERROR",
"Внутренняя ошибка сервера"
NetExceptionResponseFactory.detailedMessage("Внутренняя ошибка сервера при GetFriendsLists", e)
);
}
}
@@ -111,4 +111,4 @@ public class Net_GetFriendsLists_Handler implements JsonMessageHandler {
}
}
}
}
}
@@ -178,8 +178,8 @@ public class Net_AddUser_Handler implements JsonMessageHandler {
req,
WireCodes.Status.INTERNAL_ERROR,
"INTERNAL_ERROR",
"Внутренняя ошибка сервера"
NetExceptionResponseFactory.detailedMessage("Внутренняя ошибка сервера при AddUser", e)
);
}
}
}
}
@@ -77,8 +77,8 @@ public class Net_GetUser_Handler implements JsonMessageHandler {
req,
WireCodes.Status.INTERNAL_ERROR,
"INTERNAL_ERROR",
"Внутренняя ошибка сервера"
NetExceptionResponseFactory.detailedMessage("Внутренняя ошибка сервера при GetUser", e)
);
}
}
}
}
@@ -70,8 +70,8 @@ public class Net_SearchUsers_Handler implements JsonMessageHandler {
req,
WireCodes.Status.INTERNAL_ERROR,
"INTERNAL_ERROR",
"Внутренняя ошибка сервера"
NetExceptionResponseFactory.detailedMessage("Внутренняя ошибка сервера при SearchUsers", e)
);
}
}
}
}
@@ -83,8 +83,8 @@ public class Net_GetUserParam_Handler implements JsonMessageHandler {
req,
WireCodes.Status.INTERNAL_ERROR,
"INTERNAL_ERROR",
"Внутренняя ошибка сервера"
NetExceptionResponseFactory.detailedMessage("Внутренняя ошибка сервера при GetUserParam", e)
);
}
}
}
}
@@ -84,8 +84,8 @@ public class Net_ListUserParams_Handler implements JsonMessageHandler {
req,
WireCodes.Status.INTERNAL_ERROR,
"INTERNAL_ERROR",
"Внутренняя ошибка сервера"
NetExceptionResponseFactory.detailedMessage("Внутренняя ошибка сервера при ListUserParams", e)
);
}
}
}
}
@@ -181,8 +181,8 @@ public class Net_UpsertUserParam_Handler implements JsonMessageHandler {
req,
WireCodes.Status.INTERNAL_ERROR,
"INTERNAL_ERROR",
"Внутренняя ошибка сервера"
NetExceptionResponseFactory.detailedMessage("Внутренняя ошибка сервера при UpsertUserParam", e)
);
}
}
}
}
@@ -9,6 +9,8 @@ import server.logic.ws_protocol.JSON.entyties.Net_Request;
*/
public final class NetExceptionResponseFactory {
private static final int MAX_DETAIL_LEN = 240;
private NetExceptionResponseFactory() {
// запрет на создание объектов
}
@@ -35,6 +37,39 @@ public final class NetExceptionResponseFactory {
return resp;
}
public static String detailedMessage(String prefix, Throwable error) {
String safePrefix = prefix == null || prefix.isBlank()
? "Внутренняя ошибка сервера"
: prefix.trim();
if (error == null) {
return safePrefix;
}
String className = error.getClass().getSimpleName();
if (className == null || className.isBlank()) {
className = error.getClass().getName();
}
String detail = error.getMessage();
StringBuilder sb = new StringBuilder(safePrefix)
.append(": ")
.append(className);
if (detail != null && !detail.isBlank()) {
sb.append(": ").append(detail.trim());
}
String message = sb.toString()
.replace('\n', ' ')
.replace('\r', ' ');
if (message.length() <= MAX_DETAIL_LEN) {
return message;
}
return message.substring(0, MAX_DETAIL_LEN - 3) + "...";
}
/**
* Вариант для случаев, когда NetRequest ещё не распарсен,
* но мы уже знаем op и requestId (или они null).
@@ -53,4 +88,4 @@ public final class NetExceptionResponseFactory {
resp.setMessage(message);
return resp;
}
}
}