Аутентификация через телеграм в Spring Boot приложении

от автора

Способ аутентификации через телеграм отлично описан в документации. В этой статье мы реализуем его в Spring Boot приложении.

Создаем туннель с помощью ngrok

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

Мы запустим ngrok в докере, вот так будет выглядеть docker-compose.yaml:

services:     ngrok:         image: ngrok/ngrok:latest         #команды для запуска ngrok         command:             - "start"             - "--all"            - "--config"             - "/etc/ngrok.yml"         volumes:             - ./ngrok.yml:/etc/ngrok.yml #файл с настройками         network_mode: host 

network_mode: host нужно, чтобы контейнер использовал порты хоста и перенаправлял трафик на хост, а не в контейнер.

Для работы ngrok потребуется зарегистрироваться на их сайте и получить токен.

После этого, в той же папке, где находится docker-compose.yaml, создаем файл конфигурации ngrok.yml и добавляем туда токен:

version: 2 authtoken: YOUR_NGROK_AUTHTOKEN tunnels:     httpbin:         proto: http         addr: 8080

Теперь весь трафик, проходящий через созданный нгроком туннель, будет перенаправлен на localhost:8080.

После запуска контейнера перейдите на localhost:4040, там будет url, по которому ваше приложение доступно в интернете. Url будет оставаться неизменным, пока вы не перезапустите контейнер.

Создаем бота для аутентификации

Далее перейдите в BotFather, создайте бота, который будет использован для аутентификации, и привяжите к нему полученный url. Для этого перейдите в Bot Settings -> Domain -> Edit domain и отправьте url:

Далее переходим на страницу документации, вставляем логин бота в поле для него, выбираем способ авторизации «Callback».

При авторизации Callback будет вызвана функция TelegramAuth, в которую передастся объект с данными пользователя.
Скопируем сгенерированный код и укажем в функции TelegramAuth эндпоинт нашего сервера, на который надо отправить данные для проверки.

<!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8">     <title>Telegram auth</title> </head> <body> <script async src="https://telegram.org/js/telegram-widget.js?22" data-telegram-login="your-bot-login" data-size="large"         data-onauth="onTelegramAuth(user)"></script> <script type="text/javascript">    function onTelegramAuth(user) {     fetch("https://adc8-176-50-96-51.ngrok-free.app/auth/telegram/token", {method: 'POST', body: JSON.stringify(user)})         .then(response => response.text())         .then(body => {             alert(body)         }); } </script> </body> </html>

Создаем приложение и контроллер для аутентификации

После создания обычного Spring Boot прилоения помещаем html файл в папку resources/static, создаем контроллер, который будет отдавать наш html (getAuthScript()) и проверять данные, полученные от телеграма (authenticate())

import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*;  import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Map;  import static java.nio.charset.StandardCharsets.UTF_8;  @RestController @RequestMapping("/auth/telegram") public class AuthController {      private final String tgBotToken = "YOUR_TELEGRAM_BOT_TOKEN";      /**      * возвращает html со скриптом для аутентификации      */     @GetMapping     public ResponseEntity<Resource> getAuthScript() {         Resource resource = new ClassPathResource("static/telegramAuth.html");         var headers = new HttpHeaders();         headers.add(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=telegramAuth.html");         return ResponseEntity.ok().headers(headers).body(resource);     }      /**      * сюда отправляются данные, полученные после аутентификации      */     @PostMapping     public String authenticate(@RequestBody Map<String, Object> telegramData) {         return telegramDataIsValid(telegramData) ? "pretend-that-it-is-your-token" : "error";     }      /**      * проверяет данные, полученные из телеграм      */     private boolean telegramDataIsValid(Map<String, Object> telegramData) {         //получаем хэш, который позже будем сравнивать с остальными данными         String hash = (String) telegramData.get("hash");         telegramData.remove("hash");          //создаем строку проверки - сортируем все параметры и объединяем их в строку вида:         //auth_date=<auth_date>\nfirst_name=<first_name>\nid=<id>\nusername=<username>         StringBuilder sb = new StringBuilder();         telegramData.entrySet().stream()                 .sorted(Map.Entry.comparingByKey())                 .forEach(entry -> sb.append(entry.getKey()).append("=").append(entry.getValue()).append("\n"));         sb.deleteCharAt(sb.length() - 1);         String dataCheckString = sb.toString();          try {             //генерируем SHA-256 хэш из токена бота             MessageDigest digest = MessageDigest.getInstance("SHA-256");             byte[] key = digest.digest(tgBotToken.getBytes(UTF_8));              //создаем HMAC со сгенерированным хэшем             Mac hmac = Mac.getInstance("HmacSHA256");             SecretKeySpec secretKeySpec = new SecretKeySpec(key, "HmacSHA256");             hmac.init(secretKeySpec);              // добавляем в HMAC строку проверки и переводим в шестнадцатеричный формат             byte[] hmacBytes = hmac.doFinal(dataCheckString.getBytes(UTF_8));             StringBuilder validateHash = new StringBuilder();             for (byte b : hmacBytes) {                 validateHash.append(String.format("%02x", b));             }              // сравниваем полученный от телеграма и сгенерированный хэш             return hash.contentEquals(validateHash);         } catch (NoSuchAlgorithmException | InvalidKeyException e) {             throw new RuntimeException(e);         }     } }

Алгоритм реализации функции проверки (telegramDataIsValid()) так же описан в документации:
Проверить аутентификацию и целостность полученных данных можно, сравнив полученный хэш‑параметр с шестнадцатеричным представлением подписи HMAC‑SHA-256 строки проверки данных с хешем SHA256 токена бота, используемого в качестве секретного ключа.

Строка проверки данных представляет собой объединение всех полученных полей, отсортированных в алфавитном порядке, в формате key= с символом перевода строки (‘\n’, 0×0A), используемым в качестве разделителя.

После запуска приложения переходим на https://adc8-176-50-96-51.ngrok-free.app/auth/telegram и проходим аутентификацию в телеграме.

Заключение

Статья написана в процессе разработки стартапа на Spring Boot 🦄
Подписывайтесь, если было интересно


ссылка на оригинал статьи https://habr.com/ru/articles/848502/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *