Python - странный вход в систему с кодом сканирования

Python внешний интерфейс Django

Недавно я столкнулся с спросом, когда работал над Sparrow (удобная система Mock, которая все еще находится на внутреннем тестировании 😁). Сервер Sparrow — это продукт, написанный с использованием Django 2.0, поэтомуВсе фоны кода в этой статье представляют собой среда Django 2.0 и языком Python 3.6.3, целое является Vue + Django + Sqlite.

Операции Sparrow обычно выполняются на веб-странице, а мобильный клиент часто используется для синхронизации некоторых простых данных. Итак, вот другой опыт и распространенные сценарии использования APP.

Вообще говоря, работа продукта в основном осуществляется на мобильном телефоне, затем клиент для ПК и веб-версия могут сканировать код для входа в систему через уже зарегистрированное мобильное приложение.

Текущая ситуация такова, что Sparrow чаще всего используется в веб-версии, поэтому мне нужно, чтобы пользователи мобильных приложений сохраняли операцию входа в систему, вводя имя пользователя и пароль, когда они уже вошли в веб-версию, и позвольте пользователям мобильного приложения сканировать веб-страницу QR-код для завершения входа в мобильное приложение.

Грубый вариант использования

##Идеи дизайна

Отсканируйте код, чтобы войти по URL-адресу

Прежде всего, сервер должен предоставить URL-адрес, к которому может получить доступ мобильное приложение (показанный двумерный код для сканирования приложения), этот URL-адрес должен

  1. уникальный идентификатор пользователя
  2. код верификации

Тогда приблизительный вид его URL:

frontend/account/quick_login?user_id=<user_id>&verification_code=<verification_code>

код верификации

Откуда взялся проверочный код?

Причина в том, что явно неразумно, если URL-адрес для сканирования логина постоянно действителен, а это означает, что, пока URL-адрес получен, любой может войти в учетную запись пользователя в любое время через этот URL-адрес, поэтому проверочный код требуется.

в то же время,Код должен сопровождаться время поколения, чтобы добитьсяКод подтверждения действителен в течение одной минутыОсобенность. Для этого создайте модель с внешним ключом пользователя:

class QuickLoginRecord(models.Model, Dictable):
    user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
    create_time = models.DateTimeField(auto_now_add=True)
    update_time = models.DateTimeField(auto_now=True)
    verification_code = models.CharField(max_length=32, null=True, default='')

Соответствующая база данных, созданная Django:

Когда генерируется код подтверждения, это, конечно, когда генерируется QR-код.Поэтому этот URL-адрес запрашивается не для мобильного терминала, а для внешнего интерфейса.Когда внешний интерфейс входит в систему, доступ к URL-адресу может напрямую передать информацию о пользователе, серверная часть создает запись QuickLoginRecord, получая информацию о пользователе.

Внешний доступ и URL-адрес кода подтверждения:

frontend/account/request_quick_login

Далее весь процесс (опуская подробности):

внутренний код

url.py

urlpatterns = [
	// ···
	path('frontend/account/quick_login', AccountAction.quick_login),
    path('frontend/account/request_quick_login', AccountAction.request_quick_login),
]

models.py

class QuickLoginRecord(models.Model, Dictable):
    user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
    create_time = models.DateTimeField(auto_now_add=True)
    update_time = models.DateTimeField(auto_now=True)
    verification_code = models.CharField(max_length=32, null=True, default='')

account_action.py

Если вы не хотите видеть код здесь, кратко опишите процесс:

def request_quick_login(request: HttpRequest)

  1. Указывает, что HTTPMethod должен иметь доступ GET.
  2. Получить параметр user_id в запросе
  3. Запрос записей QuickLoginRecord по user_id
  4. Если результата запроса нет, создается новая запись QuickLoginRecord, связанные пользовательские настройки, код_верификации (create_time update_time и автоматически предоставляется в QuickLoginRecordDao)
  5. Если результат запрошен, обновите поле Verification_Code (update_time автоматически устанавливается в QuickLoginrecorddao)
  6. Вернуть успех
    @track(AccountRequestQuickLogin)
    def request_quick_login(request: HttpRequest):
        if request.method != CommonData.Method.GET.value:
            return HttpResponse(Response.methodInvalidResponse().toJson(), content_type='application/json')
        user = request.user
        r = QuickLoginRecordDao.get_record_with_user_id(user.id)
        if r is not None:
            r.verification_code = str(uuid.uuid1())
            QuickLoginRecordDao.update_record(r)
            response = Response(Success, 'Success', {'verification_code': r.verification_code})
            return HttpResponse(response.toJson(), content_type='application/json')
        else:
            record = QuickLoginRecord()
            record.user = user
            record.verification_code = str(uuid.uuid1())
            QuickLoginRecordDao.add_record(record)
            response = Response(Success, 'Success', {'verification_code': record.verification_code})
            return HttpResponse(response.toJson(), content_type='application/json')

