美文网首页
kratos中简单使用jwt和casbin

kratos中简单使用jwt和casbin

作者: 彳亍口巴 | 来源:发表于2022-09-27 16:46 被阅读0次

在上一篇文章中,我们了解了kratos框架对于中间件的注册和定义,在每个请求导到接口前都会先顺序执行注册的中间件,接口执行完成后再倒叙执行所有的中间件。
对于登录校验和权限的判断,应该是统一的,可以放到网关或者中间件中去处理,而不是放到接口中,接口只用关注业务逻辑就好了。因此需要在kratos中引入jwt和casbin,注册两个中间件,对请求进行jwt校验和权限校验

// NewWhiteListMatcher 创建casbin白名单
func NewWhiteListMatcher() selector.MatchFunc {
    whiteList := make(map[string]struct{})
    whiteList["/admin.v1.AdminService/Login"] = struct{}{}
    whiteList["/admin.v1.AdminService/Logout"] = struct{}{}
    whiteList["/admin.v1.AdminService/Register"] = struct{}{}
    whiteList["/admin.v1.AdminService/GetPublicContent"] = struct{}{}
    return func(ctx context.Context, operation string) bool {
        if _, ok := whiteList[operation]; ok {
            fmt.Println("in white list operation:", operation)
            return false
        }
        fmt.Println("not in white list operation:", operation)
        return true
    }
}

// NewMiddleware 创建中间件
func NewMiddleware(ac *conf.Auth, logger log.Logger) http.ServerOption {
    m, _ := model.NewModelFromFile("../../configs/authz/authz_model.conf")
    a := fileAdapter.NewAdapter("../../configs/authz/authz_policy.csv")

    return http.Middleware(
        recovery.Recovery(),
        //tracing.Server(),
        //logging.Server(logger),
        selector.Server(
            jwt.Server(
                func(token *jwtV4.Token) (interface{}, error) {
                    return []byte(ac.ApiKey), nil
                },
                jwt.WithSigningMethod(jwtV4.SigningMethodHS256),
            ),
            casbinM.Server(
                casbinM.WithCasbinModel(m),
                casbinM.WithCasbinPolicy(a),
                casbinM.WithSecurityUserCreator(myAuthz.NewSecurityUser),
            ),
        ).Match(NewWhiteListMatcher()).Build(),
    )
}

casbin 启动时可以创建对应的白名单,在白名单中的请求不需要校验权限,casbin的权限配置在本地,有对应的账号和对应的角色

// NewHTTPServer new an HTTP server.
func NewHTTPServer(c *conf.Server, ac *conf.Auth, logger log.Logger, s *service.AdminService) *http.Server {
    var opts = []http.ServerOption{
        NewMiddleware(ac, logger),
        http.Filter(handlers.CORS(
            handlers.AllowedHeaders([]string{"X-Requested-With", "Content-Type", "Authorization"}),
            handlers.AllowedMethods([]string{"GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS"}),
            handlers.AllowedOrigins([]string{"*"}),
        )),
    }
    if c.Http.Network != "" {
        opts = append(opts, http.Network(c.Http.Network))
    }
    if c.Http.Addr != "" {
        opts = append(opts, http.Address(c.Http.Addr))
    }
    if c.Http.Timeout != nil {
        opts = append(opts, http.Timeout(c.Http.Timeout.AsDuration()))
    }
    srv := http.NewServer(opts...)

    h := openapiv2.NewHandler()
    srv.HandlePrefix("/q/", h)

    v1.RegisterAdminServiceHTTPServer(srv, s)
    return srv
}

注册http服务时将中间件注册到kratos中


jwt和casbin的运行过程

每一次请求,都需要判断path是否在单名单中,若在白名单中,则放行,不进行任何校验操作,比如login接口,一定需要在白名单中,因为这个接口需要生成新的token,生成新的token后,客户端每次请求都可以将token放到header的Authorization字段中,jwt每次都会从请求头中获取这个字段再进行校验

