11 KiB
AuthService
Обзор класса
Minecraft_launcher поддерживает три способа входа: офлайн-профиль без пароля, учётную запись
Ely.by и учётную запись Microsoft. AuthService закрывает первые два: это Yggdrasil-клиент Ely.by
плюс офлайн-режим. За третий отвечает MsaAuthService.
Результат любого способа — структура AuthResult, объявленная в этом же заголовке. Она содержит
ровно то, что подставляется в аргументы запуска вида ${auth_*}, поэтому дальше запуск игры идёт
по общему пути независимо от того, как пользователь вошёл.
Класс также умеет скачивать authlib-injector — библиотеку, которая перенаправляет обращения игры
к серверу авторизации на Ely.by.
Место в проекте и зависимости
Экземпляр создаётся и принадлежит LauncherBackend; других владельцев нет.
Заголовок подключает MsaAuthService — ради общей структуры AuthResult — и
GameLauncher через поля LaunchOptions, которые заполняются из результата
авторизации.
Требования сборки: Qt6::Core (QDateTime, QJsonObject, QString) и Qt6::Network
(QNetworkAccessManager).
Иерархия и роль
Наследует QObject: даёт мета-объектную систему, сигнал progress и владение по родителю.
Виртуальных методов базового класса не переопределяет.
Публичные структуры
AuthResult
Результат авторизации — то, что подставляется в ${auth_*} аргументы запуска.
| Поле | Тип | Описание |
|---|---|---|
ok |
bool |
Авторизация удалась |
twoFactorRequired |
bool |
Ely.by отклонил пароль с пометкой two factor — нужен одноразовый код |
licenseMissing |
bool |
Вход в Microsoft прошёл, но копии игры на аккаунте нет. Обрабатывается отдельно от прочих ошибок, потому что чинится только покупкой |
error |
QString |
Текст ошибки, когда ok равен false |
playerName |
QString |
Подставляется в ${auth_player_name} |
uuid |
QString |
Подставляется в ${auth_uuid}; hex без дефисов |
accessToken |
QString |
Подставляется в ${auth_access_token} |
clientToken |
QString |
Подставляется в ${clientid} |
userType |
QString |
Подставляется в ${user_type}; принимает значения legacy (офлайн), msa (Microsoft) и ELYBY |
refreshToken |
QString |
Только для аккаунтов Microsoft: продлевает сессию без ввода пароля |
xuid |
QString |
Только для Microsoft; подставляется в ${auth_xuid} |
expiresAt |
QDateTime |
Только для Microsoft: UTC-время, когда протухает accessToken |
Псевдонимы типов
AuthService::Callback — std::function<void(const AuthResult &)>. Все сетевые методы
асинхронные: колбэк вызывается ровно один раз и всегда в потоке GUI.
Публичные методы
explicit AuthService(QObject *parent = nullptr)
Создаёт сервис и его QNetworkAccessManager. Конструктор помечен explicit.
static AuthResult offline(const QString &nickname)
Готовит результат для офлайн-профиля без единого сетевого запроса. UUID выводится из ника так же,
как это делает сам Minecraft в офлайне: UUID.nameUUIDFromBytes(("OfflinePlayer:" + name) .getBytes(UTF_8)). Благодаря этому один и тот же ник всегда даёт один и тот же UUID, и прогресс
на сервере не теряется.
static QString generateClientToken()
Случайный clientToken лаунчера. Генерируется один раз на профиль и хранится вместе с ним:
Yggdrasil связывает выданный accessToken именно с этим значением.
void loginElyBy(const QString &login, const QString &password, const QString &clientToken, const QString &accessToken, Callback callback)
Полный цикл входа в Ely.by: сначала проверка имеющегося токена, затем его продление, и только при
неудаче — авторизация по паролю. Пароль можно оставить пустым, если уже есть рабочий
accessToken, — тогда пользователю не придётся вводить его заново.
По ходу работы испускает progress с описанием текущего шага. Результат приходит в callback
один раз; при ответе с пометкой двухфакторной аутентификации в нём выставлен
twoFactorRequired, и вызывающий код должен спросить у пользователя код и продолжить через
loginElyByWithTotp().
void loginElyByWithTotp(const QString &login, const QString &password, const QString &totp, const QString &clientToken, Callback callback)
Повтор авторизации с одноразовым кодом двухфакторной аутентификации. Пароль и код объединяются в одно поле в формате «пароль:код», как того требует Ely.by.
void ensureAuthlibInjector(const QString &targetDir, std::function<void(const QString &path, const QString &error)> callback)
Скачивает authlib-injector в targetDir, если его там ещё нет. Колбэк получает либо путь к
готовому jar, либо текст ошибки — заполнено всегда ровно одно из двух.
Библиотека нужна только для профилей Ely.by: она подключается к JVM аргументом -javaagent и
перенаправляет обращения игры к серверу авторизации.
Сигналы
progress(const QString &message)
Описание текущего шага авторизации. Испускается по ходу всех сетевых операций.
Обработчик показывает сообщение пользователю: в главном окне лаунчера оно попадает в плашку статуса и держится до следующего сообщения, потому что шаг может занять заметное время.
Владение и время жизни
Класс наследует QObject и принимает parent в конструкторе — родитель его и удалит.
QNetworkAccessManager создаётся в конструкторе с сервисом в роли родителя и уничтожается вместе
с ним.
Колбэки захватываются по значению и живут до своего единственного вызова. Уничтожение сервиса во время незавершённого запроса отменяет запрос вместе с менеджером сети — колбэк в этом случае не вызывается, поэтому захватывать в него сырые указатели на объекты с меньшим временем жизни, чем у сервиса, нельзя.
Потокобезопасность
Только поток GUI. Все сетевые методы асинхронные, и их колбэки вызываются в том же потоке, в котором создан сервис. Собственной синхронизации в классе нет.
Взаимодействие с другими классами
LauncherBackend вызывает методы входа при запуске игры и переправляет сигнал progress в
интерфейс. Полученный AuthResult он раскладывает по полям LaunchOptions, которые уходят в
GameLauncher. Путь, возвращённый ensureAuthlibInjector(), попадает в поле
authlibInjectorPath тех же параметров запуска.
Сохранением токенов между запусками занимается LauncherBackend: сам сервис ничего не пишет на
диск, кроме скачанного jar.
Внешнее взаимодействие
Сеть, исходящие запросы. Класс общается с сервером авторизации Ely.by через
QNetworkAccessManager. Формат — JSON поверх HTTPS, запросы инициирует всегда лаунчер. Внутренний
помощник postJson() разделяет три исхода: успешный ответ, ответ с кодом ошибки и транспортную
ошибку — последняя отдаётся отдельным параметром, чтобы отличить недоступную сеть от отказа
сервера.
Отдельным каналом идёт загрузка authlib-injector — обычная HTTPS-загрузка файла в
targetDir. Повторных попыток при неудаче класс не делает: решение о повторе принимает вызывающий
код.
Все сигналы и колбэки приходят в поток GUI.
Пример использования
auto *auth = new AuthService(this);
connect(auth, &AuthService::progress, this, &Backend::showStatus);
auth->loginElyBy(profile.login, profile.password,
profile.clientToken, profile.accessToken,
[this](const AuthResult &result) {
if (result.twoFactorRequired) {
emit twoFactorRequired(m_pendingProfileName);
return;
}
if (!result.ok) {
emit launchError(result.error);
return;
}
continueLaunch(result);
});
При создании этого документа использовался ИИ.