Дизайн схемы тихого входа в небольшую программу

Апплет WeChat
Дизайн схемы тихого входа в небольшую программу

1. Предпосылки

Прежде всего, поговорим о том, как использовать возможности WeChat по идентификации пользователя при разработке мини-программ?

Официальный представитель WeChat предоставляет два вида логотипов:

  1. OpenIdЭто идентификатор пользователя для небольшой программы/общедоступной учетной записи, и разработчики могут идентифицировать пользователя с помощью этого идентификатора.
  2. UnionIdЭто идентификация пользователя для одного и того же субъекта WeChat апплета/общедоступной учетной записи/приложения, и разработчику необходимо связать предмет той же учетной записи на открытой платформе WeChat. Разработчики могут использоватьUnionId, чтобы обеспечить обмен данными между несколькими мини-программами, официальными учетными записями и даже приложениями.

Два идентификатора одного и того же пользователя являются постоянными для одного и того же апплета.Даже если пользователь удалит апплет, при следующем входе в апплет разработчик все равно сможет идентифицировать его по записям в фоновом режиме. Итак, как получитьOpenIdа такжеUnionIdШерстяная ткань?

Раннее программирование раннего (апрель 2018 г.)wx.getUserInfoИнтерфейс для получения информации о пользователе. Первоначальная цель разработки этого интерфейса — надеяться, что разработчики смогут вызывать этот интерфейс только тогда, когда им действительно нужна информация о пользователе (например, аватар, псевдоним, номер мобильного телефона и т. д.). Но многие разработчики для того, чтобы получитьUnionId, этот интерфейс будет вызываться непосредственно при запуске апплета, вызывая проблемы у пользователей при использовании апплета. Это сводится к следующим пунктам:

  1. Разработчик напрямую обращается к домашней странице апплетаwx.getUserInfoАвторизация и всплывающее окно для получения информации о пользователе заставят некоторых пользователей нажать кнопку «Отклонить».
  2. Если разработчик не принимает меры по поводу отказа пользователя от всплывающего окна, пользователь должен авторизовать псевдоним аватара и другую информацию, чтобы продолжить использование апплета, что заставит некоторых пользователей отказаться от использования апплета.
  3. У пользователей нет хорошего способа повторной авторизации.Хотя WeChat официально добавил страницу настроек, позволяющую пользователям выбирать повторную авторизацию, многие пользователи не знают, что они могут это сделать.

Чиновники WeChat также знают об этой проблеме и обновили три возможности для получения информации о пользователях:

  1. Используйте компоненты для получения информации о пользователе.
  2. Если пользователь соответствует определенным условиям, вы можете использоватьwx.loginПолученный код напрямую изменяется наunionId.
  3. wx.getUserInfoнет зависимостейwx.loginданные можно назвать.

В этой статье в основном рассказывается о второй способности, официальной WeChat.Поощряйте разработчиков получать разумный доступ, не беспокоя пользователейunionid, и просить пользователя использовать псевдоним аватара только в случае необходимости, который выводит понятия «автоматический вход» и «вход пользователя».

2. Что такое автоматический вход?

Апплет может легко получить идентификатор пользователя, предоставленный WeChat, с помощью возможности входа в систему, предоставленной официальным лицом WeChat, и быстро установить пользовательскую систему в апплете.

Многие разработчики будутwx.loginа такжеwx.getUserInfoСвязанный вызов используется как логин, по сутиwx.loginВход завершен,wx.getUserInfoПросто получите дополнительную информацию о пользователе.

существуетwx.loginполучатьcodeПосле этого он будет отправлен на бэкенд разработчика, а бэкенд разработчика через интерфейс пойдет на бэкенд WeChat в обмен наopenidа такжеsessionKey(сейчас будетunionidтакже вернуть), поставить пользовательский статус входа3rd_session(Этот бизнес называетсяauth-token) возвращается к внешнему интерфейсу, и поведение при входе завершено.wx.loginПоведение тихое, без авторизации, и пользователь не заметит.

