предисловие
В этой статье будет представлено на основе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
Логика проверки аналогична следующим образом:
Когда пользователь выбирает сканирование кода для входа в систему, его можно увидеть какA
: Внешний интерфейс отправляет запрос на авторизацию и ожидаетapp
Сканировать код.
использование пользователемapp
Сканирование кода можно рассматривать какB
: отсканировать код для авторизации и вернуть временныйToken
для вторичной аттестации.
пользователь вapp
Выполнение входа с подтверждением можно рассматривать какC
: Для подтверждения входа авторизованный пользователь находится вWeb
боковой логин.
Бэкэнд возвращает формал после того, как пользователь подтвердит входToken
можно рассматривать как шагиD
.
Последующий интерфейс в соответствии с формальнымToken
Доступ к фоновому интерфейсу, официально вWeb
Конец операции может быть рассмотренаE
иF
.
Причины вторичной сертификации
Причина, по которой необходимо снова подтверждать вход в систему после того, как пользователь сканирует код, вместо прямого входа в систему, заключается в соображениях безопасности пользователя, чтобы пользователи не могли сканировать QR-код, который нужен другим людям для входа, и напрямую войти в систему. без подтверждения. Выполнен вход, чтобы другие могли получить доступ к нашей информации без нашего ведома.
Этапы реализации
-
Пользователь заходит на веб-страницу и выбирает сканирование кода для входа в систему.
Когда пользователь решит отсканировать код для входа в систему, на серверную часть будет отправлен запрос на создание 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 } }) }
-
Отсканируйте код мобильным телефоном, статус 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); }
-
Подтверждение входа с мобильного телефона
когда пользователь
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); }