Возникли проблемы с проверкой токенов 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=/
@Brits Привет, да, секретный ключ взят из того же файла. так что я уверен, что это то же самое, однако для целей вопроса я жестко запрограммировал его. Я также уверен, что токен один и тот же в обоих местах, поскольку я копирую при тестировании функций. Я не уверен, какая еще информация будет здесь актуальна.
Добавьте немного журналирования и дайте нам пример значения tokenString
в Authenticator
(фактическое значение, а не то, что вы предполагаете) вместе с полной ошибкой, возвращаемой jwt.Parse
. Ваш код создания и проверки токена работает, поэтому я предполагаю, что проблема в другом месте (или ваш пример кода что-то исключает). «поскольку я копирую», но код вашего сервера анализирует ключ, поэтому есть место для ошибки (если мы не сможем продублировать ваш неудачный тест, трудно помочь — в вашем коде нет ничего очевидно неправильного, но многое отсутствует) .
@Brits Большое спасибо за ваши шаги по отладке, я добавил некоторую информацию для журнала, хотя кажется, что это точная строка, кажется, где-то в конце добавляется строка «входа», похоже, мне нужно вернуть мой sendToken функция, чтобы последний метод c.String() не вызывался. Я довольно привык к тому, что nodejs выдает ошибку, когда я пытаюсь записать ответ после того, как что-то уже было отправлено. Я полагаю, go(echo) этого не делает? Хотели бы вы создать ответ на основе этих выводов?... Я буду рад принять
Не беспокойтесь, я не думаю, что ответ будет особенно полезен другим (поскольку не совсем понятно, куда добавляется «логин», и я не думаю, что он находится в коде вопроса). Я думаю, урок заключается в том, что всегда стоит добавлять журналирование, чтобы гарантировать, что данные соответствуют вашим ожиданиям.
Да, конечно. Спасибо, в любом случае. PS: это есть в коде выше... в функции RouteLogin, куда я звоню sendToken(c, secretKey, username)
... так и должно быть return sendToken(c, secretKey, username)
Спасибо за замечание, совсем пропустил!
Судя по комментариям, проблема была здесь:
} 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
, и это означает, что вы получите ошибку при попытке его проанализировать. Самый простой способ обнаружить подобные проблемы — это просто добавить журналирование, чтобы убедиться, что полученное значение соответствует вашим ожиданиям.
Вы подтвердили, что токен, полученный в
validateJWT
, совпадает с отправленным вsendToken
? Также проверьте, чтоsecretKey
идентичен в этих двух функциях (мы не видим, откуда это взялось). К сожалению, здесь недостаточно информации, чтобы помочь (действительно нужен воспроизводимый пример)