Skip to content

Commit

Permalink
⚗️ Meh
Browse files Browse the repository at this point in the history
  • Loading branch information
ducdetronquito committed Dec 1, 2021
0 parents commit 7185a9f
Show file tree
Hide file tree
Showing 6 changed files with 618 additions and 0 deletions.
17 changes: 17 additions & 0 deletions .traefik.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
displayName: Magic JWT
type: middleware

import: github.com/ZeroGachis/traefik-magic-jwt

summary: Traefik plugin that verify JWT token
testData:
key: |
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSv
vkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHc
aT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIy
tvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0
e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWb
V6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9
MwIDAQAB
-----END PUBLIC KEY-----
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Traefik Magic JWT

Traefik plugin for verifying JWT.

Fork of `github/APKO/jwt_rsa`
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/ZeroGachis/traefik-magic-jwt

go 1.16
211 changes: 211 additions & 0 deletions jwt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
package traefik_magic_jwt

import (
"context"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"log"
"net/http"
"os"
"strings"
"time"
)

type Config struct {
Key string `json:"key"`
Alg string `json:"-"`
InjectHeader string `json:"-"`
Debug bool `json:"debug,omitempty"`
White map[string]*WhiteUrl `json:"white,omitempty"`
}

func CreateConfig() *Config {
return &Config{}
}

type JwtPlugin struct {
next http.Handler
rsa interface{}
alg string
injectHeader string
debug bool
white map[string]*WhiteUrl
}

func New(context context.Context, next http.Handler, config *Config, _ string) (http.Handler, error) {
if len(config.Key) == 0 {
config.Key = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuKijNSLvTJqPV+H/MfoR\nI/EkasKIYBTujUTjN5nxrw6q7acJlyq5pzb1MMMQqe/h1ACMmoWJ9dLHJqVMFz+h\nNkx99eWkXDj2agTjnh6VetG6owdC0yYiN2nm5eFsLtj8HBPhKF+5WguLUXoeNhOc\n0zdEfI6UkyLp+xmKVzrs7wXmBVaz0nV69drIYo8RI1+AUzHKJVOuWwykpcH+wk8P\nGvxXGw7CzM2NWAF5B9OUB+InAFApXx8FLZ0jQOAvCJcPZ7So7isxIyCD5RlhbcId\n35ZmzwBuOlskdyswX78yGc46aEAWFDUkMfrXZEy+RGoj0KunXwKKufh+bHYsKmvC\nywIDAQAB\n-----END PUBLIC KEY-----"
}
if len(config.Alg) == 0 {
config.Alg = "RS256"
}
if len(config.InjectHeader) == 0 {
config.InjectHeader = "injectedPayload"
}
jwtPlugin := &JwtPlugin{
next: next,
injectHeader: config.InjectHeader,
debug: config.Debug,
alg: config.Alg,
white: config.White,
}
if config.Alg == "RS256" {
if err := jwtPlugin.ParseKeys(config.Key); err != nil {
return nil, err
}
} else if config.Alg == "HS256" {
jwtPlugin.rsa = []byte(config.Key)
} else {
return nil, errors.New("bad alg")
}
return jwtPlugin, nil
}

