go-zero/rest/handler/authhandler.go

161 lines
3.9 KiB
Go
Raw Normal View History

2020-07-29 18:00:04 +08:00
package handler
2020-07-26 17:09:05 +08:00
import (
2021-03-09 21:30:45 +08:00
"bufio"
2020-07-26 17:09:05 +08:00
"context"
2020-07-30 16:22:18 +08:00
"errors"
2021-03-09 21:30:45 +08:00
"net"
2020-07-26 17:09:05 +08:00
"net/http"
"net/http/httputil"
"github.com/dgrijalva/jwt-go"
2020-08-08 16:40:10 +08:00
"github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/rest/token"
2020-07-26 17:09:05 +08:00
)
const (
2020-07-30 16:22:18 +08:00
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")
2020-07-26 17:09:05 +08:00
)
type (
2021-03-01 19:15:35 +08:00
// A AuthorizeOptions is authorize options.
2020-07-26 17:09:05 +08:00
AuthorizeOptions struct {
PrevSecret string
Callback UnauthorizedCallback
}
2021-03-01 19:15:35 +08:00
// UnauthorizedCallback defines the method of unauthorized callback.
2020-07-26 17:09:05 +08:00
UnauthorizedCallback func(w http.ResponseWriter, r *http.Request, err error)
2021-03-01 19:15:35 +08:00
// AuthorizeOption defines the method to customize an AuthorizeOptions.
AuthorizeOption func(opts *AuthorizeOptions)
2020-07-26 17:09:05 +08:00
)
2021-03-01 19:15:35 +08:00
// Authorize returns an authorize middleware.
2020-07-26 17:09:05 +08:00
func Authorize(secret string, opts ...AuthorizeOption) func(http.Handler) http.Handler {
var authOpts AuthorizeOptions
for _, opt := range opts {
opt(&authOpts)
}
parser := token.NewTokenParser()
2020-07-26 17:09:05 +08:00
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)
2020-07-26 17:09:05 +08:00
if err != nil {
unauthorized(w, r, err, authOpts.Callback)
return
}
if !tok.Valid {
2020-07-30 16:22:18 +08:00
unauthorized(w, r, errInvalidToken, authOpts.Callback)
2020-07-26 17:09:05 +08:00
return
}
claims, ok := tok.Claims.(jwt.MapClaims)
2020-07-26 17:09:05 +08:00
if !ok {
2020-07-30 16:22:18 +08:00
unauthorized(w, r, errNoClaims, authOpts.Callback)
2020-07-26 17:09:05 +08:00
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))
})
}
}
2021-03-01 19:15:35 +08:00
// WithPrevSecret returns an AuthorizeOption with setting previous secret.
2020-07-26 17:09:05 +08:00
func WithPrevSecret(secret string) AuthorizeOption {
return func(opts *AuthorizeOptions) {
opts.PrevSecret = secret
}
}
2021-03-01 19:15:35 +08:00
// WithUnauthorizedCallback returns an AuthorizeOption with setting unauthorized callback.
2020-07-26 17:09:05 +08:00
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 := newGuardedResponseWriter(w)
2020-07-30 16:22:18 +08:00
if err != nil {
detailAuthLog(r, err.Error())
} else {
detailAuthLog(r, noDetailReason)
}
2020-07-26 17:09:05 +08:00
if callback != nil {
callback(writer, r, err)
}
2020-07-30 16:22:18 +08:00
2020-07-26 17:09:05 +08:00
writer.WriteHeader(http.StatusUnauthorized)
}
type guardedResponseWriter struct {
writer http.ResponseWriter
wroteHeader bool
}
func newGuardedResponseWriter(w http.ResponseWriter) *guardedResponseWriter {
return &guardedResponseWriter{
writer: w,
}
}
func (grw *guardedResponseWriter) Flush() {
if flusher, ok := grw.writer.(http.Flusher); ok {
flusher.Flush()
}
}
2020-07-26 17:09:05 +08:00
func (grw *guardedResponseWriter) Header() http.Header {
return grw.writer.Header()
}
2021-03-09 21:30:45 +08:00
// Hijack implements the http.Hijacker interface.
// This expands the Response to fulfill http.Hijacker if the underlying http.ResponseWriter supports it.
func (grw *guardedResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return grw.writer.(http.Hijacker).Hijack()
}
2020-07-26 17:09:05 +08:00
func (grw *guardedResponseWriter) Write(body []byte) (int, error) {
return grw.writer.Write(body)
}
func (grw *guardedResponseWriter) WriteHeader(statusCode int) {
if grw.wroteHeader {
return
}
grw.wroteHeader = true
grw.writer.WriteHeader(statusCode)
}