Подпись golang-jwt недействительна

Возникли проблемы с проверкой токенов jwt с помощью golang-jwt. Я почти уверен, что формирую токены правильно, потому что могу их распечатать, и они возвращаются нормально, однако, когда я пытаюсь проанализировать их и извлечь утверждения, я получаю сообщение об ошибке, сообщающее, что подпись недействительна.

Я использую платформу echo для своего API, если это имеет значение.

Это моя функция промежуточного программного обеспечения для аутентификации.


func validateJWT(tokenString string) (*jwt.Token, error) {
    // hard coded for now
    secretKey := "keepmesecret"

    return jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
        // Don't forget to validate the alg is what you expect:
        if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
            return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
        }

        // hmacSampleSecret is a []byte containing your secret, e.g. []byte("my_secret_key")
        return []byte(secretKey), nil
    })
}

func Authenticator(next echo.HandlerFunc) echo.HandlerFunc {
    return func(c echo.Context) error {
        // Get the token from the header
        tokenString := strings.Split(c.Request().Header.Get("Authorization"), "Bearer ")[1]
        token, err := validateJWT(tokenString)
        if err != nil {
            fmt.Println(err)
            return c.JSON(401, map[string]interface{}{
                "error": "Invalid token",
            })
        }
        if !token.Valid {
            return c.JSON(401, map[string]interface{}{
                "error": "Invalid token",
            })
        }

        claims := token.Claims.(jwt.MapClaims)

        // Set the user in the context
        c.Set("user", claims["username"])

        return next(c)
    }
}

Это мое создание токена

func createToken(secretKey string, username string) (string, error) {
    claim := jwt.MapClaims{
        "username": username,
        "exp":      time.Now().Add(time.Hour * 24).Unix(),
    }

    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claim)

    tokenString, err := token.SignedString([]byte(secretKey))
    if err != nil {
        return "", err
    }

    return tokenString, nil
}

func sendToken(c echo.Context, secretKey string, username string) error {
    token, err := createToken(secretKey, username)

    if err != nil {
        return c.String(500, "Internal Server Error")
    }
    return c.String(200, token)
}

// login
func RouteLogin(c echo.Context) error {
    
    secretKey := "keepmesecret"

    username := c.FormValue("username")
    password := c.FormValue("password")

    // if username is not admin get all guest account info
    if username == "admin" {
        if password != settings.Accounts.Admin.Password {
            return c.String(401, "Unauthorized")
        } else {
            sendToken(c, secretKey, username)
        }
    } else {
        accounts := settings.Accounts.GuestAccounts
        account, ok := accounts[username]
        if !ok {
            return c.String(404, "Not Found")
        }
        if account.Password != password {
            return c.String(401, "Unauthorized")
        }
        sendToken(c, secretKey, username)
    }

    return c.String(200, "login")
}

Добавление некоторой информации журнала

Sending token:  eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MTUwNjExMzUsInVzZXJuYW1lIjoiZ3Vlc3QifQ.hbF88eu6fGs6F0S9Ttvv6eu8_mT_Iv7rBHvGq8Epvrw using secret key:  Indisputably-Salty-Orbit-7260-07erijgpeirgjpejgptrjgpptgjpritgpi4rtnghi
2024-05-06T15:52:15+10:00 | 200 | 2.5633ms | 127.0.0.1 | POST /api/auth/login
Received token:  eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MTUwNjExMzUsInVzZXJuYW1lIjoiZ3Vlc3QifQ.hbF88eu6fGs6F0S9Ttvv6eu8_mT_Iv7rBHvGq8Epvrwlogin using secret key:  Indisputably-Salty-Orbit-7260-07erijgpeirgjpejgptrjgpptgjpritgpi4rtnghi
signature is invalid
2024-05-06T15:52:24+10:00 | 401 | 2.7255ms | 127.0.0.1 | GET /api/navigate/?pathname=/

Вы подтвердили, что токен, полученный в validateJWT, совпадает с отправленным в sendToken? Также проверьте, что secretKey идентичен в этих двух функциях (мы не видим, откуда это взялось). К сожалению, здесь недостаточно информации, чтобы помочь (действительно нужен воспроизводимый пример)

Brits 05.05.2024 22:48

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

rishi 06.05.2024 03:00

Добавьте немного журналирования и дайте нам пример значения tokenString в Authenticator (фактическое значение, а не то, что вы предполагаете) вместе с полной ошибкой, возвращаемой jwt.Parse. Ваш код создания и проверки токена работает, поэтому я предполагаю, что проблема в другом месте (или ваш пример кода что-то исключает). «поскольку я копирую», но код вашего сервера анализирует ключ, поэтому есть место для ошибки (если мы не сможем продублировать ваш неудачный тест, трудно помочь — в вашем коде нет ничего очевидно неправильного, но многое отсутствует) .

Brits 06.05.2024 03:15

@Brits Большое спасибо за ваши шаги по отладке, я добавил некоторую информацию для журнала, хотя кажется, что это точная строка, кажется, где-то в конце добавляется строка «входа», похоже, мне нужно вернуть мой sendToken функция, чтобы последний метод c.String() не вызывался. Я довольно привык к тому, что nodejs выдает ошибку, когда я пытаюсь записать ответ после того, как что-то уже было отправлено. Я полагаю, go(echo) этого не делает? Хотели бы вы создать ответ на основе этих выводов?... Я буду рад принять

rishi 06.05.2024 07:55

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

Brits 06.05.2024 11:25

Да, конечно. Спасибо, в любом случае. PS: это есть в коде выше... в функции RouteLogin, куда я звоню sendToken(c, secretKey, username)... так и должно быть return sendToken(c, secretKey, username)

rishi 06.05.2024 12:47

Спасибо за замечание, совсем пропустил!

Brits 06.05.2024 12:51
Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
API ввода вопросов - это полезный инструмент для интеграции моделей машинного обучения, таких как ChatGPT, в приложения, требующие обработки...
2
7
166
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Судя по комментариям, проблема была здесь:

    } else {
...
        sendToken(c, secretKey, username)
    }

    return c.String(200, "login")

sendToken вызывается c.String(200, token), который передает содержимое строки клиенту . Реализация String вызывает Blob , который записывает заголовок ( предупредит, если это уже было сделано), а затем данные. Поскольку String вызывается дважды (один раз в sendToken, а затем снова в return c.String), на выходе будет токен с добавленным login (т. е. eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MTUwNjExMzUsInVzZXJuYW1lIjoiZ3Vlc3QifQ.hbF88eu6fGs6F0S9Ttvv6eu8_mT_Iv7rBHvGq8Epvrwlogin).

Таким образом, к токену, отправленному клиенту, добавлен текст login, и это означает, что вы получите ошибку при попытке его проанализировать. Самый простой способ обнаружить подобные проблемы — это просто добавить журналирование, чтобы убедиться, что полученное значение соответствует вашим ожиданиям.

Другие вопросы по теме