wx.getUserInfoОн существует только для предоставления более качественных услуг, таких как получение номера мобильного телефона пользователя для регистрации в качестве участника или отображение псевдонима аватара и определение пола.unionIdОбъедините с существующими портретами пользователей в других официальных учетных записях, чтобы получить исторические данные.Таким образом, разработчику не нужно принудительно выполнять авторизацию при первом входе пользователя в апплет..

2.1 Последовательность процесса автоматического входа в систему

официально даноwx.loginЛучшие практики заключаются в следующем:

Английская аббревиатура автоматического входа в системуsilentLogin, код выглядит так:

  private async silentLogin(): Promise<void> {
    try {
      this.status.silentLogin.ing();

      // 获取临时登录凭证code
      const code = await getWxLoginCode();
      // 将code发送给服务端
      const res = await API.login(code);
      // 保存登录信息,如auth-token
      storage.setSync(constant.STORAGE_SESSION_KEY, res.data);

      this.status.silentLogin.success();
    } catch (error) {
      logger.error('静默登录失败', error);
      this.status.silentLogin.fail(error);
      throw error;
    }
  }

Его можно свести к следующим трем шагам:

  1. вызов апплетаwx.login()Получатьвременные учетные данные для входаcodeИ обратно на сервер разработчиков.
  2. вызов на стороне сервераauth.code2Sessionинтерфейс, в обмен наУникальный идентификатор пользователяOpenIDа такжесеансовый ключsession_key.
  3. Сервер разработчика может быть создан на основе идентификатора пользователя.Пользовательское состояние входа (например:auth-token), используемый для интерфейсных и внутренних взаимодействий в последующей бизнес-логикеИдентифицировать пользователя.

2.2 Проверка данных разработчика и расшифровка открытых данных

После успешного автоматического входа сервер WeChat выдастsession_keyНа сервер, и это будет использоваться, когда вам нужно получить открытые данные WeChat.

Чтобы обеспечить безопасность пользовательских данных, возвращаемых открытым интерфейсом, WeChat будет подписывать данные в виде открытого текста. Разработчики могут выполнять проверку подписи пакетов данных в соответствии с потребностями бизнеса для обеспечения целостности данных.

  1. Апплет вызывает интерфейс (например,wx.getUserInfo) при получении данных, если пользователь прошел авторизацию, интерфейс одновременно вернет следующие поля. Если пользователь не авторизован, сначала появится всплывающее окно пользователя. Пользователь щелкает, чтобы согласиться с авторизацией, и интерфейс одновременно возвращает следующие поля. И наоборот, если пользователь отказывает в авторизации, вызов завершится ошибкой.
Атрибуты Типы иллюстрировать
userInfo UserInfo Информационный объект пользователя, не содержитopenidи другую конфиденциальную информацию
rawData string Строка необработанных данных, за исключением конфиденциальной информации, используемой для расчета подписи.
signature string Используйте sha1 (rawData + sessionkey), чтобы получить строку для проверки информации о пользователе.
encryptedData string Зашифрованные данные с полной информацией о пользователе, включая конфиденциальные данные
iv string Начальный вектор для алгоритма шифрования
cloudID string Облачный идентификатор, соответствующий конфиденциальным данным, будет возвращен только тогда, когда апплет, разработанный в облаке, будет активирован, а открытые данные могут быть получены непосредственно через облачный вызов.
  1. разработчики будутsignature,rawDataОтправлено на сервер разработчика для проверки. Сервер использует соответствующий пользовательскийsession_keyПодпись вычисляется по тому же алгоритмуsignature2,Сравнениеsignatureа такжеsignature2Целостность данных можно проверить. Сервер разработчика сообщает интерфейсному разработчику, что данные заслуживают доверия, а информацию о пользователях можно безопасно использовать.
  2. Если разработчик хочет получить конфиденциальные данные (такие как OpenID, UniodId), тоencryptedDataа такжеivОтправлено на сервер разработчика, используется серверомsession_key(симметричный ключ дешифрования) дляСимметричное дешифрование, чтобы получить конфиденциальные данные для хранения и вернуть разработчикам интерфейса.

