LoginSignup
7
2

公式ドキュメントすらあってるとは限らない (ポエム)

Posted at

TL;DR

公式ドキュメントも間違ってるときだってある。

公式ドキュメント通りにやってんのに動かねぇ!!

ある日私は、JWTを使った認証をGolangとechoで実装しようと考えました。
とりあえず何も分からなかったので、echoの公式ドキュメントにあるCookbookから以下のコードをコピペしてGoに突っ込んでみました。

コピーしたコード

https://echo.labstack.com/cookbook/jwt/ より引用

package main

import (
	"github.com/golang-jwt/jwt/v4"
	echojwt "github.com/labstack/echo-jwt/v4"
	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"
	"net/http"
	"time"
)

// jwtCustomClaims are custom claims extending default ones.
// See https://github.com/golang-jwt/jwt for more examples
type jwtCustomClaims struct {
	Name  string `json:"name"`
	Admin bool   `json:"admin"`
	jwt.RegisteredClaims
}

func login(c echo.Context) error {
	username := c.FormValue("username")
	password := c.FormValue("password")

	// Throws unauthorized error
	if username != "jon" || password != "shhh!" {
		return echo.ErrUnauthorized
	}

	// Set custom claims
	claims := &jwtCustomClaims{
		"Jon Snow",
		true,
		jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 72)),
		},
	}

	// Create token with claims
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

	// Generate encoded token and send it as response.
	t, err := token.SignedString([]byte("secret"))
	if err != nil {
		return err
	}

	return c.JSON(http.StatusOK, echo.Map{
		"token": t,
	})
}

func accessible(c echo.Context) error {
	return c.String(http.StatusOK, "Accessible")
}

func restricted(c echo.Context) error {
	user := c.Get("user").(*jwt.Token)
	claims := user.Claims.(*jwtCustomClaims)
	name := claims.Name
	return c.String(http.StatusOK, "Welcome "+name+"!")
}

func main() {
	e := echo.New()

	// Middleware
	e.Use(middleware.Logger())
	e.Use(middleware.Recover())

	// Login route
	e.POST("/login", login)

	// Unauthenticated route
	e.GET("/", accessible)

	// Restricted group
	r := e.Group("/restricted")

	// Configure middleware with the custom claims type
	config := echojwt.Config{
		NewClaimsFunc: func(c echo.Context) jwt.Claims {
			return new(jwtCustomClaims)
		},
		SigningKey: []byte("secret"),
	}
	r.Use(echojwt.WithConfig(config))
	r.GET("", restricted)

	e.Logger.Fatal(e.Start(":1323"))
}

コードをコピーして...
依存関係をgetして...
コンパイルして......
動かねえ!!

エラーとの格闘

/loginは動作するのに、/restrictedを使おうとするとgo側が長文エラーを吐き始めます。

