Пример проекта входа в систему с кодом сканирования от руки

Spring Boot

Пример проекта входа в систему с кодом сканирования от руки

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

В этом примере проекта в основном используются следующие технологические стеки.

  • qrcode-plugin: Набор инструментов генерации QR-код с открытым исходным кодом, ссылка на проект:GitHub.com/JuneB/Go…
  • SpringBoot: Базовая среда проекта
  • thymeleaf: механизм рендеринга страницы
  • SSE/异步请求: Событие отправки сервера
  • js: Основные операции нативного js

I. Принципиальный анализ

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

Отсканируйте код, чтобы войти в систему, см.:Поговорим о принципе сканирования QR-кода и авторизации

1. Описание сценария

Чтобы позаботиться о студентах, которые могут мало знать о входе в систему с помощью скан-кода, вот краткое введение в то, что это такое.

Вообще говоря, сканирование кода для входа включает в себя оба конца и три шага.

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

2. Краткое описание принципа и процесса

При разработке всей системы основным моментом является то, что после сканирования кода на мобильном телефоне вход на ПК проходит успешно.Какой принцип?

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

По указанному выше принципу проводится пошаговый анализ ключевых моментов

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

В конечном итоге мы выбрали отношения бизнес-процесса на следующем рисунке:

流程

II. Осуществление

Затем войдите в стадию разработки проекта и реализуйте приведенную выше блок-схему одну за другой.

1. Окружающая среда проекта

Сначала создайте проект SpringBoot, выберите версию2.2.1.RELEASE

pom зависимости следующие

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.1.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>com.github.hui.media</groupId>
        <artifactId>qrcode-plugin</artifactId>
        <version>2.2</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
</dependencies>

<build>
    <pluginManagement>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </pluginManagement>
</build>
<repositories>
    <repository>
        <id>spring-releases</id>
        <name>Spring Releases</name>
        <url>https://repo.spring.io/libs-release-local</url>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
    </repository>
    <repository>
        <id>yihui-maven-repo</id>
        <url>https://raw.githubusercontent.com/liuyueyi/maven-repository/master/repository</url>
    </repository>
</repositories>

Описание ключевых зависимостей

  • qrcode-plugin: Это не я, это, наверное, самый лучший и гибкий инструментарий на стороне java, а также он поддерживает генерацию различных крутых QR-кодов.Последняя версия на данный момент2.2При введении зависимостей, пожалуйста, укажите адрес складаhttps://raw.githubusercontent.com/liuyueyi/maven-repository/master/repository
  • spring-boot-starter-thymeleaf: механизм рендеринга шаблона, который мы выбираем, разделение внешнего и внутреннего интерфейса здесь не принято, проект содержит все функциональные точки.

конфигурационный файлapplication.yml

server:
  port: 8080

spring:
  thymeleaf:
    mode: HTML
    encoding: UTF-8
    servlet:
      content-type: text/html
    cache: false

получить локальный ip

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

import java.net.*;
import java.util.Enumeration;

public class IpUtils {
    public static final String DEFAULT_IP = "127.0.0.1";

    /**
     * 直接根据第一个网卡地址作为其内网ipv4地址,避免返回 127.0.0.1
     *
     * @return
     */
    public static String getLocalIpByNetcard() {
        try {
            for (Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces(); e.hasMoreElements(); ) {
                NetworkInterface item = e.nextElement();
                for (InterfaceAddress address : item.getInterfaceAddresses()) {
                    if (item.isLoopback() || !item.isUp()) {
                        continue;
                    }
                    if (address.getAddress() instanceof Inet4Address) {
                        Inet4Address inet4Address = (Inet4Address) address.getAddress();
                        return inet4Address.getHostAddress();
                    }
                }
            }
            return InetAddress.getLocalHost().getHostAddress();
        } catch (SocketException | UnknownHostException e) {
            return DEFAULT_IP;
        }
    }

    private static volatile String ip;

    public static String getLocalIP() {
        if (ip == null) {
            synchronized (IpUtils.class) {
                if (ip == null) {
                    ip = getLocalIpByNetcard();
                }
            }
        }
        return ip;
    }
}

2. Интерфейс входа

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

Логика страницы входа, QR-код, возвращаемый после доступа, содержание QR-кода является URL-адресом авторизации входа