Уведомление:Поскольку пользователю необходимо активировать триггер, чтобы инициировать получение интерфейса номера мобильного телефона, эта функция не вызывается API (т.wx.getUserInfoномер мобильного телефона не может быть получен), необходимо использоватьbuttonКомпонент нажмите, чтобы вызвать. получатьencryptedDataа такжеiv, также отправляется на сервер разработчика, используется серверомsession_key(симметричный ключ дешифрования) дляСимметричное дешифрованиеполучить соответствующий номер мобильного телефона.

Следует отметить, что 23 февраля 2021 года команда WeChat выпустила«Вход в мини-программу, инструкции по настройке интерфейса, связанные с пользовательской информацией», со следующими корректировками:

  1. С 23 февраля 2021 г. поwx.loginУчетные данные для входа, полученные через интерфейс, могут быть обменены напрямую.unionID.
  2. Новые версии мини-программ, выпущенные после 13 апреля 2021 г., не могут пройтиwx.getUserInfoИнтерфейс получает личную информацию пользователя (аватар, псевдоним, пол и регион) и будет напрямую получить анонимные данные.getUserInfoИнтерфейс получает зашифрованныйopenIDа такжеunionIDВозможности данных не настраиваются.
  3. новыйgetUserProfileИнтерфейс (поддерживается с версии 2.10.4 базовой библиотеки) может получать информацию об аватаре, никнейме, поле и регионе пользователя.Каждый раз, когда разработчик получает личную информацию пользователя через этот интерфейс, пользователю необходимо ее подтвердить.

То есть разработчик вызывает через компонентwx.getUserInfoВсплывающее окно больше не будет всплыть, а напрямую вернуть анонимную личную информацию пользователя. Если вы хотите получить пользовательский аватар, псевдоним, пол, региональная информация, необходимостьМодернизациясталиwx.getUserProfileинтерфейс.

2.3 Срок действия session_key

Если разработчик сталкиваетсяsession_keyНеправильная проверка подписи или сбой расшифровки, пожалуйста, обратите внимание на следующее иsession_keyсоответствующие примечания.

  1. wx.loginПри вызове пользовательsession_keyможет быть обновлен, чтобы отобразить старыйsession_keyНедействительно (механизм обновления имеет самый короткий цикл, если один и тот же пользователь звонит несколько раз за короткий промежуток времени)wx.login, не каждый вызов приводит кsession_keyобновить). Разработчики должны звонить только тогда, когда им явно нужно повторно войти в систему.wx.login, прошло во времениauth.code2SessionСервер обновлений интерфейса сохраненsession_key.
  2. WeChat не будетsession_keyсообщить застройщику о сроке действия. Основываясь на поведении пользователя при использовании Мини-программы, мы будемsession_keyПродлить. Чем чаще пользователи используют мини-программы,session_keyЧем дольше срок действия.
  3. разработчик вsession_keyВ случае неудачи вы можете получить действительныйsession_key. использовать интерфейсwx.checkSessionможно проверитьsession_keyЯвляется ли он действительным, чтобы избежать многократного выполнения апплетом процесса входа в систему.
  4. Когда разработчики реализуют настраиваемый статус входа в систему, они могут рассмотреть возможность использованияsession_keyСрок действия используется в качестве срока действия собственного статуса входа в систему, также может быть реализована настраиваемая политика своевременности.

3 Архитектура входа

用户登录架构

Архитектура решения «Вход», как показано выше, абстрагирует все функции, связанные с входом в систему."сервисный уровень"(Этот проект называет его какsession),для«Бизнес-уровень»передача. В этой статье в основном описывается серый контент, а другие модули будут объяснены в следующей статье «Дизайн входа пользователя в мини-программу».

3.1 libs- Обеспечьте методы класса, связанные с входом в систему, для вызова "бизнес-уровня"

  1. упаковкаsessionКласс, предоставляющий методы класса для вызова «бизнес-уровня». В основном это следующие методы:
