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 }