package handler import ( "context" "errors" "net/http" "net/http/httputil" "github.com/golang-jwt/jwt/v4" "github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/rest/internal/response" "github.com/zeromicro/go-zero/rest/token" ) const ( jwtAudience = "aud" jwtExpire = "exp" jwtId = "jti" jwtIssueAt = "iat" jwtIssuer = "iss" jwtNotBefore = "nbf" jwtSubject = "sub" noDetailReason = "no detail reason" ) var ( errInvalidToken = errors.New("invalid auth token") errNoClaims = errors.New("no auth params") ) type ( // An AuthorizeOptions is authorize options. AuthorizeOptions struct { PrevSecret string Callback UnauthorizedCallback } // UnauthorizedCallback defines the method of unauthorized callback. UnauthorizedCallback func(w http.ResponseWriter, r *http.Request, err error) // AuthorizeOption defines the method to customize an AuthorizeOptions. AuthorizeOption func(opts *AuthorizeOptions) ) // Authorize returns an authorization middleware. func Authorize(secret string, opts ...AuthorizeOption) func(http.Handler) http.Handler { var authOpts AuthorizeOptions for _, opt := range opts { opt(&authOpts) } parser := token.NewTokenParser() return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { tok, err := parser.ParseToken(r, secret, authOpts.PrevSecret) if err != nil { unauthorized(w, r, err, authOpts.Callback) return } if !tok.Valid { unauthorized(w, r, errInvalidToken, authOpts.Callback) return } claims, ok := tok.Claims.(jwt.MapClaims) if !ok { unauthorized(w, r, errNoClaims, authOpts.Callback) return } ctx := r.Context() for k, v := range claims { switch k { case jwtAudience, jwtExpire, jwtId, jwtIssueAt, jwtIssuer, jwtNotBefore, jwtSubject: // ignore the standard claims default: ctx = context.WithValue(ctx, k, v) } } next.ServeHTTP(w, r.WithContext(ctx)) }) } } // WithPrevSecret returns an AuthorizeOption with setting previous secret. func WithPrevSecret(secret string) AuthorizeOption { return func(opts *AuthorizeOptions) { opts.PrevSecret = secret } } // WithUnauthorizedCallback returns an AuthorizeOption with setting unauthorized callback. func WithUnauthorizedCallback(callback UnauthorizedCallback) AuthorizeOption { return func(opts *AuthorizeOptions) { opts.Callback = callback } } func detailAuthLog(r *http.Request, reason string) { // discard dump error, only for debug purpose details, _ := httputil.DumpRequest(r, true) logx.Errorf("authorize failed: %s\n=> %+v", reason, string(details)) } func unauthorized(w http.ResponseWriter, r *http.Request, err error, callback UnauthorizedCallback) { writer := response.NewHeaderOnceResponseWriter(w) if err != nil { detailAuthLog(r, err.Error()) } else { detailAuthLog(r, noDetailReason) } // let callback go first, to make sure we respond with user-defined HTTP header if callback != nil { callback(writer, r, err) } // if user not setting HTTP header, we set header with 401 writer.WriteHeader(http.StatusUnauthorized) }