Способ аутентификации через телеграм отлично описан в документации. В этой статье мы реализуем его в 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 ?
Подписывайтесь, если было интересно