Я не знаю, из-за 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, если вам нужно получить доступ к переданным параметрам, обратите внимание на следующие два момента.
- необходимо добавить в тег script
th: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. Проект
Связанные сообщения в блоге
Что касается этого сообщения в блоге, некоторые знания можно получить, просмотрев следующие статьи.
- [Серия SpringBoot WEB] Подробное объяснение отправки событий сервером SSE
- [Серия SpringBoot WEB] Асинхронный запрос знаний и использование сводки по положению
- [Серия SpringBoot WEB] Создание среды Thymeleaf
- проект:GitHub.com/JuneB/tickets…
- Исходный код проекта:GitHub.com/JuneB/tickets…
1. Блог одного пепла
Это не так хорошо, как письмо.Вышеупомянутое содержание чисто из семьи.Из-за ограниченных личных способностей неизбежно есть упущения и ошибки.Если вы найдете ошибки или у вас есть лучшие предложения, вы можете критиковать и исправлять их.
Ниже представлен серый личный блог, в котором записываются все посты в блоге по учебе и работе, приглашаю всех посетить
- Блог One Ash Личный блогblog.hhui.top
- Блог One Ash - специальный весенний блогspring.hhui.top