func (jwtPlugin *JwtPlugin) ServeHTTP(rw http.ResponseWriter, request *http.Request) {
ignoreExpired := false
logger := log.New(os.Stdout, "jwt: ["+request.RemoteAddr+"]", log.Ldate|log.Ltime)
if jwtPlugin.white != nil {
for _, v := range jwtPlugin.white {
if v.Type == "" {
v.Type = "full"
}
if strings.EqualFold(v.Type, "full") && strings.EqualFold(v.Method, request.Method) && strings.EqualFold(v.URL, request.URL.Path) {
log.Println("Serve White url")
jwtPlugin.next.ServeHTTP(rw, request)
return
}
if strings.EqualFold(v.Type, "refresh") && strings.EqualFold(v.Method, request.Method) && strings.EqualFold(v.URL, request.URL.Path) {
ignoreExpired = true
}
}
}
if err := jwtPlugin.CheckToken(request, ignoreExpired, logger); err != nil {
logger.Printf("Error Handle Token %+v\n", err)
httpError(rw, err.Message, err.StatusCode)
return
}
jwtPlugin.next.ServeHTTP(rw, request)
}
func (jwtPlugin *JwtPlugin) ParseKeys(certificate string) error {
if block, rest := pem.Decode([]byte(certificate)); block != nil {
if len(rest) > 0 {
return fmt.Errorf("extra data after a PEM certificate block")
}
if block.Type == "PUBLIC KEY" || block.Type == "RSA PUBLIC KEY" {
key, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return fmt.Errorf("failed to parse a PEM public key: %v", err)
}
jwtPlugin.rsa = key
}
}
return nil
}
func (jwtPlugin *JwtPlugin) CheckToken(request *http.Request, ignorExpired bool, log *log.Logger) *RequestError {
jwtToken, err := jwtPlugin.ExtractToken(request, log)
if err != nil {
return err
}
if jwtToken != nil {
if err = jwtPlugin.VerifyToken(jwtToken, log); err != nil {
return err
}
if !ignorExpired {
if err = handleTokenTime(jwtToken); err != nil {
return err
}
}
request.Header.Del("Authorization")
request.Header.Add(jwtPlugin.injectHeader, string(jwtToken.RawPayload))
}
return nil
}
func handleTokenTime(jwt *JWT) *RequestError {
expiredate, err := jwt.Payload.Exp.Int64()
if err != nil {
return expiredTokenError
}
if isExpire(expiredate) {
return expiredTokenError
}
return nil
}
func (jwtPlugin *JwtPlugin) ExtractToken(request *http.Request, log *log.Logger) (*JWT, *RequestError) {
authHeader, ok := request.Header["Authorization"]
if !ok {
log.Println("Header Authorization not found")
return nil, noTokenError
}
auth := authHeader[0]
if !strings.HasPrefix(auth, "Bearer ") {
log.Printf("No Beadrer token %s\n", auth)
return nil, noTokenError
}
parts := strings.Split(auth[7:], ".")
if len(parts) != 3 {
log.Println("Invalid Token format")
return nil, noTokenError
}
header, err := base64.RawURLEncoding.DecodeString(parts[0])
if err != nil {
log.Printf("Header: %+v\n", err)
return nil, badTokenError
}
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
if err != nil {
log.Printf("Payload: %+v\n", err)
return nil, badTokenError
}
signature, err := base64.RawURLEncoding.DecodeString(parts[2])
if err != nil {
log.Printf("Signature: %+v\n", err)
return nil, badTokenError
}
jwtToken := JWT{
Plaintext: []byte(auth[7 : len(parts[0])+len(parts[1])+8]),
Signature: signature,
RawPayload: payload,
}
err = json.Unmarshal(header, &jwtToken.Header)
if err != nil {
log.Printf("Json Header bad format %+v\n", err)
return nil, badTokenError
}
err = json.Unmarshal(payload, &jwtToken.Payload)
if err != nil {
log.Printf("Json Payload bad format %+v\n", err)
return nil, badTokenError
}
return &jwtToken, nil
}

func (jwtPlugin *JwtPlugin) VerifyToken(jwtToken *JWT, log *log.Logger) *RequestError {
for _, h := range jwtToken.Header.Crit {
if _, ok := supportedHeaderNames[h]; !ok {
log.Printf("unsupported header: %s\n", h)
return verifyTokenError
}
}
a, ok := tokenAlgorithms[jwtToken.Header.Alg]
if !ok {
log.Printf("unknown JWS algorithm: %s\n", jwtToken.Header.Alg)
return verifyTokenError
}
if jwtPlugin.alg != "" && jwtToken.Header.Alg != jwtPlugin.alg {
log.Printf("incorrect alg, expected %s got %s\n", jwtPlugin.alg, jwtToken.Header.Alg)
return verifyTokenError
}
if e := a.verify(jwtPlugin.rsa, a.hash, jwtToken.Plaintext, jwtToken.Signature); e != nil {
log.Printf("Verify Error %+v\n", e)
return verifyTokenError
}
return nil
}

func isExpire(ctime int64) bool {
return ctime < (time.Now().UnixNano() / 1000000000)
}
Loading

0 comments on commit 7185a9f

Please sign in to comment.