162 lines
10 KiB
Markdown
162 lines
10 KiB
Markdown
# MsaAuthService
|
|
|
|
## Обзор класса
|
|
|
|
`MsaAuthService` — авторизация через учётную запись Microsoft, то есть вход с лицензионной копией
|
|
игры. Это та же цепочка, что и в официальном лаунчере: OAuth2 → Xbox Live → XSTS → Minecraft
|
|
Services → проверка лицензии.
|
|
|
|
Результат отдаётся тем же `AuthResult`, что и [AuthService](AuthService.md) для Ely.by и офлайна,
|
|
поэтому запуск игры дальше идёт по общему пути и ничего не знает о способе входа.
|
|
|
|
Класс не показывает окно входа сам: страницу Microsoft открывает
|
|
[MicrosoftLoginDialog](../qml/MicrosoftLoginDialog.md) на стороне QML, а сервис даёт ему адрес
|
|
страницы и разбирает адрес возврата.
|
|
|
|
## Место в проекте и зависимости
|
|
|
|
Подключает `authservice.h` — ради общей структуры `AuthResult`. Экземпляр создаётся и принадлежит
|
|
[LauncherBackend](LauncherBackend.md).
|
|
|
|
Требования сборки: `Qt6::Core` (`QJsonObject`, `QString`, `QUrl`) и `Qt6::Network`
|
|
(`QNetworkAccessManager`).
|
|
|
|
Сам класс собирается всегда и не зависит от Qt WebEngine — от наличия WebEngine зависит только
|
|
окно, в котором показывается страница входа. Поэтому в сборке без WebEngine сервис существует, но
|
|
воспользоваться им нельзя: показать страницу нечем.
|
|
|
|
## Иерархия и роль
|
|
|
|
Наследует `QObject`: мета-объектная система, сигнал `progress`, владение по родителю. Виртуальных
|
|
методов базового класса не переопределяет.
|
|
|
|
## Псевдонимы типов
|
|
|
|
`MsaAuthService::Callback` — `std::function<void(const AuthResult &)>`. Колбэк вызывается ровно
|
|
один раз и в потоке GUI.
|
|
|
|
## Публичные методы
|
|
|
|
#### explicit MsaAuthService(QObject \*parent = nullptr)
|
|
|
|
Создаёт сервис и его `QNetworkAccessManager`. Конструктор помечен `explicit`.
|
|
|
|
#### static QString clientId()
|
|
|
|
Идентификатор приложения лаунчера в Microsoft. Игра ждёт его в `${clientid}`: официальный лаунчер
|
|
подставляет туда именно идентификатор приложения, а не случайный токен сессии.
|
|
|
|
#### static QUrl authorizationUrl()
|
|
|
|
Адрес страницы входа для встроенного окна браузера. В адресе уже собраны идентификатор клиента,
|
|
`redirect_uri` и параметр выбора аккаунта — вызывающему коду достаточно открыть эту ссылку.
|
|
|
|
#### static bool matchRedirect(const QUrl &url, QString \*code, QString \*error)
|
|
|
|
Отличает адрес, на который Microsoft возвращает управление после входа, от остальной навигации
|
|
внутри окна. Возвращает `true` только для адреса возврата.
|
|
|
|
При совпадении заполняется ровно одно из двух: `code` — код авторизации при успешном входе, либо
|
|
`error` — текст отказа. Разбор адреса живёт здесь, а не в QML, потому что правила совпадения
|
|
обязаны совпадать с теми, по которым сервис сам строит `redirect_uri`.
|
|
|
|
#### void loginWithCode(const QString &code, Callback callback)
|
|
|
|
Полный вход по коду, полученному из окна браузера. Проходит всю цепочку: обмен кода на токен
|
|
Microsoft, аутентификация в Xbox Live, авторизация XSTS, вход в Minecraft Services, проверка
|
|
лицензии и получение профиля.
|
|
|
|
По ходу испускает `progress` с описанием текущего шага — цепочка длинная, и без обратной связи
|
|
вход выглядел бы зависанием. Результат приходит в `callback` один раз.
|
|
|
|
Если вход прошёл, но копии игры на аккаунте нет, в результате выставлен `licenseMissing`: этот
|
|
случай чинится только покупкой, поэтому обрабатывается отдельно от прочих ошибок.
|
|
|
|
#### void loginWithRefreshToken(const QString &refreshToken, Callback callback)
|
|
|
|
Продление сессии без участия пользователя. Refresh-токен Microsoft живёт куда дольше суточного
|
|
токена Minecraft, так что при повторном запуске лаунчера обычно хватает его, и окно входа
|
|
показывать не приходится.
|
|
|
|
Проходит ту же цепочку, начиная с обмена refresh-токена. Неудача означает, что токен окончательно
|
|
протух и нужен полноценный вход через окно.
|
|
|
|
## Сигналы
|
|
|
|
#### progress(const QString &message)
|
|
|
|
Описание текущего шага цепочки авторизации.
|
|
|
|
Обработчик показывает сообщение пользователю. Сигнал особенно важен для этого класса: шагов пять,
|
|
каждый — отдельный сетевой запрос, и между ними проходит заметное время.
|
|
|
|
## Владение и время жизни
|
|
|
|
Класс наследует `QObject` и принимает `parent` — родитель его и удалит. `QNetworkAccessManager`
|
|
создаётся в конструкторе с сервисом в роли родителя.
|
|
|
|
Каждый шаг цепочки вызывается из колбэка предыдущего, поэтому незавершённый вход держит цепочку
|
|
захваченных колбэков до своего конца. Уничтожение сервиса посреди цепочки обрывает её вместе с
|
|
менеджером сети, и колбэк не вызывается.
|
|
|
|
## Потокобезопасность
|
|
|
|
Только поток GUI. Все методы асинхронные, колбэки и сигналы приходят в поток, где создан сервис.
|
|
|
|
## Взаимодействие с другими классами
|
|
|
|
`LauncherBackend` создаёт сервис, отдаёт в QML адрес страницы входа, принимает от окна код
|
|
авторизации и вызывает `loginWithCode()`. Полученные `refreshToken` и `expiresAt` он сохраняет в
|
|
профиле, чтобы при следующем запуске обойтись `loginWithRefreshToken()`.
|
|
|
|
Заполненный `AuthResult` дальше раскладывается по полям `LaunchOptions` для
|
|
[GameLauncher](GameLauncher.md) — ровно так же, как результат от [AuthService](AuthService.md).
|
|
|
|
Со стороны QML вход выглядит так: `LauncherBackend` испускает сигнал с адресом страницы, главное
|
|
окно открывает [MicrosoftLoginDialog](../qml/MicrosoftLoginDialog.md), тот следит за навигацией и
|
|
возвращает код обратно в бэкенд.
|
|
|
|
## Внешнее взаимодействие
|
|
|
|
**Сеть, исходящие запросы.** Класс последовательно обращается к пяти внешним службам: конечной
|
|
точке OAuth2 Microsoft, Xbox Live, XSTS, Minecraft Services и профильной конечной точке Minecraft.
|
|
Формат — JSON поверх HTTPS, кроме первого шага, где тело запроса отправляется как форма
|
|
(`postForm()`); дальше используются `postJson()` и `getJson()` с токеном в заголовке
|
|
авторизации.
|
|
|
|
Все запросы инициирует лаунчер. Ответ каждого шага разбирается тремя исходами: успех, ошибка с
|
|
кодом состояния и транспортная ошибка — последняя приходит отдельным параметром, чтобы отличить
|
|
недоступную сеть от отказа службы.
|
|
|
|
Повторных попыток класс не делает. Все сигналы и колбэки приходят в поток GUI.
|
|
|
|
## Пример использования
|
|
|
|
```cpp
|
|
auto *msa = new MsaAuthService(this);
|
|
connect(msa, &MsaAuthService::progress, this, &Backend::showStatus);
|
|
|
|
// 1. открыть окно браузера на этом адресе
|
|
emit microsoftLoginUrlReady(MsaAuthService::authorizationUrl());
|
|
|
|
// 2. когда окно поймало адрес возврата
|
|
QString code, error;
|
|
if (MsaAuthService::matchRedirect(url, &code, &error) && !code.isEmpty()) {
|
|
msa->loginWithCode(code, [this](const AuthResult &result) {
|
|
if (result.licenseMissing) {
|
|
emit loginFailed(tr("На аккаунте нет копии Minecraft"));
|
|
return;
|
|
}
|
|
if (!result.ok) {
|
|
emit loginFailed(result.error);
|
|
return;
|
|
}
|
|
storeSession(result);
|
|
});
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
При создании этого документа использовался ИИ.
|