имя метода Функция сцены, которые будут использоваться
silentLogin Инициировать автоматический вход -
login Авторизоваться,silentLoginинкапсуляция метода Используется для инициации автоматического входа в систему при запуске апплета.
refreshLogin обновить состояние входа,silentLoginинкапсуляция метода Используется для инициации автоматического входа в систему по истечении срока действия статуса входа.
ensureSessionKey проверятьsessionKeyНезависимо от того, истек ли срок его действия, обновите статус входа в систему, когда он истечет. При привязке авторизованного номера мобильного телефона WeChat проверьте, не истек ли срок его действия.Если срок его действия истек, необходимо повторно авторизовать всплывающее окно.
  1. Декоратор:

    • fuse-line: Механизм прерывателя цепи, если его вызвать несколько раз в течение короткого периода времени, перестанет отвечать на определенный период времени, аналогично медленному запуску TCP. для решенияrefreshLogin,loginи другие методы параллельной обработки задач.
    • single-queue: в режиме с одной очередью одновременно допускается выполнение только одного сетевого запроса. После того, как запрос заблокирован, тот же запрос будет помещен в очередь, ожидая возврата текущего запроса, потребляя тот же результат. для решенияrefreshLogin,loginи другие методы параллельной обработки задач.

4. Когда вызывать тихий вход

4.1 Вызывается при запуске апплета

Поскольку в большинстве случаев приходится полагаться на логистику, при запуске апплета (app.onLaunch()) вызывает автоматический вход в систему, является наиболее распространенным средством. Здесь мы инкапсулируемloginФункция выглядит следующим образом, первый вызовwx.checkSessionсудитьsession_keyСрок действия истек, еслиsession_keyСрок действия не истек и существует локальноauth_tokenОпределяемый пользователем статус входа означает, что текущий статус автоматического входа все еще действителен и никаких других операций не требуется. В противном случае это означает, что состояние автоматического входа в систему недопустимо или новый пользователь никогда не инициировал автоматический вход в систему, тогда инициируется процесс автоматического входа.

public async login(): Promise<void> {
    // 调用wx.checkSession判断session_key是否过期
    const hasSession = await checkSession();

    // 本地已有可用登录态且session_key未过期,resolve。
    if (this.getAuthToken() && hasSession) return Promise.resolve();

    // 否则,发起静默登录
    await this.silentLogin();
}

Однако из-за собственного процесса запуска апплета функции обработчиков жизненного цикла приложений, страниц и компонентов не поддерживаются.Асинхронная блокировка. Поэтому весьма вероятно, что после загрузки страницы апплета процесс автоматического входа в систему не был завершен, что приведет к некоторым последующим операциям, зависящим от состояния входа (например, инициация запроса).ошибка.

4.2 Вызывается при инициации запроса интерфейса

Чтобы быть в безопасности, если некоторым интерфейсам необходимо иметь настраиваемый статус входа в систему для аутентификации, им необходимо перехватить запрос, когда запрос инициирован, проверить статус входа и обновить имя входа. Код обновления для входа выглядит следующим образом:

  public async refreshLogin(): Promise<void> {
    try {
      // 清除 Session
      this.clearSession();
      // 发起静默登录
      await this.silentLogin();
    } catch (error) {
      throw error;
    }
  }

