简体   繁体   中英

go-restful + JWT authentication

I'm trying to plug JWT authentication within a very simple go service written with go-restful .

The code is very similar to:

package main

import (
    "github.com/emicklei/go-restful"
    "log"
    "net/http"
)

type User struct {
    Id, Name string
}

type UserList struct {
    Users []User
}

func getAllUsers(request *restful.Request, response *restful.Response) {
    log.Printf("getAllUsers")
    response.WriteEntity(UserList{[]User{{"42", "Gandalf"}, {"3.14", "Pi"}}})
}

func NewUserService() *restful.WebService {
    ws := new(restful.WebService)
    ws.
        Path("/users").
        Consumes(restful.MIME_XML, restful.MIME_JSON).
        Produces(restful.MIME_JSON, restful.MIME_XML)

    ws.Route(ws.GET("").To(getAllUsers))

    return ws
}


func main() {
    restful.Add(NewUserService())
    log.Printf("start listening on localhost:8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

where restful.Request is a wrapper around http.Request .

That being said, it might be possible to use the Auth0 jwt middleware .

But as a golang newbie, I'm a bit lost in the plumbing process. I see that I must use a Filter function like

ws.Filter(jwtAuthentication)

where

func jwtAuthentication(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
    // Jwt Magic goes here \o
    chain.ProcessFilter(req, resp)
}

But I don't figure how and where should I instanciate the JWT middleware.

Here is the example of filter implementation using auth0/go-jwt-middleware . In real life you probably want to avoid creating new instance of jwtMiddleware every time.

func jwtAuthentication(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
    jwtMiddleware := jwtmiddleware.New(jwtmiddleware.Options{
        ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) {
            return []byte("My Secret"), nil
        },
        SigningMethod: jwt.SigningMethodHS256,
    })

    if err := jwtMiddleware.CheckJWT(resp.ResponseWriter, req.Request); err != nil {
        logger.Errorf("Authentication error: %v", err)
    }
    chain.ProcessFilter(req, resp)
}

After the filter the parsed token will be in the context ( auth0/go-jwt-middleware uses gorilla/context ). Default context key is user .

Note: when JWTMiddleware.SigningMethod is set, the middleware verifies that tokens are signed with the specific signing algorithm.

If the signing method is not constant, the ValidationKeyGetter callback can be used to implement additional checks.

Important to avoid security issues described here .

Here is example of Login API to generate Token, and JWT Authentication filter to check authentication

import (
  "os"
  "strings"
  "github.com/dgrijalva/jwt-go"
  "golang.org/x/crypto/bcrypt"
)

type Token struct {
  UserId   uint
  Username string
  jwt.StandardClaims
}

type Account struct {
  ID       uint
  Email    string
  Password string
  Token    string
}

func Login(request *restful.Request, response *restful.Response) {
  account := &Account{ID: 1, Email: "test@email.com" }
  // TODO - account should be pulled from database

  tk := &Token{ UserId: account.ID }
  token := jwt.NewWithClaims(jwt.GetSigningMethod("HS256"), tk)
  tokenString, _ := token.SignedString([]byte("JWT-SECRET-GOES-RIGHT-HERE"))
  account.Token = tokenString
  account.Password = ''
  response.WriteEntity(account)
}

func JwtAuthentication(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
    tokenHeader := req.Request.HeaderParameter("Authorization")

    if tokenHeader == "" {
      resp.WriteErrorString(http.StatusForbidden, "Not Authorized")
      return
    }

    splitted := strings.Split(tokenHeader, " ")
    if len(splitted) != 2 {
      resp.WriteErrorString(http.StatusForbidden, "Not Authorized")
      return
    }

    tokenPart := splitted[1]
    tk := &Token{}

    token, err := jwt.ParseWithClaims(tokenPart, tk, func(token *jwt.Token) (interface{}, error) {
      return []byte("JWT-SECRET-GOES-RIGHT-HERE"), nil
    })

    if err != nil { //Malformed token, returns with http code 403 as usual
      resp.WriteErrorString(http.StatusForbidden, "Not Authorized")
      return
    }

    if !token.Valid { //Token is invalid, maybe not signed on this server
      resp.WriteErrorString(http.StatusForbidden, "Not Authorized")
      return
    }

    chain.ProcessFilter(req, resp)
}

And then apply filter

ws.Filter(JwtAuthentication)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM