github.com/lingyao2333/mo-zero@v1.4.1/rest/handler/authhandler.go (about)

     1  package handler
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"net/http"
     7  	"net/http/httputil"
     8  
     9  	"github.com/golang-jwt/jwt/v4"
    10  	"github.com/lingyao2333/mo-zero/core/logx"
    11  	"github.com/lingyao2333/mo-zero/rest/internal/response"
    12  	"github.com/lingyao2333/mo-zero/rest/token"
    13  )
    14  
    15  const (
    16  	jwtAudience    = "aud"
    17  	jwtExpire      = "exp"
    18  	jwtId          = "jti"
    19  	jwtIssueAt     = "iat"
    20  	jwtIssuer      = "iss"
    21  	jwtNotBefore   = "nbf"
    22  	jwtSubject     = "sub"
    23  	noDetailReason = "no detail reason"
    24  )
    25  
    26  var (
    27  	errInvalidToken = errors.New("invalid auth token")
    28  	errNoClaims     = errors.New("no auth params")
    29  )
    30  
    31  type (
    32  	// An AuthorizeOptions is authorize options.
    33  	AuthorizeOptions struct {
    34  		PrevSecret string
    35  		Callback   UnauthorizedCallback
    36  	}
    37  
    38  	// UnauthorizedCallback defines the method of unauthorized callback.
    39  	UnauthorizedCallback func(w http.ResponseWriter, r *http.Request, err error)
    40  	// AuthorizeOption defines the method to customize an AuthorizeOptions.
    41  	AuthorizeOption func(opts *AuthorizeOptions)
    42  )
    43  
    44  // Authorize returns an authorization middleware.
    45  func Authorize(secret string, opts ...AuthorizeOption) func(http.Handler) http.Handler {
    46  	var authOpts AuthorizeOptions
    47  	for _, opt := range opts {
    48  		opt(&authOpts)
    49  	}
    50  
    51  	parser := token.NewTokenParser()
    52  	return func(next http.Handler) http.Handler {
    53  		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    54  			tok, err := parser.ParseToken(r, secret, authOpts.PrevSecret)
    55  			if err != nil {
    56  				unauthorized(w, r, err, authOpts.Callback)
    57  				return
    58  			}
    59  
    60  			if !tok.Valid {
    61  				unauthorized(w, r, errInvalidToken, authOpts.Callback)
    62  				return
    63  			}
    64  
    65  			claims, ok := tok.Claims.(jwt.MapClaims)
    66  			if !ok {
    67  				unauthorized(w, r, errNoClaims, authOpts.Callback)
    68  				return
    69  			}
    70  
    71  			ctx := r.Context()
    72  			for k, v := range claims {
    73  				switch k {
    74  				case jwtAudience, jwtExpire, jwtId, jwtIssueAt, jwtIssuer, jwtNotBefore, jwtSubject:
    75  					// ignore the standard claims
    76  				default:
    77  					ctx = context.WithValue(ctx, k, v)
    78  				}
    79  			}
    80  
    81  			next.ServeHTTP(w, r.WithContext(ctx))
    82  		})
    83  	}
    84  }
    85  
    86  // WithPrevSecret returns an AuthorizeOption with setting previous secret.
    87  func WithPrevSecret(secret string) AuthorizeOption {
    88  	return func(opts *AuthorizeOptions) {
    89  		opts.PrevSecret = secret
    90  	}
    91  }
    92  
    93  // WithUnauthorizedCallback returns an AuthorizeOption with setting unauthorized callback.
    94  func WithUnauthorizedCallback(callback UnauthorizedCallback) AuthorizeOption {
    95  	return func(opts *AuthorizeOptions) {
    96  		opts.Callback = callback
    97  	}
    98  }
    99  
   100  func detailAuthLog(r *http.Request, reason string) {
   101  	// discard dump error, only for debug purpose
   102  	details, _ := httputil.DumpRequest(r, true)
   103  	logx.Errorf("authorize failed: %s\n=> %+v", reason, string(details))
   104  }
   105  
   106  func unauthorized(w http.ResponseWriter, r *http.Request, err error, callback UnauthorizedCallback) {
   107  	writer := response.NewHeaderOnceResponseWriter(w)
   108  
   109  	if err != nil {
   110  		detailAuthLog(r, err.Error())
   111  	} else {
   112  		detailAuthLog(r, noDetailReason)
   113  	}
   114  
   115  	// let callback go first, to make sure we respond with user-defined HTTP header
   116  	if callback != nil {
   117  		callback(writer, r, err)
   118  	}
   119  
   120  	// if user not setting HTTP header, we set header with 401
   121  	writer.WriteHeader(http.StatusUnauthorized)
   122  }