Весь процесс показан на рисунке ниже:

  • запрос на перехват:
    1. Определить, требуется ли аутентификация: Когда запрос инициирован, перехватите запрос и определите, нужно ли добавить запрос.auth-token, если не требуется, инициируйте запрос напрямую. При необходимости перейдите к шагу 2.
    2. Определите, нужно ли вам инициировать автоматический вход в систему:судитьstorageсуществует вauth-token, если он не существует, инициируйте «Обновить вход».
    3. добавить заголовок запросаauth-token:Добавить кauth-token, инициируйте запрос.
  • Связь с сервером: инициировать запрос, сервер обрабатывает запрос и возвращает результат.
  • перехватить ответ: Разобрать код состояния
    1. Код состоянияAUTH_FAIL: Сервер возвращаетсяcodeЕсть две причины запуска этого сценария: во-первых, интерфейс должен быть аутентифицирован, но он не передается при инициации запроса.auth-token, дваauth-tokenИстекший. В это время последний запрос будет содержатьauth-tokenс локальным хранилищемauth-tokenСравните, если оно несовместимо, это означает, что состояние входа в систему было обновлено, а затем повторно инициируйте запрос напрямую. Если последовательно,Инициировать обновление входа в систему, получить новыйauth-tokenПосле повторной инициации запроса это действие незаметно для пользователя.
    2. Код состоянияUSER_WX_SESSIONKEY_EXPIRE: сервер возвращаетсяcodeЭто «Статус входа в систему истек», который представляет собой код состояния, настроенный для авторизованного пользователя с ошибкой входа в систему по номеру мобильного телефона. Если срок действия статуса входа истек, это означает, что код состояния, хранящийся на сервереsession_keyОн также просрочен, тогда зашифрованные данные, полученные по клику авторизованного номера мобильного телефона, отправляются на сервер для симметричного расшифрования.session_keyНеверный, невозможно расшифровать настоящий номер мобильного телефона. Поэтому необходимо повторно инициировать автоматический вход в систему, дождаться, пока пользователь снова нажмет кнопку авторизации, чтобы получить новые зашифрованные данные, а затем инициировать новый запрос на расшифровку.
    3. Код состояния другой:НапримерSuccessИли, если другие бизнес-запросы неверны, перехват не выполняется, и возвращается ответ, позволяющий анализировать бизнес-код.

4.3 Загадка wx.checkSession Strike

Основываясь на процессе, вызываемом при инициировании вышеуказанного запроса интерфейса, у многих людей возникнут сомнения, так как сервер вернетauth-tokenИстек срок действия кода состояния, почему бы не перехватить запрос перед отправкой, используйтеwx.checkSessionПроверяет ли интерфейс, не истек ли срок действия статуса входа (как показано на рисунке ниже, добавлены шаги в красном поле)?

Это потому, что мы обнаружили в ходе экспериментов, что вsession_keyистек,wx.checkSessionЕсть определенный шанс вернутьсяtrue. Увеличениеwx.checkSessionШаги не гарантируют на 100%, что срок действия статуса входа не истечет, и в будущем все равно необходимо будет обрабатывать различные коды статуса.

Есть также соответствующие отзывы от сообщества, которые не были учтены:

Итак, вывод такой:wx.checkSessionНадежность менее 100%.

На основании вышеизложенного нам необходимоsession_keyСрок действия делает некоторую отказоустойчивость:

  1. Инициировать потребность в использованииsession_keyперед запросом сделать это один разwx.checkSessionОперация в случае сбоя обновляет состояние входа.
  2. использование серверной частиsession_keyПосле сбоя расшифровки открытых данных возвращается определенный код ошибки (например:USER_WX_SESSIONKEY_EXPIRE), внешний интерфейс обновляет состояние входа.

4.4 Параллельная обработка

Мы знаем, что при запуске апплета различные средства мониторинга и отчетности должны получать личную информацию пользователя, которую можно получить только после «тихого входа в систему», поэтому несколькоloginпросить. В другом случае предположим, что новый пользователь заходит на сложную бизнес-страницу и одновременно инициирует пять разных бизнес-запросов.Бывает, что все эти пять запросов требуют аутентификации, тогда все пять запросов будут перехвачены и инициированы.refreshLoginпросить. Очевидно, что такой параллелизм неразумен.

Исходя из этого, мы разработали следующую схему:

  • режим одиночной очереди:

    1. запросить блокировку: Одновременно разрешен только один выполняющийся сетевой запрос.

    2. очередь ожидания: после того, как запрос заблокирован, тот же запрос будет помещен в очередь, и тот же результат будет использован после ожидания возврата текущего запроса.

  • автоматический выключатель: при многократном вызове в течение короткого промежутка времени перестает отвечать на запросы в течение определенного периода времени, аналогично медленному запуску TCP.

