Простая реализация входа по скан-коду

задняя часть Vue.js
Простая реализация входа по скан-коду

предисловие

В этой статье будет представлено на основеSpringBoot + Vue + AndroidРеализован вход по скан-кодуdemoОбщая идея, полный код выложен наGitHub.WebАдрес терминального опыта:http://42.192.64.144/qr,apkссылка для скачивания:GitHub.com/Чжан Цзивэй1…. Имя пользователя: не пустое, пароль: 123456, см. эффект в конце статьи, если в общей реализации есть какие-либо несоответствия, добро пожаловать на обмен и обсуждение, а также реализовать некоторые ссылкиКаков принцип сканирования QR-кода и входа в систему?.

Описание Проекта

задняя часть:SpringBoot,Redis.

внешний интерфейс:Vue,Vue Router,VueX,Axios,vue-qr,ElemntUI.

Андроид:ZXing,XUI,YHttp.

Реализовать идеи

Общий вход с кодом сканирования иOAuth2.0Логика проверки аналогична следующим образом:

image-20210921205657426

Когда пользователь выбирает сканирование кода для входа в систему, его можно увидеть какA: Внешний интерфейс отправляет запрос на авторизацию и ожидаетappСканировать код.

использование пользователемappСканирование кода можно рассматривать какB: отсканировать код для авторизации и вернуть временныйTokenдля вторичной аттестации.

пользователь вappВыполнение входа с подтверждением можно рассматривать какC: Для подтверждения входа авторизованный пользователь находится вWebбоковой логин.

Бэкэнд возвращает формал после того, как пользователь подтвердит входTokenможно рассматривать как шагиD.

Последующий интерфейс в соответствии с формальнымTokenДоступ к фоновому интерфейсу, официально вWebКонец операции может быть рассмотренаEиF.

Причины вторичной сертификации

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

