echoでRSAモードでJWTを使う


JSON Web Token (JWT)はマイクロサービスの認証、認可に使える署名付きJSON。ユーザは内容を改ざんできないため、サーバ側に認証ステータスを保持しなくて良いし、検証時に認証サーバにアクセス不要。なので、スケーラブルな認証サービスをJSONのシンプルさを損なわず実装できる。GoogleはOAuth2のトークンに採用しているし、広く採用されている標準と言える。

Echoフレームワークでは、JWT Middlewareを使用可能。公式のサンプル

公式のサンプルは共有秘密鍵を使った署名であるHMACを使っているが、実際に運用する場合には秘密鍵を個別のマイクロサービスに複製しなくて良い公開鍵暗号方式の署名の方が楽なので、やりかたを調べてみた。

署名方法をRSAにして、JWTWithConfigをミドルウェアに渡してあげるだけ。楽チンですね :)

package main

import (
    "io/ioutil"
    "net/http"
    "time"

    jwt "github.com/dgrijalva/jwt-go"
    "github.com/labstack/echo"
    "github.com/labstack/echo/middleware"
)

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

    if username == "jon" && password == "shhh!" {
        // Create token
        token := jwt.New(jwt.SigningMethodRS512)

        // Set claims
        claims := token.Claims.(jwt.MapClaims)
        claims["name"] = "Jon Snow"
        claims["admin"] = true
        claims["exp"] = time.Now().Add(time.Hour * 72).Unix()

        keyData, _ := ioutil.ReadFile("sample_key")
        key, _ := jwt.ParseRSAPrivateKeyFromPEM(keyData)
        // Generate encoded token and send it as response.
        t, err := token.SignedString(key)
        if err != nil {
            return err
        }
        return c.JSON(http.StatusOK, map[string]string{
            "token": t,
        })
    }

    return echo.ErrUnauthorized
}

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()

    // 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")

    keyData, _ := ioutil.ReadFile("sample_key.pub")
    key, _ := jwt.ParseRSAPublicKeyFromPEM(keyData)

    r.Use(middleware.JWTWithConfig(middleware.JWTConfig{
        SigningKey:    key,
        SigningMethod: "RS512",
    }))
    r.GET("", restricted)

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