103 lines
2.4 KiB
Go
103 lines
2.4 KiB
Go
package jwtmw
|
||
|
||
import (
|
||
"net/http"
|
||
"strings"
|
||
|
||
"github.com/golang-jwt/jwt/v4"
|
||
)
|
||
|
||
type Claims struct {
|
||
TenantID int64 `json:"tenant_id"`
|
||
UserID int64 `json:"user_id"`
|
||
Role string `json:"role"`
|
||
jwt.RegisteredClaims
|
||
}
|
||
|
||
// Middleware 网关侧预检 JWT;公开路径放行。中台仍会再次校验。
|
||
func Middleware(secret string, publicPrefixes []string) func(http.HandlerFunc) http.HandlerFunc {
|
||
parser := jwt.NewParser(jwt.WithValidMethods([]string{"HS256"}))
|
||
return func(next http.HandlerFunc) http.HandlerFunc {
|
||
return func(w http.ResponseWriter, r *http.Request) {
|
||
path := r.URL.Path
|
||
for _, p := range publicPrefixes {
|
||
if path == p || strings.HasPrefix(path, p) {
|
||
next(w, r)
|
||
return
|
||
}
|
||
}
|
||
if r.Method == http.MethodOptions {
|
||
next(w, r)
|
||
return
|
||
}
|
||
auth := r.Header.Get("Authorization")
|
||
if !strings.HasPrefix(strings.ToLower(auth), "bearer ") {
|
||
writeErr(w, http.StatusUnauthorized, "gateway: missing bearer token")
|
||
return
|
||
}
|
||
raw := strings.TrimSpace(auth[len("Bearer "):])
|
||
if raw == "" || strings.Count(raw, ".") != 2 {
|
||
writeErr(w, http.StatusUnauthorized, "gateway: malformed token")
|
||
return
|
||
}
|
||
token, err := parser.ParseWithClaims(raw, &Claims{}, func(t *jwt.Token) (any, error) {
|
||
return []byte(secret), nil
|
||
})
|
||
if err != nil || token == nil || !token.Valid {
|
||
writeErr(w, http.StatusUnauthorized, "gateway: invalid token ("+shortJWTErr(err)+"),请重新登录")
|
||
return
|
||
}
|
||
next(w, r)
|
||
}
|
||
}
|
||
}
|
||
|
||
func shortJWTErr(err error) string {
|
||
if err == nil {
|
||
return "rejected"
|
||
}
|
||
msg := err.Error()
|
||
switch {
|
||
case strings.Contains(msg, "expired"):
|
||
return "expired"
|
||
case strings.Contains(msg, "signature"):
|
||
return "bad signature"
|
||
case strings.Contains(msg, "malformed"):
|
||
return "malformed"
|
||
case strings.Contains(msg, "used before"):
|
||
return "not yet valid"
|
||
default:
|
||
if len(msg) > 80 {
|
||
return msg[:80]
|
||
}
|
||
return msg
|
||
}
|
||
}
|
||
|
||
func writeErr(w http.ResponseWriter, code int, msg string) {
|
||
w.Header().Set("Content-Type", "application/json")
|
||
w.WriteHeader(code)
|
||
_, _ = w.Write([]byte(`{"code":` + itoa(code) + `,"message":"` + escapeJSON(msg) + `"}`))
|
||
}
|
||
|
||
func escapeJSON(s string) string {
|
||
s = strings.ReplaceAll(s, `\`, `\\`)
|
||
s = strings.ReplaceAll(s, `"`, `\"`)
|
||
s = strings.ReplaceAll(s, "\n", " ")
|
||
return s
|
||
}
|
||
|
||
func itoa(n int) string {
|
||
if n == 0 {
|
||
return "0"
|
||
}
|
||
var b [12]byte
|
||
i := len(b)
|
||
for n > 0 {
|
||
i--
|
||
b[i] = byte('0' + n%10)
|
||
n /= 10
|
||
}
|
||
return string(b[i:])
|
||
}
|