feat: add JWT service

This commit is contained in:
Paul Pan 2022-09-17 10:10:53 +08:00
parent c6436125ef
commit 787fc39c29
9 changed files with 174 additions and 6 deletions

View File

@ -1,6 +1,8 @@
WebServer:
Address: 0.0.0.0
Port: 8000
JwtSigningKey: 'rq67SdQIRABhHq40'
JwtExpireHour: 12
Redis:
Db: 0

1
go.mod
View File

@ -7,6 +7,7 @@ require (
github.com/gin-contrib/pprof v1.4.0
github.com/gin-contrib/zap v0.0.2
github.com/gin-gonic/gin v1.8.1
github.com/golang-jwt/jwt/v4 v4.4.2
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.13.0
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a

2
go.sum
View File

@ -130,6 +130,8 @@ github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGF
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs=
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=

View File

@ -1,19 +1,37 @@
package e
const (
Success Err = 0
Unknown Err = 1
Success Err = 0
Unknown Err = 1
InternalError Err = 100
InvalidParameter Err = 101
NotFound Err = 102
DatabaseError Err = 103
TokenUnknown Err = 200
TokenEmpty Err = 201
TokenMalformed Err = 202
TokenTimeError Err = 203
TokenInvalid Err = 204
TokenSignError Err = 205
TokenRevoked Err = 206
)
var msgText = map[Err]string{
Success: "Success",
Unknown: "Unknown error",
Success: "Success",
Unknown: "Unknown error",
InternalError: "Internal Error",
InvalidParameter: "Invalid Parameter",
NotFound: "Not Found",
DatabaseError: "Database Error",
TokenUnknown: "Unknown Error (Token)",
TokenEmpty: "Token Empty",
TokenMalformed: "Token Malformed",
TokenTimeError: "Token Time Error",
TokenInvalid: "Token Invalid",
TokenSignError: "Token Sign Error",
TokenRevoked: "Token Revoked",
}

View File

@ -1,8 +1,10 @@
package global
type ConfigWebServer struct {
Address string `yaml:"Address"`
Port int `yaml:"Port"`
Address string `yaml:"Address"`
Port int `yaml:"Port"`
JwtSigningKey string `yaml:"JwtSigningKey"`
JwtExpireHour int `yaml:"JwtExpireHour"`
}
type ConfigRedis struct {

View File

@ -0,0 +1,35 @@
package jwt
import (
"github.com/WHUPRJ/woj-server/internal/e"
"github.com/gin-gonic/gin"
"strings"
)
func (s *service) Handler() gin.HandlerFunc {
return func(c *gin.Context) {
tokenHeader := c.GetHeader("Authorization")
if tokenHeader == "" || !strings.HasPrefix(strings.ToLower(tokenHeader), "bearer ") {
e.Pong(c, e.TokenEmpty, nil)
c.Abort()
return
}
token := tokenHeader[7:]
claim, err := s.ParseToken(token)
if err != e.Success {
e.Pong(c, err, nil)
c.Abort()
return
}
// TODO: validate claim version
// if !s.Validate(claim) {
// e.Pong(c, e.TokenRevoked, nil)
// c.Abort()
// }
c.Set("claim", claim)
c.Next()
}
}

View File

@ -0,0 +1,11 @@
package jwt
import "github.com/golang-jwt/jwt/v4"
type Claim struct {
UID uint `json:"id"`
UserName string `json:"user_name"`
NickName string `json:"nick_name"`
Version int `json:"version"`
jwt.RegisteredClaims
}

View File

@ -0,0 +1,32 @@
package jwt
import (
"github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/internal/global"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
var _ Service = (*service)(nil)
type Service interface {
ParseToken(tokenText string) (*Claim, e.Err)
SignClaim(claim *Claim) (string, e.Err)
// TODO: Validate(claim *Claim) bool
Handler() gin.HandlerFunc
}
type service struct {
log *zap.Logger
SigningKey []byte
ExpireHour int
}
func NewJwtService(g *global.Global) Service {
return &service{
log: g.Log,
SigningKey: []byte(g.Conf.WebServer.JwtSigningKey),
ExpireHour: g.Conf.WebServer.JwtExpireHour,
}
}

View File

@ -0,0 +1,65 @@
package jwt
import (
"fmt"
"github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/pkg/utils"
"github.com/golang-jwt/jwt/v4"
"go.uber.org/zap"
"time"
)
func (s *service) ParseToken(tokenText string) (*Claim, e.Err) {
if tokenText == "" {
return nil, e.TokenEmpty
}
token, err := jwt.ParseWithClaims(
tokenText,
&Claim{},
func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return s.SigningKey, nil
})
if ve, ok := err.(*jwt.ValidationError); ok {
if ve.Errors&jwt.ValidationErrorMalformed != 0 {
return nil, e.TokenMalformed
} else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 {
// Token is either expired or not active yet
return nil, e.TokenTimeError
} else {
return nil, e.TokenInvalid
}
} else if err != nil {
s.log.Warn("JWT Token Parse Error", zap.Error(err))
return nil, e.TokenUnknown
}
if token.Valid {
c := token.Claims.(*Claim)
return c, e.Success
}
return nil, e.TokenInvalid
}
func (s *service) SignClaim(claim *Claim) (string, e.Err) {
now := time.Now()
claim.IssuedAt = jwt.NewNumericDate(now)
claim.ExpiresAt = jwt.NewNumericDate(now.Add(time.Duration(s.ExpireHour) * time.Hour))
claim.ID = utils.RandomString(16)
// TODO: use per-user claim.Version to tracker invalidation
claim.NotBefore = jwt.NewNumericDate(time.Now())
token := jwt.NewWithClaims(jwt.SigningMethodHS512, claim)
ss, err := token.SignedString(s.SigningKey)
if err != nil {
s.log.Warn("jwt.SignedString error", zap.Error(err))
return "", e.TokenSignError
}
return ss, e.Success
}