authhandler.go 3.1 KB

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