kratos发布了v2版本,新版本全面拥抱了grpc,但是相对的对常用的工具组件支持不够友好,小型项目也可能不需要用到protocol,好在kratos开放了一些中间件和handle可以方便的进行自定义,本文介绍http Filter(filters ...FilterFunc) ServerOption
先看一下Filter 的定义
package http
import "net/http"
// FilterFunc is a function which receives an http.Handler and returns another http.Handler.
type FilterFunc func(http.Handler) http.Handler
// FilterChain returns a FilterFunc that specifies the chained handler for HTTP Router.
func FilterChain(filters ...FilterFunc) FilterFunc {
return func(next http.Handler) http.Handler {
for i := len(filters) - 1; i >= 0; i-- {
next = filters[i](next)
}
return next
}
}
filter handle 函数的参数和返回都是http.handler 执行时机在业务逻辑handlefunc之前。下面根据具体定义来编写一个基于Redis的鉴权登录拦截器,具体代码如下:
import (
"context"
"encoding/json"
"github.com/go-kratos/kratos/v2/log"
http2 "github.com/go-kratos/kratos/v2/transport/http"
"github.com/go-redis/redis"
"net/http"
"note-svr/ecode"
"note-svr/internal/model/user"
"note-svr/middleware/reply"
"time"
)
const (
// token 有效期
tokenExpire = 60 * 60 * 24 * 60 * time.Second
)
type Auth struct {
redis *redis.Client
expire time.Duration
}
type authKey struct{}
type Options struct {
Addr string
WriteTimeout time.Duration
ReadTimeout time.Duration
TokenExpire time.Duration
}
func New(opt *Options) *Auth {
if opt.TokenExpire == 0 {
opt.TokenExpire = tokenExpire
}
return &Auth{
redis: redis.NewClient(&redis.Options{Addr: opt.Addr, WriteTimeout: opt.WriteTimeout, ReadTimeout: opt.ReadTimeout}),
expire: opt.TokenExpire,
}
}
func (a *Auth) PutUser(user *user.User) error {
bs, err := json.Marshal(user)
if err != nil {
log.Errorf("auth json marshal err: %v", err)
return err
}
err = a.redis.Set(user.Token, bs, a.expire).Err()
return err
}
func (a *Auth) GetUser(token string) (*user.User, error) {
bs, err := a.redis.Get(token).Bytes()
if err != nil {
log.Errorf("token %s, bs: %x", token, bs)
return nil, err
}
u := &user.User{}
if err := json.Unmarshal(bs, &u); err != nil {
log.Errorf("auth json marshal err: %v", err)
}
return u, nil
}
func (a *Auth) Guest() http2.FilterFunc {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
u, _ := a.tokenUser(request)
if u != nil {
ctx := context.WithValue(request.Context(), authKey{}, u)
request = request.WithContext(ctx)
}
next.ServeHTTP(writer, request)
})
}
}
func (a *Auth) User() http2.FilterFunc {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
u, err := a.tokenUser(request)
if err != nil || u == nil {
e, ok := err.(*ecode.Err)
if !ok {
e = ecode.New(ecode.NoLoginCode, err.Error())
}
resp, _ := json.Marshal(reply.Status{Code: e.Code(), Message: e.Message})
writer.Write(resp)
return
}
ctx := context.WithValue(request.Context(), authKey{}, u)
request = request.WithContext(ctx)
next.ServeHTTP(writer, request)
})
}
}
func (a *Auth) tokenUser(request *http.Request) (u *user.User, err error) {
token := ""
if request.Form.Get("token") != "" {
token = request.Form.Get("token")
}
if ctoken, err := request.Cookie("token"); err == nil {
if ctoken.Value != "" {
token = ctoken.Value
}
}
if htoken := request.Header.Get("token"); htoken != "" {
token = htoken
}
if token == "" {
return nil, ecode.NoLoginErr
}
return a.GetUser(token)
}
func FromContext(ctx context.Context) (u *user.User, ok bool) {
u, ok = ctx.Value(authKey{}).(*user.User)
return
}
使用时先调用New函数初始化auth对象,Guest() 是游客态拦截器,未登录不会拦截,登录时会向context写入用户信息,User() 未登录会直接返回response。
项目中应用:
1、首先需要在登录时调用PutUser将用户信息写入Redis
2、server中初始化auth
au := auth.New(&auth.Options{
Addr: data.Redis.Addr,
WriteTimeout: data.Redis.WriteTimeout,
ReadTimeout: data.Redis.ReadTimeout,
})
3、配置路由时添加拦截器
user.GET("/info", UserInfo, au.User())
4、业务中通过context获取用户信息
func UserInfo(ctx http.Context) error {
u, _ := auth.FromContext(ctx)
return reply.JSON(ctx, u, nil)
}
kratos v2官方文档地址:简介 | Kratos