func Server(keyFunc jwt.Keyfunc, opts ...Option) middleware.Middleware {
    o := &options{
        signingMethod: jwt.SigningMethodHS256,
    }
    for _, opt := range opts {
        opt(o)
    }
    return func(handler middleware.Handler) middleware.Handler {
        return func(ctx context.Context, req interface{}) (interface{}, error) {
            if header, ok := transport.FromServerContext(ctx); ok {
                if keyFunc == nil {
                    return nil, ErrMissingKeyFunc
                }
                auths := strings.SplitN(header.RequestHeader().Get(authorizationKey), " ", 2)
                if len(auths) != 2 || !strings.EqualFold(auths[0], bearerWord) {
                    return nil, ErrMissingJwtToken
                }
                jwtToken := auths[1]
                var (
                    tokenInfo *jwt.Token
                    err       error
                )
                if o.claims != nil {
                    tokenInfo, err = jwt.ParseWithClaims(jwtToken, o.claims(), keyFunc)
                } else {
                    tokenInfo, err = jwt.Parse(jwtToken, keyFunc)
                }
                if err != nil {
                    if ve, ok := err.(*jwt.ValidationError); ok {
                        if ve.Errors&jwt.ValidationErrorMalformed != 0 {
                            return nil, ErrTokenInvalid
                        } else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 {
                            return nil, ErrTokenExpired
                        } else {
                            return nil, ErrTokenParseFail
                        }
                    }
                    return nil, errors.Unauthorized(reason, err.Error())
                } else if !tokenInfo.Valid {
                    return nil, ErrTokenInvalid
                } else if tokenInfo.Method != o.signingMethod {
                    return nil, ErrUnSupportSigningMethod
                }
                ctx = NewContext(ctx, tokenInfo.Claims)
                return handler(ctx, req)
            }
            return nil, ErrWrongContext
        }
    }
}

jwt校验通过后,需要进行casbin权限校验,权限校验的时候也是先从请求头中拿到token信息,再转换成对应的username(实际上token的生成依赖的是username),然后再拿到请求路径等信息进行校验

// ParseFromContext casbin校验客户端传过来的token
func (su *SecurityUser) ParseFromContext(ctx context.Context) error {
    // 从jwt的token中获取用户名
    if claims, ok := jwt.FromContext(ctx); ok {
        su.AuthorityId = claims.(jwtV4.MapClaims)[ClaimAuthorityId].(string)
        fmt.Printf("AuthorityId:%s\n", su.AuthorityId) // 实际上就是请求的username 如zzr
    } else {
        fmt.Printf("missing claim\n")
        return errors.New("jwt claim missing")
    }

    if header, ok := transport.FromServerContext(ctx); ok {
        su.Path = header.Operation()
        su.Method = "*"
        for _, key := range header.RequestHeader().Keys() {
            fmt.Printf("key:%s value:%s\n", key, header.RequestHeader().Get(key))
        }
    } else {
        return errors.New("jwt claim missing")
    }

    return nil
}

func Server(opts ...Option) middleware.Middleware {
    o := &options{
        securityUserCreator: nil,
    }
    for _, opt := range opts {
        opt(o)
    }

    o.enforcer, _ = stdcasbin.NewSyncedEnforcer(o.model, o.policy)

    return func(handler middleware.Handler) middleware.Handler {
        return func(ctx context.Context, req interface{}) (interface{}, error) {

            if o.enforcer == nil {
                return nil, ErrEnforcerMissing
            }
            if o.securityUserCreator == nil {
                return nil, ErrSecurityUserCreatorMissing
            }

            securityUser := o.securityUserCreator()
            if err := securityUser.ParseFromContext(ctx); err != nil {
                return nil, ErrSecurityParseFailed
            }

            ctx = context.WithValue(ctx, SecurityUserContextKey, securityUser)

            allowed, err := o.enforcer.Enforce(securityUser.GetSubject(), securityUser.GetObject(), securityUser.GetAction())
            if err != nil {
                return nil, err
            }
            if !allowed {
                return nil, ErrUnauthorized
            }
            return handler(ctx, req)
        }
    }
}

若校验通过,则放行本次请求,否则返回对应的错误码和错误原因

相关文章

网友评论

      本文标题:kratos中简单使用jwt和casbin

      本文链接:https://www.haomeiwen.com/subject/mcofartx.html