Этапы реализации

  1. Пользователь заходит на веб-страницу и выбирает сканирование кода для входа в систему.

    Когда пользователь решит отсканировать код для входа в систему, на серверную часть будет отправлен запрос на создание QR-кода, и серверная часть сгенерирует QR-код.UUIDи сохранить вRedis(фиксированное время действия), устанавливается статусUNUSED(неиспользованный) состояние, еслиRedisкеш истек, тоEXPIRE(Просрочено), клиентская часть генерирует QR-код в соответствии с содержимым, возвращенным серверной частью, и устанавливает таймер.UUID, отправьте запрос на серверную часть, получите статус QR-кода и обновите содержимое, отображаемое в интерфейсе.

    Создайте внутренний интерфейс QR-кода:

    /**
     * 生成二维码内容
     *
     * @return 结果
     */
    @GetMapping("/generate")
    public BaseResult generate() {
        String code = IdUtil.simpleUUID();
        redisCache.setCacheObject(code, CodeUtils.getUnusedCodeInfo(), 
                                  DEFAULT_QR_EXPIRE_SECONDS, TimeUnit.SECONDS);
        return BaseResult.success(GENERATE_SUCCESS, code);
    }
    

    Внешний интерфейс получает контент и генерирует QR-код:

    getToken() {
        this.codeStatus = 'EMPTY'
        this.tip = '正在获取登录码,请稍等'
        // 有效时间 60 秒
        this.effectiveSeconds = 60
        clearInterval(this.timer)
        request({
            method: 'get',
            url: '/code/generate'
        }).then((response) => {
            // 请求成功, 设置二维码内容, 并更新相关信息
            this.code = `${HOST}/code/scan?code=${response.data}`
            this.codeStatus = 'UNUSED'
            this.tip = '请使用手机扫码登录'
            this.timer = setInterval(this.getTokenInfo, 2000)
        }).catch(() => {
            this.getToken()
        })
    }
    

    Интерфейс для бэкенда для возврата информации о статусе QR-кода:

    /**
     * 获取二维码状态信息
     *
     * @param code 二维码
     * @return 结果
     */
    @GetMapping("/info")
    public BaseResult info(String code) {
        CodeVO codeVO = redisCache.getCacheObject(code);
        if (codeVO == null) {
            return BaseResult.success(INVALID_CODE, StringUtils.EMPTY);
        }
        return BaseResult.success(GET_SUCCESS, codeVO);
    }
    

    Внешний интерфейс опрашивает для получения статуса QR-кода:

    getTokenInfo() {
        this.effectiveSeconds--
        // 二维码过期
        if (this.effectiveSeconds <= 0) {
            this.codeStatus = 'EXPIRE'
            this.tip = '二维码已过期,请刷新'
            return
        }
        // 轮询查询二维码状态
        request({
            method: 'get',
            url: '/code/info',
            params: {
                code: this.code.substr(this.code.indexOf('=') + 1)
            }
        }).then(response => {
            const codeVO = response.data
            // 二维码过期
            if (!codeVO || !codeVO.codeStatus) {
                this.codeStatus = 'EXPIRE'
                this.tip = '二维码已过期,请刷新'
                return
            }
            // 二维码状态为为正在登录
            if (codeVO.codeStatus === 'CONFIRMING') {
                this.username = codeVO.username
                this.avatar = codeVO.avatar
                this.codeStatus = 'CONFIRMING'
                this.tip = '扫码成功,请在手机上确认'
                return
            }
            // 二维码状态为确认登录
            if (codeVO.codeStatus === 'CONFIRMED') {
                clearInterval(this.timer)
                const token = codeVO.token
                store.commit('setToken', token)
                this.$router.push('/home')
                Message.success('登录成功')
                return
            }
        })
    }
    
  2. Отсканируйте код мобильным телефоном, статус QR-кода изменится

    Когда пользователь сканирует код мобильным телефоном (вошел в систему и правильноapp, в противном случае скан-код перейдет на настроенную рекламную страницу), а статус QR-кода будет обновлен доCONFIRMING(подлежит подтверждению) статус, и вRedisИнформация об имени пользователя и аватаре добавляется в кеш для внешнего отображения, а информация о входе пользователя (адрес для входа, браузер, операционная система) также будет возвращена в кеш.appдисплей, при создании временногоTokenдаватьapp(фиксированное время действия).

    Фоновая обработка при сканировании кода пользователем:

    /**
     * 处理未使用状态的二维码
     *
     * @param code 二维码
     * @param token token
     * @return 结果
     */
    private BaseResult handleUnusedQr(String code, String token) {
        // 校验 app 端访问传递的 token
        boolean isLegal = JwtUtils.verify(token);
        if (!isLegal) {
            return BaseResult.error(AUTHENTICATION_FAILED);
        }
        // 保存用户名、头像信息, 供前端展示
        String username = JwtUtils.getUsername(token);
        CodeVO codeVO = CodeUtils.getConfirmingCodeInfo(username, DEFAULT_AVATAR_URL);
        redisCache.setCacheObject(code, codeVO, DEFAULT_QR_EXPIRE_SECONDS, TimeUnit.SECONDS);
        // 返回登录地址、浏览器、操作系统以及一个临时 token 给 app
        String address = HttpUtils.getRealAddressByIp();
        String browser = HttpUtils.getBrowserName();
        String os = HttpUtils.getOsName();
        String tmpToken = JwtUtils.sign(username);
        // 将临时 token 作为键, 用户名为内容存储在 redis 中
        redisCache.setCacheObject(tmpToken, username, DEFAULT_TEMP_TOKEN_EXPIRE_MINUTES, TimeUnit.MINUTES);
        LoginInfoVO loginInfoVO = new LoginInfoVO(address, browser, os, tmpToken);
        return BaseResult.success(SCAN_SUCCESS, loginInfoVO);
    }
    
  3. Подтверждение входа с мобильного телефона

    когда пользовательappКогда вы нажмете, чтобы подтвердить вход, он будет содержать сгенерированный временныйTokenОтправьте запрос на обновление статуса, статус QR-кода будет обновлен доCONFIRMED(логин подтвержден), и бэкенд сгенерирует формальныйTokenСохранить какRedis, интерфейс получает это при опросе статуса обновленияToken, то используйте этоTokenчтобы залогиниться.

    Бэкэнд обрабатывает код для подтверждения входа:

    /**
     * 处理未待确认状态的二维码
     *
     * @param code 二维码
     * @param token token
     * @return 结果
     */
    private BaseResult handleConfirmingQr(String code, String token) {
        // 使用临时 token 获取用户名, 并从 redis 中删除临时 token
        String username = redisCache.getCacheObject(token);
        if (StringUtils.isBlank(username)) {
            return BaseResult.error(AUTHENTICATION_FAILED);
        }
        redisCache.deleteObject(token);
        // 根据用户名生成正式 token并保存在 redis 中供前端使用
        String formalToken = JwtUtils.sign(username);
        CodeVO codeVO = CodeUtils.getConfirmedCodeInfo(username, DEFAULT_AVATAR_URL, formalToken);
        redisCache.setCacheObject(code, codeVO, DEFAULT_QR_EXPIRE_SECONDS, TimeUnit.SECONDS);
        return BaseResult.success(CONFIRM_SUCCESS);
    }
    

Демонстрация эффекта

动画.gif

5.gif