長文エラー(ユーザー名など一部改変してあります)
{"time":"2023-05-22T00:24:04.737973449+09:00","level":"-","prefix":"echo","file":"recover.go","line":"113","message":"[PANIC RECOVER] interface conversion: interface {} is *jwt.Token, not *jwt.Token (types from different packages) goroutine 19 [running]:\ngithub.com/labstack/echo/v4/middleware.RecoverWithConfig.func1.1.1()\n\t/home/user/go/pkg/mod/github.com/labstack/echo/v4@v4.10.2/middleware/recover.go:93 +0x14e\npanic({0x6ee9e0, 0xc00011bef0})\n\t/usr/lib/go-1.20/src/runtime/panic.go:884 +0x213\ntest/accessible({0x7c0558, 0xc0000abcc0})\n\t/projectpath/main.go:57 +0xd9\ngithub.com/labstack/echo-jwt/v4.Config.ToMiddleware.func2.1({0x7c0558, 0xc0000abcc0})\n\t/home/user/go/pkg/mod/github.com/labstack/echo-jwt/v4@v4.2.0/jwt.go:237 +0x41d\ngithub.com/labstack/echo/v4.(*Echo).add.func1({0x7c0558, 0xc0000abcc0})\n\t/home/user/go/pkg/mod/github.com/labstack/echo/v4@v4.10.2/echo.go:575 +0x51\ngithub.com/labstack/echo/v4/middleware.RecoverWithConfig.func1.1({0x7c0558, 0xc0000abcc0})\n\t/home/user/go/pkg/mod/github.com/labstack/echo/v4@v4.10.2/middleware/recover.go:119 +0xfe\ngithub.com/labstack/echo/v4/middleware.LoggerWithConfig.func2.1({0x7c0558, 0xc0000abcc0})\n\t/home/user/go/pkg/mod/github.com/labstack/echo/v4@v4.10.2/middleware/logger.go:126 +0xe2\ngithub.com/labstack/echo/v4.(*Echo).ServeHTTP(0xc0000c66c0, {0x7bcf70?, 0xc00014a000}, 0xc000100200)\n\t/home/user/go/pkg/mod/github.com/labstack/echo/v4@v4.10.2/echo.go:662 +0x3d1\nnet/http.serverHandler.ServeHTTP({0xc00011bc20?}, {0x7bcf70, 0xc00014a000}, 0xc000100200)\n\t/usr/lib/go-1.20/src/net/http/server.go:2936 +0x316\nnet/http.(*conn).serve(0xc0000fc2d0, {0x7bd198, 0xc00011bb30})\n\t/usr/lib/go-1.20/src/net/http/server.go:1995 +0x612\ncreated by net/http.(*Server).Serve\n\t/usr/lib/go-1.20/src/net/http/server.go:3089 +0x5ed\n\ngoroutine 1 [IO wait]:\ninternal/poll.runtime_pollWait(0x7f7d96c1dff8, 0x72)\n\t/usr/lib/go-1.20/src/runtime/netpoll.go:306 +0x89\ninternal/poll.(*pollDesc).wait(0xc0000fea00?, 0x4?, 0x0)\n\t/usr/lib/go-1.20/src/internal/poll/fd_poll_runtime.go:84 +0x32\ninternal/poll.(*pollDesc).waitRead(...)\n\t/usr/lib/go-1.20/src/internal/poll/fd_poll_runtime.go:89\ninternal/poll.(*FD).Accept(0xc0000fea00)\n\t/usr/lib/go-1.20/src/internal/poll/fd_unix.go:614 +0x2bd\nnet.(*netFD).accept(0xc0000fea00)\n\t/usr/lib/go-1.20/src/net/fd_unix.go:172 +0x35\nnet.(*TCPListener).accept(0xc0000a8810)\n\t/usr/lib/go-1.20/src/net/tcpsock_posix.go:148 +0x25\nnet.(*TCPListener).AcceptTCP(0xc0000a8810)\n\t/usr/lib/go-1.20/src/net/tcpsock.go:284 +0x3d\ngithub.com/labstack/echo/v4.tcpKeepAliveListener.Accept({0x4410a0?})\n\t/home/user/go/pkg/mod/github.com/labstack/echo/v4@v4.10.2/echo.go:982 +0x1d\nnet/http.(*Server).Serve(0xc000130000, {0x7bcd60, 0xc0000b4180})\n\t/usr/lib/go-1.20/src/net/http/server.go:3059 +0x385\ngithub.com/labstack/echo/v4.(*Echo).Start(0xc0000c66c0, {0xc00009eb9a, 0x5})\n\t/home/user/go/pkg/mod/github.com/labstack/echo/v4@v4.10.2/echo.go:679 +0xce\nmain.main()\n\t/projectpath/main.go:87 +0x5a6\n\ngoroutine 20 [runnable]:\nsyscall.Syscall(0x0?, 0x0?, 0x4ef052?, 0x7ffff800000?)\n\t/usr/lib/go-1.20/src/syscall/syscall_linux.go:69 +0x27\nsyscall.read(0xc0000fea80?, {0xc00011bc31?, 0x0?, 0x0?})\n\t/usr/lib/go-1.20/src/syscall/zsyscall_linux_amd64.go:711 +0x45\nsyscall.Read(...)\n\t/usr/lib/go-1.20/src/syscall/syscall_unix.go:178\ninternal/poll.ignoringEINTRIO(...)\n\t/usr/lib/go-1.20/src/internal/poll/fd_unix.go:794\ninternal/poll.(*FD).Read(0xc0000fea80?, {0xc00011bc31?, 0x1?, 0x1?})\n\t/usr/lib/go-1.20/src/internal/poll/fd_unix.go:163 +0x2ce\nnet.(*netFD).Read(0xc0000fea80, {0xc00011bc31?, 0x0?, 0x0?})\n\t/usr/lib/go-1.20/src/net/fd_posix.go:55 +0x29\nnet.(*conn).Read(0xc0000b41a0, {0xc00011bc31?, 0x0?, 0x0?})\n\t/usr/lib/go-1.20/src/net/net.go:183 +0x45\nnet/http.(*connReader).backgroundRead(0xc00011bc20)\n\t/usr/lib/go-1.20/src/net/http/server.go:674 +0x3f\ncreated by net/http.(*connReader).startBackgroundRead\n\t/usr/lib/go-1.20/src/net/http/server.go:670 +0xca\n\n"}
{"time":"2023-05-22T00:24:04.738101663+09:00","id":"","remote_ip":"127.0.0.1","host":"localhost:1323","method":"GET","uri":"/restricted","user_agent":"curl/7.81.0","status":500,"error":"","latency":367498,"latency_human":"367.498µs","bytes_in":0,"bytes_out":36}

う~ん、わからんw!

長文エラーが一瞬にして吐かれたので、頭の使用率は100%です。理解できません。

とりあえず最初の方に[PANIC RECOVER] interface conversion: interface {} is *jwt.Token, not *jwt.Token
と表示されてるので、型アサーション系のエラーということだけはわかりました。
どう見ても*jwt.Token*jwt.Tokenって同じなのに何が違うんや??と思い色々調べました。
(今見たらnot *jwt.Token~の後に(types from different packages)って書いてありますね...これに気づいてたらもっと早く解決できてたかもしれません。)

エラーについて調べてgo mod tidyをしてみたり、コードの順番を入れ替えてみたり、OSを変えてみたり...
あれこれ試しているうちに一日が過ぎました。

そして、このissueにたどり着きます。
このissueについてるコメントに、

echoで使ってるjwtと、importで使うjwtのバージョンを合わせてくれよな!(超絶意訳)

と書いてあり、まさかとは思いながらecho-jwtのREADMEを読んでみました。
するとどうやら、最近echo-jwtが使うjwtのバージョンがv4からv5に変わっているようでした。

そこでコード内のimportで使っているjwtのバージョンをv5にすると

動いた!!!

どうやらバージョン違いの問題で一日を潰したようです。
同じ作成者なら依存関係に破壊的なアップデートをしたときにドキュメントも更新してくれ!!

ということで問題になっていたドキュメントを修正して、GithubでPull Requestを出しました。

教訓

  • 公式ドキュメントも万能じゃない。エラーが起きたらバージョン違いなども視野に入れて解決しよう。
  • エラー文はちゃんと読もう。

ここまで読んでいただいてありがとうございました!

7
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
2