@CrossOrigin
@Controller
public class QrLoginRest {
    @Value(("${server.port}"))
    private int port;

    @GetMapping(path = "login")
    public String qr(Map<String, Object> data) throws IOException, WriterException {
        String id = UUID.randomUUID().toString();
        // IpUtils 为获取本机ip的工具类,本机测试时,如果用127.0.0.1, localhost那么app扫码访问会有问题哦
        String ip = IpUtils.getLocalIP();

        String pref = "http://" + ip + ":" + port + "/";
        data.put("redirect", pref + "home");
        data.put("subscribe", pref + "subscribe?id=" + id);


        String qrUrl = pref + "scan?id=" + id;
        // 下面这一行生成一张宽高200,红色,圆点的二维码,并base64编码
        // 一行完成,就这么简单省事,强烈安利
        String qrCode = QrCodeGenWrapper.of(qrUrl).setW(200).setDrawPreColor(Color.RED)
                .setDrawStyle(QrCodeOptions.DrawStyle.CIRCLE).asString();
        data.put("qrcode", DomUtil.toDomSrc(qrCode, MediaType.ImageJpg));
        return "login";
    }
}

Обратите внимание на приведенную выше реализацию, мы возвращаем представление и передаем три данных

  • redirect: URL-адрес перенаправления (страница, перенаправленная после авторизации приложения)
  • subscribe: URL-адрес подписки (пользователи будут посещать этот URL-адрес, открывать длинное соединение и получать код сканирования и события входа в систему, отправленные сервером)
  • qrcode: изображение QR-кода в формате base64.

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

Затем соответствующая html-страница вresources/templatesПод файлом добавить файлlogin.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="SpringBoot thymeleaf"/>
    <meta name="author" content="YiHui"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <title>二维码界面</title>
</head>
<body>

<div>
    <div class="title">请扫码登录</div>
    <img th:src="${qrcode}"/>
    <div id="state" style="display: none"></div>

    <script th:inline="javascript">
        var stateTag = document.getElementById('state');

        var subscribeUrl = [[${subscribe}]];
        var source = new EventSource(subscribeUrl);
        source.onmessage = function (event) {
            text = event.data;
            console.log("receive: " + text);
            if (text == 'scan') {
                stateTag.innerText = '已扫描';
                stateTag.style.display = 'block';
            } else if (text.startsWith('login#')) {
                // 登录格式为 login#cookie
                var cookie = text.substring(6);
                document.cookie = cookie;
                window.location.href = [[${redirect}]];
                source.close();
            }
        };

        source.onopen = function (evt) {
            console.log("开始订阅");
        }
    </script>
</div>
</body>
</html>

Обратите внимание, что в приведенной выше реализации html тег с состоянием id по умолчанию невидим;EventSourceРеализовать SSE (преимущество в режиме реального времени и имеет собственную функцию повтора), а формат определяется для возвращаемых результатов.

  • Если сервер полученscanсообщение, измените копию метки состояния и сделайте ее видимой
  • Если получено с сервераlogin#cookieОтформатировать данные, указывающие на успешный вход в систему,#Последний - это файл cookie, установите локальный файл cookie, затем перенаправьте на домашнюю страницу и закройте долгое соединение.

Во-вторых, в теге script, если вам нужно получить доступ к переданным параметрам, обратите внимание на следующие два момента.

  • необходимо добавить в тег scriptth:inline="javascript"
  • [[${}]]получить переданные параметры

3. ссэ интерфейс

В ранее зарегистрированном интерфейсеsseИнтерфейс регистрации, клиент будет получать доступ к этому интерфейсу при доступе к странице входа.Согласно нашему предыдущему учебному документу sse, это может быть реализовано следующим образом

private Map<String, SseEmitter> cache = new ConcurrentHashMap<>();

@GetMapping(path = "subscribe", produces = {org.springframework.http.MediaType.TEXT_EVENT_STREAM_VALUE})
public SseEmitter subscribe(String id) {
    // 设置五分钟的超时时间
    SseEmitter sseEmitter = new SseEmitter(5 * 60 * 1000L);
    cache.put(id, sseEmitter);
    sseEmitter.onTimeout(() -> cache.remove(id));
    sseEmitter.onError((e) -> cache.remove(id));
    return sseEmitter;
}

4. Интерфейс сканирования кода

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