def quick_login(request: HttpRequest):

  1. Указывает, что HTTPMethod должен иметь доступ GET.
  2. Получить user_id в запросе
  3. Получить код подтверждения в запросе
  4. Получить QuickLoginRecord с кодом подтверждения
  5. Если запись не существует, в ней указано, что коды не существуют или срок их действия истек.
  6. Если он существует, сравните поле Update_Time, чтобы определить, превысило ли он 60 секунд
  7. Более 60 секунд для возврата «Срок действия кода подтверждения истек»
  8. Войдите в систему в течение 60 секунд
  9. Вернуть успех
	@track(AccountQuickLogin)
    def quick_login(request: HttpRequest):
        if request.method != CommonData.Method.GET.value:
            return HttpResponse(Response.methodInvalidResponse().toJson(), content_type='application/json')
        user_id = request.GET.get('user_id')
        verification_code = request.GET.get('verification_code')

        record = QuickLoginRecordDao.get_record_with_verification_code(verification_code)
        if record is None:
            response = Response(QuickLoginFailed, '验证码不存在或已过期', {})
            return HttpResponse(response.toJson(), content_type='application/json')

        now = datetime.now(timezone.utc)
        offset = (now - record.update_time).seconds

        if offset > 60:
            response = Response(QuickLoginFailed, '验证码已过期', {})
            return HttpResponse(response.toJson(), content_type='application/json')

        user = AccountDao.get_user_with_id(user_id)
        if user is None:
            response = Response(QuickLoginFailed, '用户不存在', {})
            return HttpResponse(response.toJson(), content_type='application/json')
        user.backend = 'django.contrib.auth.backends.ModelBackend'
        print('用户 ' + user.username + ' 尝试登录')
        auth.login(request, user)
        accountInfo = User.objects.get(id=user.id)
        response = Response(Success, 'Success', {'id': accountInfo.id,
                                                 'username': accountInfo.username,
                                                 'email': accountInfo.email})
        return HttpResponse(response.toJson(), content_type='application/json')

quick_login_record_dao.py

class QuickLoginRecordDao:
    @staticmethod
    def add_record(record):
        record.save()

    @staticmethod
    def get_record_with_user_id(user_id):
        try:
            record = QuickLoginRecord.objects.get(user_id=user_id)
            return record
        except:
            return None

    @staticmethod
    def update_record(record):
        result = QuickLoginRecord.objects.filter(id=record.id).update(
            verification_code=record.verification_code,
            update_time=datetime.datetime.now())
        if result > 0:
            return True
        else:
            return False

    @staticmethod
    def get_record_with_verification_code(code):
        try:
            record = QuickLoginRecord.objects.get(verification_code=code)
            return record
        except:
            return None

интерфейсный код

Эффект фронтенда следующий:

В состоянии входа в систему нажмите кнопку «Вход с кодом сканирования клиента» в правом верхнем углу, и появится QR-код.

Для динамических эффектов, модальных окон и т.п. я не буду приводить слишком много кода, а только сосредоточусь на коде основного процесса:

Кнопки панели навигации

<p class="nav-item" v-if="account.status">
	<button class="button is-primary" type="submit" @click="openModalImage">客户端扫码登录		</button>
</p>

функция openModalImage

  openModalImage () {
    const imageModal = openImageModal()
    imageModal.loading = true
    var baseUrl = window.location.protocol + '//' + window.location.host
    request('/frontend/account/request_quick_login', {
      method: 'get'
    }).then((response) => {
      var verificationCode = response.data.verification_code
      var url = baseUrl + '/frontend/account/quick_login' + '?' +
        'verification_code=' + verificationCode + '&' +
      'user_id=' + this.accountInfo.id

      QRCode.toDataURL(url)
        .then(url => {
          imageModal.imgUrl = url
          imageModal.loading = false
          imageModal.$children[0].active()
        })
        .catch(err => {
          console.error(err)
        })
    }).catch((response) => {
      notification.toast({
        message: response.message,
        type: 'danger',
        duration: 2000
      })
    })
  }

iOS-код

Код iOS не будет отображаться, просто отсканируйте код, чтобы получить доступ к URL-адресу в QR-коде, а затем добавьте некоторые недопустимые суждения об URL-адресах.

Суммировать

Пройдя весь процесс, вы можете почувствовать, что это похоже на оплату скан-кодом Alipay. Предоставьте клиенту регулярно обновляемый QR-код, чтобы он мог отсканировать его для входа в систему.

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