Вопрос или проблема
Я пытаюсь настроить исходящий веб-хук для MS Teams с помощью golang и Google App Engine (развернутого с помощью gcloud app deploy
):
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
)
var secret string = "THIS_IS_A_SECRET"
func handleWebhook(w http.ResponseWriter, r *http.Request) {
fmt.Println("Точка входа веб-хука вызвана")
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Не удается прочитать тело запроса", http.StatusBadRequest)
return
}
fmt.Println("Получен запрос веб-хука:", string(body))
// Логируем тело запроса для отладки
fmt.Printf("Тело запроса: %s\n", string(body))
// Генерируем HMAC токен из тела запроса
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(body)
expectedMAC := mac.Sum(nil)
expectedMACBase64 := base64.StdEncoding.EncodeToString(expectedMAC)
// Получаем HMAC токен из заголовка запроса
authHeader := r.Header.Get("Authorization")
if !strings.HasPrefix(authHeader, "HMAC ") {
fmt.Println("Недействительный заголовок авторизации")
http.Error(w, "Недействительный заголовок авторизации", http.StatusUnauthorized)
return
}
providedMACBase64 := strings.TrimPrefix(authHeader, "HMAC ")
// Сравниваем сгенерированный HMAC токен с предоставленным
if !hmac.Equal([]byte(providedMACBase64), []byte(expectedMACBase64)) {
fmt.Println("Недействительный HMAC токен")
fmt.Println("Ожидаемый HMAC токен:", expectedMACBase64)
fmt.Println("Предоставленный HMAC токен:", providedMACBase64)
http.Error(w, "Недействительный HMAC токен", http.StatusUnauthorized)
return
} else {
fmt.Println("Аутентификация: действительный HMAC токен")
}
// Создаем ответ в формате, ожидаемом Microsoft Teams
response := map[string]string{
"type": "message",
"text": "Веб-хук успешно получен",
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(response)
}
func main() {
http.HandleFunc("/teams-webhook", handleWebhook)
http.ListenAndServe(":8080", nil)
}
Тем не менее, ожидаемый HMAC и предоставленный HMAC всегда разные. Мне кажется, что что-то не так с моим алгоритмом HMAC. Кто-нибудь знает, в чем здесь проблема?
Для справки: я следую инструкции на https://learn.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-outgoing-webhook?tabs=verifyhmactoken%2Cdotnet
Хорошо… оказывается, я забыл декодировать ключ, закодированный в Base64:
[]byte(secret)
> base64.StdEncoding.DecodeString(secret)
Ответ или решение
Конечно! Давайте разберёмся с проблемой, с которой вы столкнулись при настройке HMAC-аутентификации для исходящего вебхука Microsoft Teams.
Проблема
Вы правильно заметили, что ожидаемый HMAC и предоставленный HMAC всегда различаются. Как выяснилось, это связано с тем, что вы не декодировали ключ, закодированный в Base64, перед использованием его в вычислении HMAC.
Решение
Для корректного вычисления HMAC вы должны декодировать секрет с помощью base64.StdEncoding.DecodeString(secret)
, прежде чем использовать его при создании HMAC. Давайте обновим ваш код соответствующим образом:
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
)
var secret string = "ВАШ_BASE64_ЗАШИФРОВАННЫЙ_КЛЮЧ"
func handleWebhook(w http.ResponseWriter, r *http.Request) {
fmt.Println("Webhook endpoint hit")
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Can't read request body", http.StatusBadRequest)
return
}
fmt.Println("Received webhook request:", string(body))
// Генерация HMAC токена из тела запроса
decodedSecret, err := base64.StdEncoding.DecodeString(secret)
if err != nil {
http.Error(w, "Invalid secret key", http.StatusInternalServerError)
return
}
mac := hmac.New(sha256.New, decodedSecret)
mac.Write(body)
expectedMAC := mac.Sum(nil)
expectedMACBase64 := base64.StdEncoding.EncodeToString(expectedMAC)
// Получение HMAC токена из заголовка запроса
authHeader := r.Header.Get("Authorization")
if !strings.HasPrefix(authHeader, "HMAC ") {
fmt.Println("Invalid Authorization header")
http.Error(w, "Invalid Authorization header", http.StatusUnauthorized)
return
}
providedMACBase64 := strings.TrimPrefix(authHeader, "HMAC ")
// Сравнение сгенерированного HMAC токена с предоставленным
if !hmac.Equal([]byte(providedMACBase64), []byte(expectedMACBase64)) {
fmt.Println("Invalid HMAC token")
fmt.Println("Expected HMAC token:", expectedMACBase64)
fmt.Println("Provided HMAC token:", providedMACBase64)
http.Error(w, "Invalid HMAC token", http.StatusUnauthorized)
return
} else {
fmt.Println("Authenticated: Valid HMAC token")
}
// Создание ответа в формате, ожидаемом Microsoft Teams
response := map[string]string{
"type": "message",
"text": "Webhook received successfully",
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(response)
}
func main() {
http.HandleFunc("/teams-webhook", handleWebhook)
http.ListenAndServe(":8080", nil)
}
Описание изменений
- Декодирование секрета: Я добавил строку, которая декодирует ваш секрет с использованием базы64 перед тем, как использовать его для вычисления HMAC.
- Обработка ошибок: Добавлен блок для обработки возможной ошибки при декодировании секрета.
Заключение
Теперь ваш код корректно обрабатывает HMAC-аутентификацию. Убедитесь, что значение переменной secret
содержит правильный Base64-кодированный ключ. Запустите ваше приложение и протестируйте интеграцию с Microsoft Teams, чтобы убедиться, что все работает как положено. Если возникнут дополнительные вопросы, не стесняйтесь обращаться!