@GetMapping(path = "scan")
public String scan(Model model, HttpServletRequest request) throws IOException {
    String id = request.getParameter("id");
    SseEmitter sseEmitter = cache.get(request.getParameter("id"));
    if (sseEmitter != null) {
        // 告诉pc端,已经扫码了
        sseEmitter.send("scan");
    }

    // 授权同意的url
    String url = "http://" + IpUtils.getLocalIP() + ":" + port + "/accept?id=" + id;
    model.addAttribute("url", url);
    return "scan";
}

После того, как пользователь просканирует код для доступа к этой странице, он найдет соответствующий компьютер-клиент в соответствии с переданным идентификатором, а затем отправитscanИнформация

Страница авторизации проста, просто добавьте авторизованную гиперссылку, а затем дополняйте токен пользователя в соответствии с фактической ситуацией (потому что нет независимого приложения и пользовательской системы, поэтому следующая - это демонстрация, и вместо этого генерируется случайный токен Несомненно

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="SpringBoot thymeleaf"/>
    <meta name="author" content="YiHui"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <title>扫码登录界面</title>
</head>
<body>

<div>
    <div class="title">确定登录嘛?</div>

    <div>
        <a id="login">登录</a>
    </div>

    <script th:inline="javascript">

        // 生成uuid,模拟传递用户token
        function guid() {

            return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
                var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
                return v.toString(16);

            });
        }

        // 获取实际的token,补齐参数,这里只是一个简单的模拟
        var url = [[${url}]];
        document.getElementById("login").href = url + "&token=" + guid();
    </script>

</div>
</body>
</html>

5. Интерфейс авторизации

После нажатия на гиперссылку авторизации выше это означает, что вход в систему выполнен успешно.Реализация нашего бэкенда выглядит следующим образом.

@ResponseBody
@GetMapping(path = "accept")
public String accept(String id, String token) throws IOException {
    SseEmitter sseEmitter = cache.get(id);
    if (sseEmitter != null) {
        // 发送登录成功事件,并携带上用户的token,我们这里用cookie来保存token
        sseEmitter.send("login#qrlogin=" + token);
        sseEmitter.complete();
        cache.remove(id);
    }

    return "登录成功: " + token;
}

6. Главная

После успешной авторизации пользователя он автоматически перейдет на главную страницу, на главной сделаем просто, просто создадим приветственную копию.

@GetMapping(path = {"home", ""})
@ResponseBody
public String home(HttpServletRequest request) {
    Cookie[] cookies = request.getCookies();
    if (cookies == null || cookies.length == 0) {
        return "未登录!";
    }

    Optional<Cookie> cookie = Stream.of(cookies).filter(s -> s.getName().equalsIgnoreCase("qrlogin")).findFirst();
    return cookie.map(cookie1 -> "欢迎进入首页: " + cookie1.getValue()).orElse("未登录!");
}

7. Измерено

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

演示

Обратите внимание на несколько ключевых моментов на скриншоте выше.

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

8. Резюме

Реальные варианты развития бизнеса могут не совпадать с предложенными в этой статье, а могут быть и более элегантные реализации (просьба проповедовать тем, у кого есть опыт в этой сфере) Данная статья носит справочный характер и не представляет собой стандарт или a Это совершенно точно, если вы заведете всех в яму, пожалуйста, оставьте сообщение (конечно, я не несу ответственности 🙃)

В приведенном выше примере демонстрируется линия от руки и проецируется двумерный код входа в систему, основное использование технологии в точке

  • qrcode-plugin: Генерация QR-кода, еще раз убедительно Amway считает, что лучше всего использовать набор инструментов для генерации QR-кода в экосистеме Java.GitHub.com/JuneB/Go…(Хоть и более агрессивно, но я не брал платы за рекламу, потому что это тоже то, что я написал 😂)
  • SSE: Push-события на стороне сервера, одноканальная связь на стороне сервера для достижения push-сообщений.
  • SpringBoot/Thymeleaf: Базовая среда демонстрационного проекта

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

III. Другое

0. Проект

Связанные сообщения в блоге

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


1. Блог одного пепла

Это не так хорошо, как письмо.Вышеупомянутое содержание чисто из семьи.Из-за ограниченных личных способностей неизбежно есть упущения и ошибки.Если вы найдете ошибки или у вас есть лучшие предложения, вы можете критиковать и исправлять их.

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