GolangのEchoでJWT


Echoの準備

DockerでGolangコンテナを使います。

FROM golang:1.14.2-alpine3.11

WORKDIR /go/src

COPY ./src /go/src

ENV GOPATH ''

RUN apk update && apk add --no-cache git
docker-compose.yml
version: '3'
services:
  goecho:
    image: goecho
    build: .
    ports:
      - 8080:8080
    volumes:
      - ./src:/go/src
    tty: true
$ docker-compose up -d --build
$ docker-compose exec goecho go mod init src
$ docker-compose exec goecho go get github.com/labstack/echo/v4
src/main.go
package main

import (
  "net/http"
  "github.com/labstack/echo/v4"
  "github.com/labstack/echo/v4/middleware"
)

func main() {
  // Echo instance
  e := echo.New()

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

  // Routes
  e.GET("/", hello)

  // Start server
  e.Logger.Fatal(e.Start(":8080"))
}

// Handler
func hello(c echo.Context) error {
  return c.String(http.StatusOK, "Hello, World!")
}
$ docker-compose exec goecho go run main.go

JWTの実装

JWTの説明は省略します。知りたい方は以下のリンク先を。

EchoでJWT認証を実装します。まあCookbook通りですが。。

src/main.go
package main

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

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

  // とりあえずのパスワード認証
  if username != "taro" || password != "shhh!" {
    return echo.ErrUnauthorized
  }

  // トークン作成
  token := jwt.New(jwt.SigningMethodHS256)

  claims := token.Claims.(jwt.MapClaims)
  claims["name"] = "Taro"
  claims["admin"] = true
  claims["exp"] = time.Now().Add(time.Hour * 24).Unix()

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

  return c.JSON(http.StatusOK, map[string]string{
    "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.(jwt.MapClaims)
  name := claims["name"].(string)
  return c.String(http.StatusOK, "Welcome "+name+"!")
}

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

  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")
  r.Use(middleware.JWT([]byte("secret")))
  r.GET("", restricted)

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

JWT認証の必要がないURLはアクセスできます。

$ curl localhost:8080
Accessiblesrc

JWT認証が必要なURLはトークンが必要です。

$ curl localhost:8080/restricted
{"message":"missing or malformed jwt"}

トークンを取得します。

$ curl -X POST -d 'username=taro' -d 'password=hoge' localhost:8080/login
{"message":"Unauthorized"}
$ curl -X POST -d 'username=taro' -d 'password=shhh!' localhost:8080/login
{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6dHJ1ZSwiZXhwIjoxNTg3NjI4NjI0LCJuYW1lIjoiVGFybyJ9.5mqtxA0XiNcslwC22n7gNL97KPl6zYdt6AhpF9PkIKk"}

取得したトークンで認証できるか確認します。

$ curl localhost:8080/restricted  -H "Authorization: Bearer hoge"
{"message":"invalid or expired jwt"}
$ curl localhost:8080/restricted  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6dHJ1ZSwiZXhwIjoxNTg3NjI4NjI0LCJuYW1lIjoiVGFybyJ9.5mqtxA0XiNcslwC22n7gNL97KPl6zYdt6AhpF9PkIKk"
Welcome Taro!

成功しました。
これでステートレスな認証が可能ですね