Как показано на рисунке выше, сначалаrefreshLoginЗапрос поставлен в очередь, и в очереди есть только один запрос.Отправьте запрос, и предохранитель считается равным 1. Сервер возвращает результат запроса и использует результат. Затем запустите другойrefreshLoginзапрос, в очереди только один запрос, отправьте запрос, а фьюз засчитывается как 2. Затем инициируются три последовательных запроса. Поскольку предыдущий запрос не был выполнен, три запроса помещаются в очередь и возвращается результат предыдущего запроса. Четыре запроса в очереди потребляют один и тот же результат. Предохранитель сбрасывается из-за срабатывания порога автоматического охлаждения.

Вышеуказанные две схемы вводятся через режим декоратора, код такой:refreshLoginФункция на самом делеslientLoginСлой инкапсуляции функций, который вызывается при инициализации интерфейса. в то время как вышеупомянутыйloginфункция такжеslientLoginУровень инкапсуляции функции, которая вызывается при запуске пользовательского апплета.

  @singleQueue({ name: 'refreshLogin' })
  @fuseLine({ name: 'refreshLogin' })
  public async refreshLogin(): Promise<void> {
    try {
      // 清除 Session
      this.clearSession();
      await this.silentLogin();
    } catch (error) {
      throw error;
    }
  }

На данный момент многие читатели могут еще не понять механизм предохранителя. Назначение предохранителя состоит в том, чтобы обеспечить защиту предохранителя для функции. все запросы будут отклонены в течение этого времени. Если ни один запрос не проходит в пределах порога автоматического охлаждения, сбросьте предохранитель. Код выглядит следующим образом:

export default function fuseLine({
  // 一次熔断前重试次数
  tryTimes = 3,

  // 重试间隔,单位 ms
  restoreTime = 5000,

  // 自动冷却阈值,单位 ms
  coolDownThreshold = 1000,

  // 名称
  name = 'unnamed',
}: {
  tryTimes?: number;
  restoreTime?: number;
  name?: string;
  coolDownThreshold?: number;
} = {}) {
  // 请求锁
  let fuseLocked = false;

  // 当前重试次数
  let fuseTryTimes = tryTimes;

  // 自动冷却
  let coolDownTimer;

  // 重置保险丝
  const reset = () => {
    fuseLocked = false;
    fuseTryTimes = tryTimes;
    logger.info(`${name}-保险丝重置`);
  };

  const request = async () => {
    if (fuseLocked) throw new Error(`${name}-保险丝已熔断,请稍后重试`);

    // 已达最大重试次数
    if (fuseTryTimes <= 0) {
      fuseLocked = true;

      // 重置保险丝
      setTimeout(() => reset(), restoreTime);

      throw new Error(`${name}-保险丝熔断!!`);
    }

    // 自动冷却系统
    if (coolDownTimer) clearTimeout(coolDownTimer);
    coolDownTimer = setTimeout(() => reset(), coolDownThreshold);

    // 允许当前请求通过保险丝,记录 +1
    fuseTryTimes = fuseTryTimes - 1;
    logger.info(`${name}-通过保险丝(${tryTimes - fuseTryTimes}/${tryTimes})`);
    return Promise.resolve();
  };

  return function(
    _target: Record<string, any>,
    _propertyName: string,
    descriptor: TypedPropertyDescriptor<(...args: any[]) => any>,
  ) {
    const method = descriptor.value;
    descriptor.value = async function(...args: any[]) {
      await request();
      if (method) return method.apply(this, args);
    };
  };
}

5. Наконец

Прочитав это, я полагаю, вы уже поняли разницу между «автоматическим входом в систему» ​​и «пользовательским входом в систему». «Тихий вход» — это процесс получения статуса входа в WeChat.Получив идентификатор пользователя, предоставленный WeChat, можно быстро установить пользовательскую систему в апплете. «Вход пользователя» — это процесс, в котором пользователь разрешает физическому лицу открывать данные, чтобы стать участником. Это относится к преобразованию из гостевого состояния в состояние участника с полномочиями на покупку и другие операции.

Это не одно и то же понятие, «логин пользователя» будет обсуждаться в следующей статье.«Дизайн архитектуры входа пользователя в мини-программу»объяснил в.